this
是 JS 这门语言的魅力之一——灵活方便又难以捉摸,即使是有经验的程序员,如果不仔细也有可能搞错,关于this
的用法也成为许多公司的经典面试题。
如果你写过 Java ,你可能接触过this
——一般指向当前对象,实际上,这时候this
的含义已经确定了,因为Java属于编译期绑定,而JS属于运行期绑定
,所以导致this的含义在运行过程中可能有多种变化。
进一步说,this和它声明环境无关,而完全取决于他的执行环境。务必牢记这句话。
//读以下代码之前,必须先阅读《哈利·波特》原著。(笑)
var name = '罗恩';
var aaa = {
name: '哈利',
say: function () {
console.log(this.name);
}
}
var bbb = {
name: '赫敏',
say: aaa.say
}
var ccc = aaa.say;
aaa.say(); //哈利
bbb.say(); //赫敏
ccc(); //罗恩
我们看第一行,aaa.say()
调用的是aaa
对象本身的say()
方法,此时this
指代的是aaa对象本身,所以此时输出当然就是aaa
对象的name
属性值。
第二行,bbb.say();
输出赫敏
一定和JS新手们的常识不相符,其实只要牢记“this取决于执行环境”就能想明白。bbb对象是怎么声明自身的say方法的呢?它只是把aaa对象的say方法引用过来,注意,引用的是一个方法而非一个对象,而aaa.say存储的是一个匿名函数,所以这种写法和以下代码并没有什么区别。
var bbb = {
name: '赫敏',
say: function () {
console.log(this.name);
}
}
第三行的ccc()是在最外层执行,也就是在全局对象window
下。所以ccc()
执行的时候this
指代的就是window
对象。而在window对象下声明了name属性,就相当于window.name = '罗恩'
,输出的当然就是罗恩
。
当然,也有特殊情况,那就是 setTimeout 和 setInterval 。
我把开头的aaa对象的声明改成:
var aaa = {
name: '哈利',
getName: function () {
setTimeout(function(){
console.log(this.name);
},100)
}
}
仅仅是在console.log(this.name)
外面套了一个setTimeout
,猜猜原来三行的输出会是什么?
答案:3个罗恩
。
也就是说,三次this
,指代的都是window
对象。
关于为什么会这样,我这里暂时不详细展开,因为涉及到JS异步回调的知识,如果你仅仅想快速熟悉this的用法,那么只要记住这个特殊情况即可。这个知识点曾经是阿里还是小米的面试题。
显然,三个罗恩不是我想要的,毕竟韦斯莱夫人的孩子已经够多了。那么我们只需稍微改写一下这个方法:
getName: function () {
//在setTimeout外存储this指代的对象
var that = this;
setTimeout(function(){
//this.name变成了that.name
console.log(that.name);
},100)
}
输出就又正常了。
显然,that
并不是一个关键字,只是一个大家解决这种情况时约定俗成的名字。如果你愿意,也可以叫thatGuy。当然,考虑到可能会有其他人维护你的代码,还是用that
比较好。
之所以写这篇文章,是为了我下一篇文章做铺垫:
《巧用JavaScript中apply()和call()》
敬请期待~~