一次搞懂bind、apply和call

374 阅读3分钟

最近在面试一些公司,发现考察 JS 基础的公司非常多,有一次问到了 bindapplycall 的区别,当场没答上来。这次就好好来复习一下 bindapplycall 的用法。

这三个方法的共同点在于改变 this 指向,能够把一个对象的方法交给另一个对象来执行,并且 applycall 是立即执行的。

为什么要改变 this 指向呢?比如A对象有一个 say 方法,B对象需要用到这个方法,我们不需要专门为B重新写这个方法,只需要借用A的方法即可。

apply

MDN文档上对其的解释为:

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

fn.apply(obj, [argsArray])

需要注意的是,apply 的调用者必须是函数,第二个参数必须是数组或者类数组,他们会被转换成数组传入 fn,映射到 fn 对应的参数上。这是 apply 和 call 之间很重要的区别。

我们可以使用 apply 来计算一个数组中的最大值:

const numbers = [5, 6, 2, 3, 7]

const max = Math.max.apply(null, numbers);

console.log(max);
// 输出结果: 7

还可以将一个数组中的每一项添加到另一个数组:

let array = ['a', 'b'];
let elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); 
// ["a", "b", 0, 1, 2]

上文有提到类数组,那什么是类数组呢?

一般数组的特征有:

  • 可以通过 index 来进行查询,比如 array[0]
  • 数组长度属性 length
  • 可通过 for 循环和 forEach 方法遍历。

类数组就是和数组有类似特征的对象(你没看错,类数组是个对象),比如下面这个对象:

let array = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
};

这个 array 对象可以通过 index 调用,具有 length 属性,同时也可以通过 for循环来遍历。

注意:类数组无法使用 forEach、splice、push 等数组原型链上的方法,毕竟类数组不是真数组。


call

call和apply比较类似,区别在于call方法接受的是参数列表,而apply接受的是一个参数数组。MDN 对 call 的解释为:

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

fn.call(obj, a, b, ...)

关于 call 需要注意:

  • 调用 call 的对象和 apply 一样,必须是个函数;
  • call 的第一个参数是一个对象,如果不传,默认为全局对象 window
  • 第二个参数开始,可以接受任意个参数,每个参数都会映射到相应位置的 fn 的参数上;
  • 如果将所有参数作为数组传入,他们会作为一个整体映射到 fn 对应的第一个参数上,之后的参数都为空。
fn.call(obj, 1, 2, 3)
// fn 接收的参数为 1, 2, 3

fn.call(obj, [1,2,3])
// fn 接收的参数实际上是 [1,2,3], undefined, undefined

bind

MDN 对 bind 的解释为:

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

bind 的语法如下:

fn.bind(obj[, arg1[, arg2[, ...]]])

bind 一样能改变函数的 this 指向,不过 bind 的返回值是函数,并且需要稍后调用才能执行。如果 bind 的第一个参数是 null 或者 undefinedthis 就指向全局对象 window

const module = {
	x: 12,
  getX: function(){
  	return this.x;
  }
}

const unboundGetX = module.getX;
// unboundGetX 为 undefined

const boundGetX = unboundGetX.bind(module)
// boundGetX 为 42

来一个总结

  • apply、call和bind都能改变对象执行上下文;
  • apply接收参数为数组或类数组,call 接收的是参数列表,bind接收的是函数;
  • apply和call是立即执行,bind需要稍后调用才会执行。