日期:2014-05-16  浏览次数:20356 次

理解javascript函数调用和“this”

2011.8.11

多年以来,我看到大量关于javascript函数调用的困惑。尤其,许多人抱怨函数调用中“this”的语意是混乱的。

在我看来,大量这样的混乱可以通过理解核心函数调用原语被清理,然后再看所有其他在原语之上进行包装的调用函数的方法。实际上,这正好是ECMAScript规格对这个问题的考虑。在某些领域,这个是一个规格的简化,但基本思想是一样的。

核心原语

首先,我们来看核心函数调用原语,一个函数的调用方法[1]。这个调用方法是相对直线向前的(The call method is relatively straightforward.)。

1.     构造参数列表(argList)从参数1到最后

2.     第一个参数是thisValue

3.     把this 赋值给thisValue 并用argList 作为参数列表调用函数

例如:

function hello(thing) {
  console.log(this + " says hello " + thing);
}
 
hello.call("Yehuda", "world") //=> Yehuda says hello world

正如你看到的,我们通过把this赋值给 "Yehuda"和一个单一参数来调用hello 方法。这就是javascript函数调用核心原语。你能想象所有其他的函数调用都是对这个原语包装。(包装是使用一个便利的语法和按照更基本的核心原语描述它)

[1] In the ES5 spec, the call method isdescribed in terms of another, more low level primitive, but it’s a very thinwrapper on top of that primitive, so I’m simplifying a bit here. See the end ofthis post for more information.

简单函数调用

很明显,任何时候使用call 调用函数都是相当的烦人的。Javascript允许我们使用括弧直接调用函数(hello("world"))。我们这样做的时候,调用包装为:

function hello(thing) {
  console.log("Hello " + thing);
}
 
// this:
hello("world")
 
// desugars to:
hello.call(window, "world");

这个行为在ECMAScript中只有当使用严格模式时改变了:

// 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)等同。

[2]实际上,我说了点谎。ECMAScript 5规格说一般(大多情况)传递的是undefined ,但被调用的函数在非严格模式时应该改变它的thisValue 为全局对象。这允许严格模式调用者避免破坏现存的非严格模式库。

成员函数

下一个非常常见的方法是调用作为对象的成员方法(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(pe