call和apply区别,哪个性能更好?

call和apply都能够让函数执行,并指定函数执行时的this指向。区别如下:

  • call的参数个数不确定,第一个参数为this指向,后面的参数需要一个一个分开来写;
  • apply参数个数固定,接收两个参数,第一个参数为this指向,第二个参数为数组或者类数组对象。
  • bind是给函数绑定this指向,返回一个绑定了this的函数,并不会执行函数。

性能:3个参数以内,两者性能差不多,3个参数以上,call性能更好。

需要注意:当参数为数组时,优先使用apply,也可以使用call加ES6的展开运算符来实现。

console.time可以用来测试程序的执行时间。

call

call方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。

call模拟实现

Function.prototype.myCall = function(context) {
    if(typeof this !== 'function') {
        throw Error('not a function');
    }
    context.fn = this;
    const args = [...arguments].slice(1);
    const res = context.fn(...args);
    delete context.fn;
    return res;
}
var name = 'windowName';
const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this);
        console.log([...arguments]); // [ 1, 2, 4, 6 ]
        console.log(this.name);
    }
};

const fn = obj.sayName;
fn(); // windowName
fn.myCall(obj, 1, 2, 4, 6); // lisi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

apply

apply方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

apply(null, [1, 2])

如果函数处于非严格模式下,则指定为null或undefined 时会自动替换为指向全局对象,原始值会被包装。

非严格模式下:

var name = 'wangwu';
const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
        console.log([...arguments]); // [ 1, 2, 3 ]
        console.log(this.name); // wangwu
    }
};

obj.sayName.apply(null, [1, 2]);
1
2
3
4
5
6
7
8
9
10
11

严格模式下:

"use strict";
var name = 'wangwu';
const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this); // null
        console.log([...arguments]); // [ 1, 2, 3 ]
        console.log(this.name); // Uncaught TypeError: Cannot read property 'name' of null
    }
};

obj.sayName.apply(null, [1, 2]);
1
2
3
4
5
6
7
8
9
10
11
12
"use strict";
var name = 'wangwu';
const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this); // 12
        console.log([...arguments]); // [ 1, 2, 3 ]
        console.log(this.name); // undefined
    }
};

obj.sayName.apply(12, [1, 2]);

1
2
3
4
5
6
7
8
9
10
11
12
13

apply模拟实现

Function.prototype.myApply = function(context) {
    if(typeof this !== 'function') {
        throw Error('not a function');
    }
    context.fn = this;
    // 如果myApply有参数
    console.log(arguments);
    // arguments[1]本身就是数组
    if (arguments[1]) {
        // console.log(...arguments[1]);
        // 利用展开运算符(...)将数组参数展开
        return context.fn(...arguments[1]);
    }
    return context.fn();
}
var name = 'windowName';
const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this);
        console.log([...arguments]); // [ 3, 2, 4 ]
        console.log(this.name);
    }
};

const fn = obj.sayName;
// fn(); // windowName
fn.myApply(obj, [3, 2, 4]); // lisi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

bind

bind方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

构造函数效果模拟

function test() {
    this.hobbies = '篮球';
    console.log(this); // test { hobbies: '篮球' }
    console.log(this instanceof bindTest); // true
    console.log(this.name + '--' + this.age);
}

test.prototype.work = '工人';

const obj = {
    name: 'lisi',
    age: 12
};

var name = 'wangwu';
var age = 12;

const bindTest = test.bind(obj);
// bindTest当作构造函数调用
// 这个时候的this已经指向了p
const p = new bindTest();
console.log(p.hobbies); // 篮球
console.log(p.work); // 工人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

bind模拟实现

Function.prototype.mybind = function(thisArg) {
    if (typeofthis !== 'function') {
      throw TypeError('Bind must be called on a function');
    }
    // 拿到参数,为了传给调用者
    const args = Array.prototype.slice.call(arguments, 1),
      // 保存 this
      self = this,
      // 构建一个干净的函数,用于保存原函数的原型
      nop = function() {},
      // 绑定的函数
      bound = function() {
        // this instanceof nop, 判断是否使用 new 来调用 bound
        // 如果是 new 来调用的话,this的指向就是其实例,
        // 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
        return self.apply(
          thisinstanceof nop ? this : thisArg,
          args.concat(Array.prototype.slice.call(arguments))
        );
      };

    // 箭头函数没有prototype,箭头函数this永远指向它所在的作用域
    if (this.prototype) {
      nop.prototype = this.prototype;
    }
    // 修改绑定函数的原型指向
    bound.prototype = new nop();

    return bound;
  }
}

const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this);
        console.log([...arguments]); // [ 1, 2, 3 ]
        return this.name;
    }
};
const unboundFn = obj.sayName;
const boundFn = unboundFn.myBind(obj, 1, 2, 3);
console.log(boundFn()); // lisi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

参考文档

  1. 前端战五渣学JavaScript——call、apply以及bind
  2. this、apply、call、bind