人只有献身于社会,才能找出那短暂而有风险的生命的意义。 ——<阿尔伯特·爱因斯坦>
作者:Ashish Lahoti
译者:cl9000
来源:https://codingnconcepts.com/javascript/call-vs-bind-vs-apply/
这是JavaScript面试中经常被问到的问题。这篇文章描述了函数原型方法call()、apply()和bind()及其语法、用法和实际示例。
Function.prototype是什么?
首先,我们需要了解这三个函数的调用、应用和绑定都是函数的原型。这是什么意思?
让我们打印函数的结构来理解它。
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: ƒ ()
|
您可以看到,apply、bind和call是Function的原型函数,这意味着您可以在定义的任何新函数上使用这三个函数。
何时使用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(); obj1.increment(); obj2.increment();
|
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); increment.call(obj2);
increment.apply(obj1); increment.apply(obj2);
var bindFn1 = increment.bind(obj1); var bindFn2 = increment.bind(obj2); bindFn1(); bindFn2();
|
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 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); Math.min.apply([1, 2, 3, 4, 5]);
|
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();
var boundClick = myButton.click.bind(myButton); boundClick();
|
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'); Obj.logY = logProp.bind(Obj, '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);
console.log(add5(10));
|
Output
15
Call vs Bind vs Apply
函数对象、函数objects、函数calls、call、apply和bind的比较:
|
函数执行时间 |
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); sumFn.call(numObj2, 1, 2, 3, 4);
sumFn.apply(numObj1, [1,2,3,4]); sumFn.apply(numObj2, [1,2,3,4]);
let sumBindFn1 = sumFn.bind(numObj1); let sumBindFn2 = sumFn.bind(numObj2); sumBindFn1(1, 2, 3, 4); sumBindFn2(1, 2, 3, 4);
|
output:
11
12
11
12
11
12
参考
关注【公众号】,了解更多。
关注【公众号】,了解更多。
关注【公众号】,了解更多。

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