JavaScript 里的 this 到底指得是什么?很多人都会告诉你 this 指的是当前对象。这样理解对么?在大多数情况下确实没错。比如我们经常会在网页上写这样的 JavaScript:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="submit" value="提交" onclick="this.value='正在提交数据'"Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
/>
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
这里的this显然指的是当前对象,即这个提交按钮。通常,我们使用this的情况都与此类似。但是有什么情况不是这样的呢?Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
大家看看这个例子:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
var foo = function() {Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
    console.log(
this
);Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
foo();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
new foo();
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
比较一下 foo() 和 new foo() 的运行结果,你会发现,前者 this 指向的并非 foo 本身,而是当前页面的window对象,而后者才真正的指向foo。这是为什么呢?Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
其实这牵涉到JavaScript的一条重要特性,就是所谓的“闭包”。闭包这个概念说复杂也不复杂,但也不是简单到能用一两句话说清。偶会在以后的文章中深入探讨这个Javascript 最重要的特性。现在,我要告诉大家的是,因为闭包的存在,JavaScript中的作用域变得相当重要。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
所谓的作用域,简单的说,就是创建一个函数时在什么环境下创建的。而this变量的值,如果没有指定的话,就是函数当前的作用域。 Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
在前面的例子里,foo() 函数是在全局作用域(这里就是window对象),所以this的值是当前的window对象。而通过 new foo() 这样的形式,其实是创建了一个foo()的副本,并在这个副本上进行的操作,所以这里的this就是foo()的这个副本。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
这样讲可能有点抽象,大家来看个实际的例子:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" id="aButton" value="demo"  />Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<script type="text/javascript">Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function demo() {Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
   
this.value =
Math.random();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
</script>
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
如果直接调用demo() 函数,程序就会报错,因为demo函数是在window对象中定义的,所以demo的拥有者(作用域)是window,demo的this也是window。而window是没有value属性的,所以就报错了。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM

Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
如果我们通过创建副本的方式,将这个函数的副本添加到一个HTML元素,那么他的所有者就成了这个元素,this也指代了这个元素:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
document.getElementById("aButton").onclick = demo;Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
这样就将aButton的onlick属性设置为demo()的一个副本,this也指向了aButton。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM

Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
你甚至可以为多个不同的HTML元素创建不同的函数副本。每个副本的拥有者都是相对应的HTML元素,各自的this也都指向他们的拥有者,不会造成混乱。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM

Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
但是,如果你这样定义某个元素的onlick事件:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" id="aButton" value="demo" onclick="demo()" />Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
点击这个button之后,你会发现,程序又会报错了——this又指向了window!Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
其实,这种方法并没有为程序创建一个函数,而只是引用了这个函数。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
具体看一下区别吧。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
使用创建函数副本的方法:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" id="aButton" value="demo"Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
/>
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<script type="text/javascript">Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
var button = document.getElementById("aButton");Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function demo()
{Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
   
this.value =
Math.random();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
button.onclick
= demo;Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
alert(button.onclick);Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
</script>
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
得到的输出是:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function demo() {Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
   
this.value =
Math.random();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
使用函数引用的方法:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" id="aButton" value="demo" onclick="demo()"Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
/>
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<script type="text/javascript">Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
var button = document.getElementById("aButton");Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function demo()
{Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
   
this.value =
Math.random();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
alert(button.onclick);Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
</script>
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
得到的输出是:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function onclick() {Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
    demo();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
这样就能看出区别了吧。函数引用的方式中,onclick事件只是直接调用demo()函数,而demo()函数的作用域仍旧是window对象,所以this仍然指向window。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM

Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
这样就又引出了一个问题:既然函数副本这么好用,为什么还需要函数引用的方法呢?答案是性能。每新建一个函数的副本,程序就会为这个函数副本分配一定的内存。而实际应用中,大多数函数并不一定会被调用,于是这部分内存就被白白浪费了。而使用函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针,这样效率就高很多。程序员么,节约为主,恩 Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
所以我们来看一个更好的解决方案:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<script type="text/javascript">Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function demo(obj) {Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
    obj.value
=
Math.random();Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
</script>Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" value="demo" onclick="demo(this)" />Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" value="demo" onclick="demo(this)" />Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
<input type="button" value="demo" onclick="demo(this)" />
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
这样,效率和需求就都能兼顾了。Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
最后再多讲一句:在前面的文章里,我特别强调了“如果没有指定this的话”。其实this是可以指定的。Function对象有两个方法:call()和apply()。这两个方法都支持指定一个函数中的this。有兴趣的话您可以去查一下Javascript的手册,看看这两个函数都是干什么用的。而我们经常用的 new foo() 可以用以下这段伪代码来描述:Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
function new (somefunction) {Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
   
var args = [].slice.call(arguments, 1
);Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
    somefunction.prototype.constructor
=
somefunction;Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
    somefunction.apply(somefunction.prototype, args);Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
   
return
somefunction.prototype;Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM
}
Ô ÅÖ6!=èÄÉwww.netcsharp.cn1zDI!”ïM