JavaScript | apply、bind与call

在JavaScript中,有着3个方法能够改变函数内部的this指向,他们分别为:

  • call
  • bind
  • apply

下面就来依次介绍这几个函数的特性以及不同点

1.call方法

call方法调用一个对象的一个方法

  • 调用一个函数
  • 改变函数内部this的指向

语法:Function.call(thisArg, arg1, arg2)

  1. thisArg:在函数运行时指定的 this 值
  2. arg1,arg2:传递的其他参数
  3. 返回值就是调用的函数的返回值

常见用法:

call比较常见的用法是可以实现属性的继承,继承父类的构造函数中的属性。

简单理解就是在调用父类构造函数的同时,把父类构造函数中的this指向子构造函数中的this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function Pen(brand, manufacturer){
    this.brand = brand;
    this.manufacturer = manufacturer;
}

function MPen(brand, manufacturer, draw){
    // 通过call方法,将父类构造函数中的this指向子类构造函数中的this,从而实现属性的继承
    Pen.call(this, brand, manufacturer);
    this.draw = draw;
}

let mp = new MPen('晨光', '厂家', '荧光绘画');
console.log(mp);

2.apply方法

call方法一样apply方法也是调用一个对象的一个方法

  • 调用一个函数

  • 改变函数内部this的指向

  • 参数与call()有些不同

语法:Function.call(thisArg, args)

  1. thisArg:在函数运行时指定的 this 值
  2. args:函数调用时候需要的参数,必须以数组的形式传入才可以
  3. 返回值就是调用的函数的返回值

常见用法:

比如可以借助apply方法的特性借助数学对象(Math)来得到数组中最大的一项的值

1
2
3
4
let arr = [1, 3, 114, 514, 1919, 23, 8, 1, 0];
let max = Math.max.apply(Math, arr);
let min = Math.min.apply(Math, arr);
console.log(max, min);

还可以利用这种特性实现两个数组的合并

1
2
3
4
5
6
7
let arr = [1, 3, 114, 514, 1919, 23, 8, 1, 0];
let arr2 = [123, 654, 789, 987];

let res = Array.prototype.push.apply(arr, arr2);
// push 方法返回新数组的长度
console.log(res); // 13
console.log(arr); // Array(13) [1, 3, 114, 514, 1919, 23, 8, 1, 0, 123, 654, 789, 987]

总结:

通过以上两个案例可以看出,当目标函数不可以接受以数组形式传入参数的时候,就可以里apply方法的特性来解决这个问题

3.bind方法

apply以及call方法不同,bind方法不会直接调用函数,但是同样能够改变函数内部的this指向

语法:function.bind(thisArg, arg1, arg2)

  1. thisArg:在函数运行时指定的 this
  2. arg1,arg2:传递的其他参数
  3. 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

常见用法:

我们可以利用bind的特性给函数设置一个初始参数,当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。

 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
function list() {
    return Array.prototype.slice.call(arguments);
}

function add(arg1, arg2){
    return arg1 + arg2;
}

// 直接调用
let l1 = list(1, 2, 3); // 相当于[1, 2, 3].slice();
console.log(l1); // [1, 2, 3]
let a1 = add(1, 2);
console.log(a1); // 3

// 使用bind()创建一个新的函数,其拥有预设的参数列表 [114514]
let beastlist = list.bind(list, 114514);

// 使用bind()创建一个新的函数,其拥有一个预设的参数 为 1919810

let beastAdd = add.bind(add, 1919810);

// 直接调用
let l2 = beastlist();
console.log(l2); // [114514]

let l3 = beastlist(1, 2, 3);
console.log(l3); // [114514, 1, 2, 3]

let a2 = beastAdd(1);
console.log(a2); // 1919810 + 1 = 1919811

let a3 = beastAdd(1, 2);
console.log(a3); // 1919810 + 1 = 1919811 参数2无效

还可以利用这种特性来实现比如:有一个按钮,当点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮

这里就会设计到一个setTimeout函数中this的指向问题,正常情况下setTimeout函数中this是指向window对象的,所以就可以通过这个方式来重新给setTimeout中的this指向函数调用的btn对象

1
2
3
4
5
6
7
8
var btn = document.querySelector('button');

btn.onclick = function (){
  this.disabled = true; //这个this 指向的是btn这个按钮
  setTimeout(function() {
    this.disabled = false; //此时定时器函数里面的this指向的是btn
  }.bind(this), 1000);  // 这个this指向的是btn对象
}

总结

相同点:

  • 都会改变函数内部的this指向

不同点:

  • callapply会调用函数,并改变函数内部this指向
  • callapply的参数不同,call传递形式为arg1,arg2...的形式,apply的形式为[arg]
  • bind不会调用函数,可以改变函数内部this的指向

主要运用场景:

  • call常用于继承
  • apply常用于数组相关的方法,比如借助数学对象实现求数组的最大值最小值、合并数组等
  • bind改变定时器内部的this指向或者给函数设置初始参数