JavaScript中的call,bind和apply方法

Posted by cl9000 on March 30, 2020

人只有献身于社会,才能找出那短暂而有风险的生命的意义。 ——<阿尔伯特·爱因斯坦>

作者:Ashish Lahoti
译者:cl9000
来源:https://codingnconcepts.com/javascript/call-vs-bind-vs-apply/

这是JavaScript面试中经常被问到的问题。这篇文章描述了函数原型方法call()apply()bind()及其语法、用法和实际示例。

Function.prototype是什么?

首先,我们需要了解这三个函数的调用、应用和绑定都是函数的原型。这是什么意思?

让我们打印函数的结构来理解它。

1
console.dir(Function);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
▼ ƒ Function()
arguments: (...)
caller: (...)
length: 1
name: "Function"
▼ prototype: ƒ ()
arguments: (...)
caller: (...)
length: 0
name: ""
➤ constructor: ƒ Function()
➤ apply: ƒ apply()
➤ bind: ƒ bind()
➤ call: ƒ call()
➤ toString: ƒ toString()
➤ Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]()
➤ get arguments: ƒ ()
➤ set arguments: ƒ ()
➤ get caller: ƒ ()
➤ set caller: ƒ ()

您可以看到,applybindcallFunction的原型函数,这意味着您可以在定义的任何新函数上使用这三个函数。

何时使用call()、bind()和apply()

当您定义一个新函数并从代码中的某个地方调用它时,它将在上下文中执行。这个上下文称为 this,它指向一个对象。这个对象根据调用函数的方式而变化。

1
2
3
4
5
6
7
8
9
10
11
var a = 1;
var increment = function(){
return console.log(this.a + 1);
}

var obj1 = { a: 2, increment: increment };
var obj2 = { a: 3, increment: increment };

increment(); //this = window
obj1.increment(); //this = obj1
obj2.increment(); //this = obj2

Output
2
3
4

在上面的代码片段中,我们在三个不同的上下文中调用了同一个increment()函数,这改变了this引用,并产生了三个不同的输出。

  • 第一个increment()函数在全局上下文中被调用,并绑定到全局对象。如果您在浏览器中工作,window是全局对象。
  • 接下来的两个increment()函数在隐式上下文中被调用,这个函数绑定到调用函数的对象。这被称为隐式上下文,因为函数与对象紧密耦合(在object内部定义),当从obj.ƒn()这样的对象调用函数时,这个引用在编译时绑定到object

有时,我们可能希望在显式上下文中调用函数,这样我们就可以在运行时调用函数时控制该引用。这就是call()apply()bind()发挥作用的地方。让我们使用call()apply()bind()方法来调用相同的increment()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var increment = function(){
return console.log(this.a + 1);
}

var obj1 = { a: 4 };
var obj2 = { a: 5 };

increment.call(obj1); //this = obj1
increment.call(obj2); //this = obj2

increment.apply(obj1); //this = obj1
increment.apply(obj2); //this = obj2

var bindFn1 = increment.bind(obj1);
var bindFn2 = increment.bind(obj2);
bindFn1(); //this = obj1
bindFn2(); //this = obj2

Output
5
6
5
6
5
6

从上面的代码片段注意几点:-

  • 所有这三个call()apply()bind()都是Function的原型,所以你可以在任何函数increment.call()increment.apply()increment.bind()上使用它们。
  • 这三个call()apply()bind()在运行时提供不同的对象上下文(obj1、obj2),并产生不同的输出。
  • call()apply()立即执行一个函数,bind()则返回一个可以稍后执行的绑定函数。

现在我们已经理解了这些原型函数的基本用法,让我们通过示例来看看它们的语法和实际用法。

call()

语法

1
functionName.call(thisArg, arg1, arg2, ...)

当使用call()调用函数时,

  • this指的是thisArg
  • 逗号分隔的参数arg1, arg2…是函数的参数

记住:“call()参数用逗号分隔”。

实际使用情况

1. 借用其他对象的功能

Javascript中,每个对象都可以有原型函数,这些函数实际上只在该对象上执行。使用call()方法,您可以借用这些对象的功能,并在不同的对象上执行它。

让我们来看看数组原型函数

1
console.dir(Array);
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
Output

▼ ƒ Array()
arguments: (...)
caller: (...)
length: 1
name: "Array"
▼ prototype: ƒ ()
length: 0
➤ constructor: ƒ Array()
concat: ƒ concat()
➤ copyWithin: ƒ copyWithin()
➤ fill: ƒ fill()
find: ƒ find()
➤ findIndex: ƒ findIndex()
➤ lastIndexOf: ƒ lastIndexOf()
➤ pop: ƒ pop()
➤ push: ƒ push()
reverse: ƒ reverse()
➤ shift: ƒ shift()
➤ unshift: ƒ unshift()
➤ slice: ƒ slice()
sort: ƒ sort()
➤ splice: ƒ splice()
➤ includes: ƒ includes()
➤ indexOf: ƒ indexOf()
➤ join: ƒ join()
➤ keys: ƒ keys()
➤ entries: ƒ entries()
➤ values: ƒ values()
➤ forEach: ƒ forEach()
➤ filter: ƒ filter()
➤ flat: ƒ flat()
➤ flatMap: ƒ flatMap()
➤ map: ƒ map()
➤ every: ƒ every()
➤ some: ƒ some()
➤ reduce: ƒ reduce()
➤ reduceRight: ƒ reduceRight()
➤ toLocaleString: ƒ toLocaleString()
➤ toString: ƒ toString()

让我们借用数组原型方法的功能call()函数:-

1
2
Array.prototype.concat.call([1,2,3], [4,5]);
Array.prototype.join.call([1,2,3,4,5], ":")

Output
[1, 2, 3, 4, 5]
1:2:3:4:5

同样借用car对象的start功能,将其用于aircraft

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var car = {
name: 'car',
start: function() {
console.log('Start the ' + this.name);
},
speedup: function() {
console.log('Speed up the ' + this.name)
},
stop: function() {
console.log('Stop the ' + this.name);
}
};

var aircraft = {
name: 'aircraft',
fly: function(){
console.log('Fly');
}
};

car.start.call(aircraft);

2. 链构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Box(height, width) {
this.height = height;
this.width = width;
}

function Widget(height, width, color) {
Box.call(this, height, width);
this.color = color;
}

function Dialog(height, width, color, title) {
Widget.call(this, height, width, color);
this.title = title;
}

var dialog = new Dialog('red', 100, 200, 'Title');

在上面的代码片段中,我们可以看到如何在当前this上下文中通过调用父构造函数来链构造函数.

apply()

语法

1
functionName.apply(thisArg, [arg1, arg2, ...])

当使用apply()方法调用函数时,

  • this指的是thisArg
  • 第二个参数是一个值数组[arg1, arg2,…],是函数的参数

记住: apply()接受数组形式的参数

实际用法

apply()函数的实际用法与call()函数相同。它们之间唯一的区别是,apply()接受args作为数组,而call()接受args作为逗号分隔的值。

可以使用apply()函数将参数作为数组传递,调用任何接受参数作为逗号分隔值的函数。

1
2
Math.min(1, 2, 3, 4, 5);           //args as comma separated
Math.min.apply([1, 2, 3, 4, 5]); //args as an array

bind()

语法

1
functionName.bind(thisArg)

当使用bind()方法调用函数时,

  • this指的是thisArg
  • 返回可以稍后调用的新的绑定函数

记住: bind()不会立即调用函数。它返回一个新的可以稍后调用的绑定函数。

实际用法

1. 上下文界限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Button = function(content) { 
this.content = content;
};
Button.prototype.click = function() {
console.log(this.content + ' clicked');
};

var myButton = new Button('OK');
myButton.click();

var looseClick = myButton.click;
looseClick(); // not bound, 'this' is not myButton - it is the global object

var boundClick = myButton.click.bind(myButton);
boundClick(); // bound, 'this' is myButton

Output
OK clicked
undefined clicked
OK clicked

2. 函数与参数绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var logProp = function(prop) {
console.log(this[prop]);
};

var Obj = {
x : 5,
y : 10
};

Obj.log = logProp.bind(Obj);
Obj.logX = logProp.bind(Obj, 'x'); //binding with prop x
Obj.logY = logProp.bind(Obj, 'y'); //binding with prop y

Obj.log('x');
Obj.logX();

Obj.log('y');
Obj.logY();

Output
5
5
10
10

另外一个例子

1
2
3
4
5
6
7
var sum = function(a, b) {
return a + b;
};

var add5 = sum.bind(null, 5);
//add5 is binding function with a = 5
console.log(add5(10)); //b =10

Output
15

Call vs Bind vs Apply

函数对象、函数objects、函数callscallapplybind的比较:

函数执行时间 this 绑定时间
function object ƒ future future
function call ƒ() now now
ƒ.call() now now
ƒ.apply() now now
ƒ.bind() future now

让我们看一下最后一个例子,并一起使用call()apply()bind()方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let numObj1 = {num: 1};
let numObj2 = {num: 2};

let sumFn = function(...args){
console.log(this.num + args.reduce((a,b)=> a+b, 0));
}

sumFn.call(numObj1, 1, 2, 3, 4); //this = numObj1
sumFn.call(numObj2, 1, 2, 3, 4); //this = numObj2

sumFn.apply(numObj1, [1,2,3,4]); //this = numObj1
sumFn.apply(numObj2, [1,2,3,4]); //this = numObj2

let sumBindFn1 = sumFn.bind(numObj1); // return Fn
let sumBindFn2 = sumFn.bind(numObj2); // return Fn
sumBindFn1(1, 2, 3, 4); //this = numObj1
sumBindFn2(1, 2, 3, 4); //this = numObj2

output:
11
12
11
12
11
12

参考

关注【公众号】,了解更多。
关注【公众号】,了解更多。
关注【公众号】,了解更多。



支付宝打赏 微信打赏

赞赏一下 坚持原创技术分享,您的支持将鼓励我继续创作!