call、apply、bind的应用与区别

730 阅读4分钟

call

call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window。

var arr = [1, 2, 3, 89, 46];
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4]);//89

可以这么理解:

obj1.fn(); 
obj1.fn.call(obj1);

fn1();
fn1.call(null);

f1(f2);
f1.call(null,f2);

看一个例子:

<script>
        var message = "我的名字是"
        var obj = {
            message: 'My name is: '
        }

        function getName(firstName, lastName) {
            console.log(this.message + firstName + ' ' + lastName);
        }
        // 用call修改this指向,this指向第一个参数
        getName.call(obj, 'Dot', 'Dolby');
        getName.call(undefined, 'Dot', 'Dolby');
        getName('Dot', 'Dolby');//默认第一个参数为undefined
    </script>

image.png

apply

apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。

var arr = [1,2,3,89,46];
var max = Math.max.apply(null,arr);//89

可以这么理解

obj1.fn();
obj1.fn.apply(obj1);

fn1();
fn1.apply(null);

f1(f2);
f1.apply(null,f2);

是不是觉得和前面写的call用法很像,事实上apply 和 call 的用法几乎相同, 唯一的差别在于:当函数需要传递多个变量时, apply 可以接受一个数组作为参数输入, call 则是接受一系列的单独变量。 看一个例子:

  <script>
        var message = "我的名字是"
        var obj = {
            message: 'My name is: '
        }

        function getName(firstName, lastName) {
            console.log(this.message + firstName + ' ' + lastName);
        }
        // 用call修改this指向,this指向第一个参数
        getName.apply(obj, ['Dot', 'Dolby']);
        getName.apply(undefined, ['Dot', 'Dolby']);
        getName('Dot', 'Dolby');//默认第一个参数为undefined

    </script>

image.png

可以看到,obj 是作为函数上下文的对象,函数 getName 中 this 指向了 obj 这个对象。参数 firstName 和 lastName 是放在数组中传入 getName 函数。

call和apply可用来借用别的对象的方法,这里以call()为例:

var Person1  = function () {
    this.name = 'Dot';
}
var Person2 = function () {
    this.getname = function () {
        console.log(this.name);
    }
    Person1.call(this);//调用Person1函数,并将Person1的this改为Person2的this
}
var person = new Person2();
person.getname();       // Dot

从上面我们看到,Person2 实例化出来的对象 person 通过 getname 方法拿到了 Person1 中的 name。因为在 Person2 中,Person1.call(this) 的作用就是将Person1的this改为Person2的this,那么 Person2 就有了 Person1 中的所有属性和方法了,即person.name='Dot',相当于 Person2 继承了 Person1 的属性和方法。

对于什么时候该用什么方法,其实不用纠结。如果你的参数本来就存在一个数组中,那自然就用 apply,如果参数比较散乱相互之间没什么关联,就用 call。像上面的找一组数中最大值的例子,当然是用apply合理。

bind

和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。

bind返回值是函数

var obj = {
    name: 'Dot'
}

function printName() {
    console.log(this.name);
}

var dot = printName.bind(obj)
console.log(dot); // function () { … }
dot();  // Dot

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 printName 中的 this 并没有被改变,依旧指向全局对象 window。

参数的使用

<script>
        function fn(a, b, c) {
            console.log(a, b, c);
        }
        var fn1 = fn.bind(null, 'Dot');

        fn('A', 'B', 'C');            // A B C
        fn1('A', 'B', 'C');           // Dot A B
        fn1('B', 'C');                // Dot B C
        console.log(fn.bind(null, 'Dot'));
        fn.call(null, 'Dot');      // Dot undefined undefined
    </script>

image.png 有个有趣的问题,如果连续 bind() 两次,亦或者是连续 bind() 三次那么输出的值是什么呢?像这样:

var bar = function(){
    console.log(this.x);
}

var foo = {
    x:3
}

var sed = {
    x:4
}

var func = bar.bind(foo).bind(sed);

func(); //?

var fiv = {
    x:5
}

var func = bar.bind(foo).bind(sed).bind(fiv);

func(); //?

答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

call 是把第二个及以后的参数作为 fn 方法的实参传进去,而 fn1 方法的实参实则是在 bind 中参数的基础上再往后排。

apply、call、bind比较 那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个例子:

var obj = {
    x: 81,
};

var foo = {
    getX: function() {
        return this.x;
    }
}

console.log(foo.getX.bind(obj)());  //81
console.log(foo.getX.call(obj));    //81
console.log(foo.getX.apply(obj));   //81

三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。

也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。

再总结一下:

  • apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
  • apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用后续参数传参;
  • bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。