6

前言

在js中,所有的函数在被调用的时候都会默认传入两个参数,一个是this,还有一个是arguments。在默认情况下this都是指当前的调用函数的对象。但是有时候我们需要改变this的指向,也就是说使函数可以被其他对象来调用,那么我们应该怎样做呢?这时候我们就可以使用call,apply和bind方法了。
那么call,apply和bind来自哪里?
在js中所有的函数都是Function的实例,而且对于Function来说,它的原型即Function.prototype中含有很多东西,其中call,apply和bind方法就是Function原型中的方法,所以根据原型的规则,所有的函数都可以使用原型中属性和方法,所以来说,对于所有的函数都可以使用call,apply和bind方法。

一、方法定义

apply方法

使用 apply, 你可以继承其他对象的方法:

注意这里apply()的第一个参数是null,在非严格模式下,第一个参数为null或者undefined时会自动替换为指向全局对象,apply()的第二个参数为数组或类数组。

function fruits() {}
fruits.prototype = {
    color: "red",
    say: function() {
        console.log("My color is " + this.color);
    }
}
var apple = new fruits;
apple.say();    //My color is red

但是如果我们有一个对象banana= {color : "yellow"},我们不想对它重新定义say方法,那么我们可以通过callapplyapplesay方法:

banana = {
    color: "yellow"
}
apple.say.call(banana);     //My color is yellow
apple.say.apply(banana);    //My color is yellow

所以,可以看出callapply是为了动态改变this而出现的,当一个object没有某个方法(本案例中banana没有say方法),但是其他的有(本案例中applesay方法),我们可以借助callapply用其它对象的方法来操作

call方法

call()是apply()的一颗语法糖,作用和apply()一样,同样可实现继承,唯一的区别就在于call()接收的是参数列表,而apply()则接收参数数组。

bind方法

bind()的作用与call()和apply()一样,都是可以改变函数运行时上下文,区别是call()和apply()在调用函数之后会立即执行,而bind()方法调用并改变函数运行时上下文后,返回一个新的函数,供我们需要时再调用。

this.num = 9; 
var mymodule = {
  num: 81,
  getNum: function() { 
    console.log(this.num);
  }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 因为在这个例子中,"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

二、call,apply,bind区别

1、call的arg传参需一个一个传,apply则直接传一个数组。

function hello(name,age){
  console.log(name);
  console.log(age);
}
hello.call(this,"tsrot",24);
hello.apply(this,["tsrot",24]);

2、call和apply直接执行函数,而bind需要再一次调用。

var obj = {
    x: 81,
};
var foo = {
    getX: function() {
        return this.x;
    }
}
console.log(foo.getX.bind(obj)());  
console.log(foo.getX.call(obj));    
console.log(foo.getX.apply(obj));   

3、如何选用

  • 如果不需要关心具体有多少参数被传入函数,选用apply();
  • 如果确定函数可接收多少个参数,并且想一目了然表达形参和实参的对应关系,用call();
  • 如果我们想要将来再调用方法,不需立即得到函数返回结果,则使用bind();

三、运用场景

1、实现继承

function Animal(name) {
  this.name = name;
  this.showName = function () {
    console.log(this.name);
  }
  }
function Cat(name) {
  Animal.call(this, name); 
  }
var cat = new Cat('Black Cat');
cat.showName(); 

2、数组追加

var array1 = [1 , 2 , 3, 5];
var array2 = ["xie" , "li" , "qun" , "tsrot"];
Array.prototype.push.apply(array1, array2);
console.log(array1);

3、获取数组中的最大值和最小值

var num = [1,3,5,7,2,-10,11];
var maxNum = Math.max.apply(Math, num);
var minNum = Math.min.apply(Math, num);
console.log(maxNum); 
console.log(minNum); 

4、将伪数组转化为数组

var fakeArr = {0:'a',1:'b',length:2};
var arr1 = Array.prototype.slice.call(fakeArr);
console.log(arr1[0]); 
var arr2 = [].slice.call(fakeArr);
console.log(arr2[0]); 
arr1.push("c");
console.log(arr1); 

5、保存this变量

var foo = {
    bar : 1,
    eventBind: function(){
        var _this = this ;
        $('.someClass').on('click',function(event) {
            console.log(_this.bar);     
        });
    }
}
var foo = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            console.log(this.bar);      
        }.bind(this));
    }
}

四、手写call,apply及bind函数

首先从以下几点来考虑如何实现这几个函数

  • 不传入第一个参数,那么上下文默认为window
  • 改变了this指向,让新的对象可以执行该函数,并能接受参数

先来实现call

Function.prototype.myCall = function(context) { 
    if (typeof  this !== 'function') { 
        throw  new  TypeError('Error') 
    } 
    context = context || window ;
    context.fn = this  ;
    const args = [...arguments].slice(1); 
    const result = context.fn(...args) ;
    delete context.fn ;
    return result ;
}

以下是对实现的分析:

  • 首先context为可选参数,如果不传的话默认上下文为window
  • 接下来给context创建一个fn属性,并将值设置为需要调用的函数
  • 因为call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
  • 然后调用函数并将对象上的函数删除

以上就是实现call的思路,apply的实现也类似,区别在于对参数的处理,所以就不一一分析思路了

    Function.prototype.myApply = function(context) { 
        if (typeof  this !== 'function') { 
            throw  new  TypeError('Error') 
        } 
        context = context || window ;
        context.fn = this;  
        let result // 处理参数和 call 有区别  
        if (arguments[1]) { 
            result = context.fn(...arguments[1]) 
        } else { 
            result = context.fn() 
        } 
        delete context.fn ;
        return result; 
    }

bind的实现对比其他两个函数略微地复杂了一点,因为bind需要返回一个函数,需要判断一些边界问题,以下是bind的实现

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    const _this = this
    const args = [...arguments].slice(1)
    // 返回一个函数
    return function F() {
        // 因为返回了一个函数,我们可以 new F(),所以需要判断
        if (this instanceof F) {
        return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
    }
}

以下是对实现的分析:

  • 前几步和之前的实现差不多,就不赘述了
  • bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式,我们先来说直接调用的方式
  • 对于直接调用来说,这里选择了apply的方式实现,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments)
  • 最后来说通过new的方式,对于new的情况来说,不会被任何方式改变this,所以对于这种情况我们需要忽略传入的this

将不断更新完善,期待您的批评指正!


薇薇
298 声望24 粉丝

路漫漫其修远兮,吾将上下而求索