this关键字是javascript中看起来最复杂的机制,也是最让人困惑的机制,因为当我们初学者在接触 this时,往往习惯性的采用经验总结的方法,这固然是个好办法,但是这个大多数人都惯用的思维方式,在面对this时却让人捉摸不透,好不容易总结了规律,在其它地方却感觉this机制又并不如自己所想的那样,就像是魔法一样,让人不得不思考this内部实现到底是个什么样的机制,在一些学习技巧失效的情况下,正确的清晰认知其实现机制往往是最切实可行的方法。
当然,实际上,JavaScript 中 this 的机制并没有那么先进,但是开发者往往会把理解过程复杂化,
毫无疑问,在缺乏清晰认识的情况下,this 对你来说完全就是一种魔法。
本文大部分理解均来自于《你不知道的javascript》一书
知识网络图
1.为什么要用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
```
如果不了解this,这段代码解读起来有些费劲,请暂时抛开这些问题,且听我往下解释。
这段代码可以在不同的上下文对象(me和you),中重复使用函数speak()和identify(),不用针对每个对象写一个函数。
然而,如果不使用this,就需要给函数显示的传入一个对象,可想而知,函数要执行并输出结果,必然需要一个对象
提供依据。(上面的代码中call()已经对this进行了操作)。
```
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显得更加优雅,this提供了更加强大的方法来实现隐式传
输,因此可以将API设计地更加简洁而易于复用。
随着使用的模式越来越复杂,显示传递上下文会让代码变得越来越混乱,使用this则不会这样。
2.对this的常见误解
①.指向自身
人们很容易把函数理解为指向自身,当然这个推断从this的英语语法上是说得通的,但实际上并不是这样。
我们来看看代码。
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?
显然结果输出了四次,但是当我们调用count的值时,count仍然时0,说明this并不是指向自身。负责的开发者一
定会问“如果我增加的count属性和预期的不一样,那我增加的是哪个count ?”实际上,如果他深入探索的话,就
会发现这段代码在无意中创建了一个全局变量count,它的值为NaN。当然如果他发现了这个奇怪的结果,那一定会
接着问:“为什么它是全局的,为什么它的值是 NaN 而不是其他更合适的值?” 这个问题留给后面再深入思考。
接下来回到正题,我们怎样才可以让this就是我们想要的对象呢??
我们对原来的代码稍加修改。
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 ).call(foo,i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count );
我们利用call将this强制绑定在了foo上,这样this.count与foo.count就联系起来了。
②.指向它的作用域
第二种理解是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.bar()引用bar函数,这显然是不可行的,详
细的内部原理我们往后再细想,最主要的是在了解完词法作用域之后,我们知道,要在foo函数里
引用外部函数,直接使用词法引用标识符bar(),就可以了。
他还试图用this联通foo和bar的词法作用域,从而使bar能访问foo里的a,事实告诉我们是不可能
的,当下一节了解了this到底是什么的时候,相信你会明白为什么。
3.this到底是什么?
排除了一些错误理解之后,我们来看看 this 到底是一种什么样的机制。 之前我们说过 this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包
含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的
其中一个属性,会在函数执行的过程中用到。
也就是说,我们得学习如何寻找函数的调用位置,才能判断函数在执行过程中会如何绑定 this。
4.小结
this是非常重要的,一直重复的持续困惑并不能帮助我们理解它。
this既不指向它本身,也不指向作用域。
this是在调用时绑定的,this取决于调用的方式以及位置。