为什么需要 this
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "Hello, I'm " + identify.call( this );
console.log( greeting );
}
var me = {
name: "Kyle"
};
var you = {
name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READER
这段代码可以在不同的上下文对象(me 和 you)中重复使用函数 identify()
和 speak()
,不用针对每个对象编写不同版本的函数。
如果不使用 this
,那就需要给 identify()
和 speak()
显式传入一个上下文对象。
function identify(context) {
return context.name.toUpperCase();
}
function speak(context) {
var greeting = "Hello, I'm " + identify( context );
console.log( greeting );
}
identify( you ); // READER
speak( me ); //hello, 我是 KYLE
然而,this
提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。
随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this
则不会这样。当我们介绍对象和原型时,你就会明白函数可以自动引用合适的上下文对象有多重要。
误解
指向自身
我们想要记录一下函数 foo 被调用的次数,思考一下下面的代码:
function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 0 -- WTF?
console.log
语句产生了 4 条输出,证明 foo(..)
确实被调用了 4 次,但是 foo.count
仍然是 0。显然从字面意思来理解 this
是错误的。
执行 foo.count = 0
时,的确向函数对象 foo
添加了一个属性 count
。但是函数内部代码 this.count
中的 this
并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相同,困惑随之产生。
它的作用域
第二种常见的误解是,this
指向函数的作用域。这个问题有点复杂,因为在某种情况下它是正确的,但是在其他情况下它却是错误的。
需要明确的是,this
在任何情况下都不指向函数的词法作用域。在 JavaScript 内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript 代码访问,它存在于 JavaScript 引擎内部。
思考一下下面的代码,它试图(但是没有成功)跨越边界,使用 this
来隐式引用函数的词法作用域:
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined
这段代码中的错误不止一个。虽然这段代码看起来好像是我们故意写出来的例子,但是实际上它出自一个公共社区中互助论坛的精华代码。这段代码非常完美(同时也令人伤感)地展示了 this
多么容易误导人。
首先,这段代码试图通过 this.bar()
来引用 bar()
函数。这是绝对不可能成功的,我们之后会解释原因。调用 bar()
最自然的方法是省略前面的 this
,直接使用词法引用标识符。
此外,编写这段代码的开发者还试图使用 this
联通 foo()
和 bar()
的词法作用域,从而让 bar()
可以访问 foo()
作用域里的变量 a
。这是不可能实现的,你不能使用 this
来引用一个词法作用域内部的东西。
每当你想要把 this
和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。
this 到底是什么
排除了一些错误理解之后,我们来看看 this
到底是一种什么样的机制。
之前我们说过 this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this
就是记录的其中一个属性,会在函数执行的过程中用到。
在下一章我们会学习如何寻找函数的调用位置,从而判断函数在执行过程中会如何绑定 this
。