原文链接: 原文链接
多年来,我已经看到许多关于JavaScript函数调用的困惑。特别是,许多人抱怨函数调用中的this
语义令人困惑。
在我看来,通过理解核心函数调用语句,然后在该原语之上查看以糖为例调用功能的所有其他方式,可以消除许多此类混淆。
核心原函数
首先,让我们看一下核心函数调用原语,call
函数的调用方法。调用方法相对简单。
- 从参数1到结尾创建参数列表(
argList
) - 第一个参数是
thisValue
- 将此函数设置为此thisValue并将argList作为其参数列表来调用该函数.
例如:
function hello(thing){
console.log(this + " says hello " + thing)
}
hello.call("Mandy", "world") // Mandy says hello world
我们调用hello
方法,他
它的this
被设置成Mandy
,还有一个参数world
。这是JavaScript函数调用的核心原语。您可以将所有其他函数调用视为对该核心原语的替代(“ 替代”是采用一种方便的语法,并以更基本的核心原语进行描述)。
简单函数调用
显然,一直使用call
调用函数会很烦人。 JavaScript
使我们可以使用语法(hello("world"))
直接调用函数。当我们这样做时,调用将会被替代。
function hello(thing){
console.log("Hello" + thing)
}
// this:
hello("world")
// desugars to:
hello.call(window, "world");
当使用严格模式(use strict)时,相当于
// this:
hello("world")
// desugars to:
hello.call(undefined, "world");
函数调用fn(...args)
相当于fn.call(window[ES5-strict:undefined], ...args)
。
请注意,对于内联声明的函数也是如此:(function() {})()
相当于(function() {}).call(window [ES5-strict: undefined)
。
成员方法
方法调用中另一种个非常常见的调用方式是:方法作为对象的成员调用(person.hello()
)。
var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this + " says hello " + thing);
}
}
// this:
person.hello("world")
// 相当于
// desugars to this:
person.hello.call(person, "world");
请注意,hello
方法如何以这种形式附加到对象并不重要。请记住,我们之前将hello
定义为独立函数。让我们看看如果我们动态地将hello
方法附加到对象上会发生什么:
function hello(thing) {
console.log(this + " says hello " + thing);
}
person = { name: "Brendan Eich" }
person.hello = hello;
person.hello("world") // still desugars to person.hello.call(person, "world")
// [object Object] says hello world
hello("world") // "[object DOMWindow]world"
// [object Window] says hello world
注意,该函数没有其" this
"的持久概念。它总是在调用时根据调用方调用的方式进行设置。
用 Function.prototype.bind
因为有时使用持久化this
值引用函数可能会很方便,所以人们一直使用简单的闭包技巧将函数转换为一个函数而不改变this
:
var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this.name + " says hello " + thing);
}
}
var boundHello = function(thing) {
return person.hello.call(person, thing);
}
boundHello("world"); // Brendan Eich says hello world
即使我们的boundHello("world")
调用仍然相当于boundHello.call(window,"world")
,我们还是转过来使用我们的原始call
方法将this
值更改回我们想要的值。
我们可以通过一些调整使此技巧通用:
var bind = function(func, thisValue) {
return function() {
return func.apply(thisValue, arguments);
}
}
var boundHello = bind(person.hello, person);
boundHello("world") // "Brendan Eich says hello world"
为了理解这一点,您只需要另外两个信息。 首先,arguments
是一个类似Array的对象,表示传递给函数的所有参数。 其次,apply
方法的工作方式与call
完全相同,不同之处在于它采用了一个类似于Array的对象,而不是一次列出一个参数。
我们的bind
方法返回一个新函数。 调用它时,我们的新函数将简单地调用传入的原始函数,并将原始值设置为this
。 它还传递参数。
因为这是一个有点普遍的习惯用法,所以ES5在所有实现此行为的Function
对象上引入了一种新的bind
方法:
var boundHello = person.hello.bind(person);
boundHello("world") // "Brendan Eich says hello world"
当您需要函数作为回调传递时,这是最有用的:
var person = {
name: "Alex Russell",
hello: function() { console.log(this.name + " says hello world"); }
}
$("#some-div").click(person.hello.bind(person));
// when the div is clicked, "Alex Russell says hello world" is printed
当然,这有些笨拙,并且TC39(负责ECMAScript下一版本的委员会)继续致力于开发一种更加优雅,仍然向后兼容的解决方案。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。