重温javascript的this指向

前几天,听同事讨论起javascript的this指向问题,趁此机会复习一下这个知识点。

翻了下犀牛书,在8.2.2节(第6版p171)提到了this的指向问题:

需要注意的是,this是一个关键字,不是变量,也不是属性名。JavaScript的语法不允许给this赋值。

和变量不同,关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。很多人误以为调用嵌套函数时this会指向调用外层函数的上下文。如果你想访问这个外部函数的this值,需要将this的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。

(以上是LD亲手敲的奥,赞一个- -)
对着上面这段话提到的场景解释一下:
嵌套的函数不会从调用它的函数中继承this

1
2
3
4
5
6
7
8
var Outer = function () {
this.foo = 'outer value';
function fnInner(){
console.log(this.foo)//如果继承了外层,那么会打印outer value
}
fnInner();
};
new Outer();

答案是undefined,也就是说不会继承外层this,这个应该是最好理解的。那么如何访问外层this下的属性呢?很简单,且看:

1
2
3
4
5
6
7
8
9
var Outer = function () {
this.foo = 'outer value';
var that = this;//将this赋值给一个局部变量
function fnInner(){
console.log(that.foo);//访问that即是外层函数的this
}
fnInner();
};
new Outer();

说起that,关于这个局部变量的命名有三大党派:

  1. that
  2. self
  3. me
    你更喜欢哪一派呢?

再看下一个:

如果嵌套函数作为方法调用,其this的值指向调用它的对象。

1
2
3
4
5
6
7
8
var Outer = function () {
this.foo = 'outer value';
this.fnInner = function (){
console.log('nested func: ' + this.foo);//this即为刚刚创建的Outer实例对象
};
this.fnInner();//方法调用
};
new Outer();

理解这个应该也不难。最后一个场景:

如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。

1
2
3
4
5
6
7
8
var Outer = function () {//非严格模式
this.foo = 'outer value';
function fnInner(){
console.log(this);//此this为全局对象,在浏览器中就是window,在node中是一个global全局对象
}
fnInner();//函数调用
};
new Outer();

浏览器:

node:

再看:

1
2
3
4
5
6
7
8
9
var Outer = function () {//严格模式
'use strict';
this.foo = 'outer value';
function fnInner(){
console.log(this);
}
fnInner();
};
new Outer();

浏览器:

node:

嗯,大致有数了。

PS:严格模式是ES5推出的标准,开启这个模式(函数首行添加’use strict’;),可以防止很多由于javascript本身的设计缺陷导致的隐形bug(比如这里的this指向全局对象,可能导致全局变量污染),帮助我们及早发现问题。
 

上述只是犀牛书中提到的关于this的一部分相关内容,其实还远远不够。

在我理解来看,大致上分为以下四大场景:

一、函数仅作为函数被调用

即直接在函数名后加圆括号调用,或在高阶函数里调用函数类型的形参,甚至使用匿名立即执行函数,它们的this都指向全局对象。

好吧,好像又多出了几个小场景囧。不过无论全局函数还是局部函数,这都成立。上demo:

二、传给setTimeout、setInterval等全局函数的回调

这些回调内的this也是全局对象,只不过浏览器和node有些区别(我也刚发现- -)。

三、函数作为对象方法被调用

这种场景,就属于面向对象编程了,this的指向很好分辨,当前方法的调用者即为this的指向。在这个例子里,this指向刚刚创建的Foo的实例对象,因为是通过这个实例对象“点”someMethod()的,也就是说谁“点”了someMethod(),谁就是this。顺便提一下,这样调用是等价的:

四、函数与call,apply,bind结合使用

这个场景是最复杂的,也是几乎所有框架都会使用的技巧。

这三者都属于函数的原型方法,都可以改变函数(方法)的this指向(或者叫做改变执行上下文)。

call与apply更接近,他们会立即执行目标函数;而bind是ES5种新增的标准,用于预处理函数,事先绑定this指向。一图胜千言:

另外,我自己也好奇,如果调用boundAdd时,也用call会怎么样呢?如果你也一样好奇的话,就自己动手敲一敲代码吧,我也是希望加深你的印象~

另外提一下,call与apply的适用场景。个人认为,apply是比call强大一些的,举个复杂一点的栗子:

如果你能完全理解这个栗子的话,说明你已经对call和apply理解透彻了(其实还包含了闭包的知识点)。

至于bind的话,可以在场景二中这样使用:

总结

关于this的话题,其实还有不少,尤其在面向对象编程的领域,更加重要,需要更加深刻的理解,想要在javascript上有所造诣,这块一定要啃下。

PPS:这是换了Pro之后写的第一篇博客,这屏幕太赞了,回去再看看Air,瞬间变“渣屏”(不过比起一般的windows兼容机还是好得多的)。我用了两年零八个月Air退居二线了,希望新Pro能让我告别卡机死机的日子~科科。