浅谈JavaScript中的apply、call和bind

469 阅读3分钟

摘要

  • 三种方法均可改变函数this关键字的指向。
  • apply()接受一参数数组,返回函数执行的结果。
  • call()接受一组参数,返回函数执行的结果。
  • bind()接受一组参数,返回函数体。需在bind()后加小括号才能执行函数。
  • 箭头函数的this绑定后无论使用apply()call()还是bind()都不可修改。

浅析this关键字

  • JavaScript中的函数存在定义上下文运行上下文,通过call()apply()bind()可以改变this的指向。
  • this总指向运行上下文

定义上下文

定义上下文更准确的名称应该叫词法作用域,它指函数的定义部分所形成的作用域。

function fun1 () {
	// 函数fun1的词法作用域
	// 函数定义
	// ....
}

运行上下文

函数在调用时会产生一个调用记录,其中包含函数在哪里被调用、传入函数的参数等信息。该记录也被称为运行上下文。一个函数的this总指向函数的运行上下文。当fun1fun2中调用时,fun1this指向fun2的定义上下文(词法作用域)。

function fun2 () {
    // fun1的this指向该作用域
    fun1();
    // 函数定义
    // ...
}

this的指向

this是运行上下文的一个属性,因此常说this指向函数的运行上下文
this是在函数调用时被绑定的,与函数的声明位置(即词法作用域)没有任何关系。


call()apply()

call()

call()方法的第一个参数为要指定的this对象,第二个参数及以后为函数运行所需的参数列表。

let obj = {
    a: 1
}

function fun1 (num1, num2) {
    console.log(this); // obj {a: 1}
    console.log(num1 + num2); // 3
    console.log(this.a); // 1
}

fun1.call(obj, 1, 2);

上述代码展示了call()的两种能力。一方面它改变了fun1调用时的this关键字,让其指向obj,并通过this关键字来访问obj中的属性。另一方面它将自己接受到的参数传入fun1

apply()

call()apply()本质上并无太大差别,唯一的区别在于call()接受的是参数列表,而apply()接受的是一个参数数组。

    // ··· 同上
    fun1.apply(obk, [1, 2]); // 效果和fun1.call(obj, 1, 2)相同

bind()

bind()创建一个新的函数, 当这个新函数被调用时this键值为其提供的值,其参数列表前几项值为创建时指定的参数序列。          ----- MDN

bind()的使用方法和call()十分类似,它的第一个参数是需要绑定的this对象,之后的参数为函数运行所需的参数列表。下面让我们来试验一下。

let obj = {
    a: 1
}

function fun1 (num1, num2) {
    console.log(this);
    console.log(num1 + num2);
    console.log(this.a);
}

fun1.bind(obj, 1, 2); // fun1 { ··· }

不同于apply()call()bind()返回的并不是fun1执行完毕的返回值,而是更改了this并初始化参数之后的fun1的函数定义。因此,要执行fun1,需在bind()后面再加一对括号:

fun1.bind(obj, 1, 2)(); // 改变this并传入参数后执行fun1

apply()call()bind()的比较

相同点

  • 均可改变函数this关键字的指向。

不同点

  • apply()接受一参数数组,返回函数执行的结果。
  • call()接受一组参数,返回函数执行的结果。
  • bind()接受一组参数,返回函数体。需在bind()后加小括号才能执行函数。

箭头函数的this绑定

ES6中新加入了箭头函数,它的绑定完全继承自调用它的作用域。绑定后无论使用apply()call()还是bind()都不可修改