每一个运行上下文除了建立一个作用域链外,还提供一个名为this
的关键字。它的普遍用法是,this
作为一个独特的功能,为邻里们提供一个可访问到它的途径。但总是依赖于这个行为并不可靠:取决于我们如何进入一个特定邻居的具体情况,this
表示的完全可能是其他东西。事实上,我们如何进去邻居家本身,通常恰恰就是this
所指。有四种情形值得特别注意:
-
呼叫对象的方法
在经典的面向对象编程中,我们需要识别和引用当前对象。
this
极好地扮演了这个角色,为我们的对象提供了自我查找的能力,并指向它们本身的属性。<script type="text/javascript"> var deep_thought = { the_answer: 42, ask_question: function () { return this.the_answer; } }; var the_meaning = deep_thought.ask_question();</script>
这个例子建立了一个名为
deep_thought
的对象,设置其属性the_answer
为42,并创建了一个名为ask_question
的方法(method)。当deep_thought.ask_question()
执行时, JavaScript为函数的呼叫建立了一个运行上下文,通过”.
“运算符把this
指向被引用的对象,在此是deep_thought
这个对象。之后这个方法就可以通过this
在镜子中找到它自身的属性,返回保存在this.the_answer
中的值:42。 -
构造函数
类似地,当定义一个作为构造器的使用
new
关键字的函数时,this
可以用来引用刚创建的对象。让我们重写一个能反映这个情形的例子:<script type="text/javascript"> function BigComputer(answer) { this.the_answer = answer; this.ask_question = function () { return this.the_answer; } } var deep_thought = new BigComputer(42); var the_meaning = deep_thought.ask_question();</script>
我们编写一个函数来创建
BigComputer
对象,而不是直白地创建deep_thought
对象,并通过new
关键字实例化deep_thought
为一个实例变量。当new BigComputer()
被执行,后台透明地创建了一个崭新的对象。呼叫BigComputer
后,它的this
关键字被设置为指向新对象的引用。这个函数可以在this
上设置属性和方法,最终它会在BigComputer
执行后透明地返回。尽管如此,需要注意的是,那个
deep_thought.the_question()
依然可以像从前一样执行。那这里发生了什么事?为何this
在the_question
内与BigComputer内会有所不同?简单地说,我们是通过new
进入BigComputer
的,所以this
表示“新(new)的对象”。在另一方面,我们通过deep_thought
进入the_question
,所以当我们执行该方法时,this
表示 “deep_thought
所引用的对象”。this
并不像其他的变量一样从作用域链中读取,而是在上下文的基础上,在上下文中重置。 -
函数呼叫
假如没有任何相关对象的奇幻东西,我们只是呼叫一个普通的、常见的函数,在这种情形下
this
表示的又是什么呢?<script type="text/javascript"> function test_this() { return this; } var i_wonder_what_this_is = test_this();</script>
在这样的场合,我们并不通过
new
来提供上下文,也不会以某种对象形式在背后偷偷提供上下文。在此,this
默认下尽可能引用最全局的东西:对于网页来说,这就是window
对象。 -
事件处理函数
比普通函数的呼叫更复杂的状况,先假设我们使用函数去处理的是一个
onclick
事件。当事件触发我们的函数运行,此处的this
表示的是什么呢?不凑巧,这个问题不会有简单的答案。如果我们写的是行内(inline)事件处理函数,
this
引用的是全局window
对象:<script type="text/javascript"> function click_handler() { alert(this); // 弹出 window 对象 }</script> ...<button id='thebutton' οnclick='click_handler()'>Click me!</button>
但是,如果我们通过JavaScript来添加事件处理函数,
this
引用的是生成该事件的DOM元素。(注意:此处的事件处理非常简洁和易于阅读,但其他的就别有洞天了。请使用真正的addEvent函数取而代之):<script type="text/javascript"> function click_handler() { alert(this); // 弹出按钮的DOM节点 } function addhandler() { document.getElementById('thebutton').onclick = click_handler; } window.onload = addhandler;</script> ...<button id='thebutton'>Click me!</button>
复杂情况
让我们来短暂地运行一下这个最后的例子。我们需要询问deep_thought
一个问题,如果不是直接运行click_handler
而是通过点击按钮的话,那会发生什么事情?解决此问题的代码貌似十分直接,我们可能会这样做:
<script type="text/javascript"> function BigComputer(answer) { this.the_answer = answer; this.ask_question = function () { alert(this.the_answer); } } function addhandler() { var deep_thought = new BigComputer(42), the_button = document.getElementById('thebutton'); the_button.onclick = deep_thought.ask_question; } window.onload = addhandler;</script>
很完美吧?想象一下,我们点击按钮,deep_thought.ask_question
被执行,我们也得到了“42”。但是为什么浏览器却给我们一个undefined
? 我们错在何处?
其实问题显而易见:我们给ask_question
传递一个引用,它作为一个事件处理函数来执行,与作为对象方法来运行的上下文并不一样。简而言之,ask_question
中的 this
关键字指向了产生事件的DOM元素,而不是在BigComputer
的对象中。DOM元素并不存在一个the_answer
属性,所以我们得到的是 undefined
而不是”42″. setTimeout
也有类似的行为,它在延迟函数执行的同时跑到了一个全局的上下文中去了。
这个问题会在程序的所有角落时不时突然冒出,如果不细致地追踪程序的每一个角落的话,还是一个非常难以排错的问题,尤其在你的对象有跟DOM元素或者window
对象同名属性的时候。