本文深入解析 JavaScript 中 this 关键字在事件处理中的行为差异。通过对比函数赋值(拷贝)与行内调用(指向)两种场景,阐明 this 指向 HTML 元素或全局 window 对象的原理。文章结合 PPK 的经典观点,详细说明了传统事件注册、行内注册及微软 attachEvent 模型下的 this 指向规则,帮助开发者避免常见错误。
首先介绍在
event handling场景中的应用,随后探讨 this 的其他用法。
自身指向
我们先分析函数 doSomething() 内部的 this 究竟指向何处?
function doSomething() {
this.style.color = '#cc0000';
}
JavaScript 中的 this 始终指向运行该函数的“自身”。这是一种指向函数对象的方式。若在页面中直接定义 doSomething() 函数,其“自身”即指页面,也就是 JavaScript 的全局对象 window。而 onclick 属性则属于 HTML 元素本身。
这种“所有权”关系是 JavaScript 面向对象(OO)特性的体现。更多细节可参考《将对象作为关联数组》一文。
------------ window --------------------------------------
| / \ |
| | |
| this |
| ---------------- | |
| | HTML element | <-- this ----------------- |
| ---------------- | | doSomething() | |
| | | ----------------- |
| -------------------- |
| | onclick property | |
| -------------------- |
| |
----------------------------------------------------------
若 doSomething() 执行时未与任何特定对象关联,关键字 this 将指向 window 窗口。此时函数试图修改 window.style.color,由于 window 对象没有 style 属性,该操作会引发 JavaScript 错误。
拷贝机制
因此,正确使用 this 具有一定难度。在上述示例中,函数本应指向 HTML 元素“自身”。换言之,存在一个函数被拷贝至 onclick 属性。我们来看传统事件注册的情况。
element.onclick = doSomething;
由于函数被完整拷贝至 onclick 属性(此时它成为该方法),在事件处理执行时,this 指向 HTML 元素,从而成功修改颜色。
------------ window --------------------------------------
| |
| |
| |
| ---------------- |
| | HTML element | <-- this ----------------- |
| ---------------- | | doSomething() | |
| | | ----------------- |
| ----------------------- | |
| |copy of doSomething()| <-- copy function |
| ----------------------- |
| |
----------------------------------------------------------
这使得我们可以为多个事件处理程序分配同一个函数拷贝。每次执行时,this 均指向正确的 HTML 元素:
------------ window --------------------------------------
| |
| |
| |
| ---------------- |
| | HTML element | <-- this ----------------- |
| ---------------- | | doSomething() | |
| | | ----------------- |
| ----------------------- | |
| |copy of doSomething()| <-- copy function |
| ----------------------- | |
| | |
| ----------------------- | |
| | another HTML element| <-- this | |
| ----------------------- | | |
| | | | |
| ----------------------- | |
| |copy of doSomething()| <-- copy function |
| ----------------------- |
| |
----------------------------------------------------------
每次函数被调用,this 指向当前正在处理事件的 HTML 元素(即 doSomething() 函数的一个拷贝实例)。
指向机制
如果使用行内事件注册方式呢?
<element onclick="doSomething()">
这里没有拷贝函数,而是直接引用它,区别何在?此时 onclick 属性不包含实际函数代码,仅包含一个函数调用语句。
doSomething();
上述代码的含义是:“去执行 doSomething() 函数”。在 doSomething() 内部,this 关键字再次指向全局 window 对象,导致函数返回错误结果。
------------ window --------------------------------------
| / \ |
| | |
| this |
| ---------------- | |
| | HTML element | <-- this ----------------- |
| ---------------- | | doSomething() | |
| | | ----------------- |
| ----------------------- / \ |
| | go to doSomething() | | |
| | and execute it | ---- reference to |
| ----------------------- function |
| |
----------------------------------------------------------
区别分析
若要通过 this 访问 HTML 元素以处理事件,必须确保该引用实际写入 onclick 属性中,且事件处理已正确注册。如果采用以下方式:
element.onclick = doSomething;
alert(element.onclick)
得到的结果是
function doSomething()
{
this.style.color = '#cc0000';
}
可以看到,this 关键字位于 onclick 方法内部,它指向 HTML 元素。
但如果采用以下写法:
<element onclick="doSomething()">
alert(element.onclick)
得到的结果是
function onclick()
{
doSomething()
}
这里仅引用了函数 doSomething()。this 关键字不在 onclick 方法作用域内,因此不指向 HTML 元素。
示例:拷贝
在以下示例中,this 被写入 onclick 方法:
element.onclick = function () {doSomething()}
element.attachEvent('onclick',doSomething)
<element onclick="doSomething()">
示例:指向
在以下示例中,this 指向 window:
element.onclick = function () {doSomething()}
element.attachEvent('onclick',doSomething)
<element onclick="doSomething()">
需注意上述 attachEvent 方法。这是微软事件注册模型的缺点,它创建了对函数的引用而非拷贝,因此有时无法确定当前处理事件的 HTML 元素。
结合使用
在使用行内事件注册时,也可以将 this 传递给函数。因此可以这样使用:
<element onclick="doSomething(this)">
function doSomething(obj) {
// this is present in the event handler and is sent to the function
// obj now refers to the HTML element, so we can do
obj.style.color = '#cc0000';
}