本文详细解析JavaScript中this关键字的指向规则。通过全局函数、对象方法、事件处理函数以及call/apply/bind等场景,说明this在不同调用方式下的具体指向。重点讲解事件绑定中赋值与引用的区别,以及如何通过改变调用上下文来修正this指向,帮助开发者避免常见的this绑定错误。
this 关键字指向原则
与其他面向对象语言类似,JavaScript 也提供了 this 关键字,用于在函数内部指向调用该方法的对象。在实际开发中,确定 this 的具体指向至关重要,通常可以遵循以下核心原则进行判断:
- 当函数通过 Function.call 或 Function.apply 方法被调用时,this 指向这两个方法的第一个参数。若该参数为 null 或 undefined,则 this 会指向全局对象(在浏览器环境中即 window 对象)。
- 若函数通过 Function.bind 方法被调用,this 将指向 bind 方法第一个参数所指定的对象(该绑定在方法创建时即已确定)。
- 当函数作为某个对象的方法被调用时,this 指向该对象本身。
- 如果函数以独立函数的形式被调用,且不依附于任何特定对象,则 this 指向全局变量 window。
全局函数中的 this 指向
全局函数场景
首先,我们来分析“全局函数”的情况:
function introduce() {
alert("Hello, I am Laruence\r\n");
}
对于上述定义的函数,this 关键字究竟指向哪里呢?
定义在全局作用域内的函数,其所有者即为当前页面,也就是 window 对象。
这也是为什么我将函数名用引号括起来的原因。因为定义在全局的函数,本质上就是 window 对象的一个方法。
因此,我们既可以直接通过函数名调用,也可以通过 window.方法名的方式调用。此时,方法内部的 this 关键字指向其所有者:window 对象。
如果我们查看 window 对象的 introduce 属性,会得到如下结果:
var name = "I am Laruence";
function introduce() {
alert(this.name);
}
alert(window.introduce);
/**
* output:
* function introduce() {
* alert(this.name);
* }
*/
阅读上述代码后,你可能会联想到:既然全局函数是 window 对象的方法,而全局变量是 window 对象的属性(前文作用域部分已提及),那么在全局函数中是否可以通过 this 关键字来访问全局变量呢?
答案是肯定的。如果调用 introduce 函数,你将看到输出:“我是 Laruence”。
事件处理函数中的 this 指向
事件处理函数场景
或许,导致 this 关键字令人困惑的主要原因,在于将函数(方法)应用于事件处理时。
<input id="name" type="text" name="name" value="Laruence" />
例如,现在我们需要在点击“name”输入框时,显示该输入框的 value 值。此时可以编写如下代码:
function showValue() {
alert(this.value);
}
document.getElementById('name').onclick = showValue;
上述代码运行正常,但为何如此?不是说函数的 this 指针永远指向函数所有者吗?不是说全局变量的所有者是 window 对象吗?
如果你能想到这个问题,说明你正在认真研读本文;否则,建议你从头开始阅读,否则即便读完,你可能依然感到困惑。
没错,对于上述代码,showValue 确实定义在全局对象中,那么问题只能出在 onclick 事件绑定的环节。
我们知道,在 JavaScript 中一切皆对象,函数和方法也是对象的属性,区别仅在于函数具有可执行的内部属性。因此,在上述代码中,对 onclick 绑定处理器时,实际上是将 id 为 name 的输入框 DOM 对象的 onclick 属性进行了赋值。
也就是说,我们将 showValue 函数复制给了 name 输入框对象的 onclick 属性。如果此时查看该 onclick 属性,会发现:
function showValue() {
alert(this.value);
}
document.getElementById('name').onclick = showValue;
alert(document.getElementById('name').onclick);
/**
* output
* function showValue() {
* alert(this.value);
* }
*/
因此,当事件触发时,便会调用 name 输入框的 onclick 方法,此时 this 关键字自然指向 name 输入框元素。
然而,令人困惑的情况随之而来,例如下面这种写法:
function showValue() {
alert(this.value);
}
<input id="name" type="text" name="name" value="Laruence" onclick="showValue()"/>
该写法无法正常运行,这又是为什么呢?
这是因为此时并非赋值操作,而是引用传递。
如果我们注意两种 onclick 的写法,会发现之前的方法使用的是:
dom.onclick = showvalue; //没有调用符
而刚才的方法使用的是:
onclick = "showvalue()" //有调用符
这也能侧面反映出两者的区别:前者是赋值,后者是引用。如果此时查看输入框的 onclick 属性,我们会得到:
alert(dom.onclick);
/**
* output:
* function onclick() {
* showValue();
* }
*/
看到区别了吗?这就解释了原因所在。
讲到这里,有一个有趣的例子,大家可以在 IE 浏览器下尝试:
<img src="xxx" onerror="alert(1);} function hi() { alert(2); " />
改变 this 指向的方法
改变 this 指向的方法
既然我们已经知道了原因,那么如何让 this 指向我们期望的目标呢?
对于上述事件处理函数,我们可以采用以下几种写法:
dom.onclick = showValue();
dom.onclick = function() { alert(this.value) ;}
<input onclick="alert(this.value);" /> //想想刚才我们的引用,是如何把这句嵌入的.
dom.addEventListener(dom, showValue, false); //ff only
而对于非事件处理函数的场合,我们可以使用 apply 或 call 方法来改变 this 关键字的指向。
例如:
var laruence = {
name : 'laruence',
age : 26,
position : 'Senior PHP Engineer',
company : 'Baidu.inc'
};
function introduce() {
alert(this.name);
}
introduce.call(laruence);
函数被 Function.call 调用的示例:
var myObject = {
sayHello : function() {
console.log('Hi! My name is ' + this.myName);
},
myName : 'Rebecca'
};
var secondObject = {
myName : 'Colin'
};
myObject.sayHello(); // logs 'Hi! My name is Rebecca'
myObject.sayHello.call(secondObject); // logs 'Hi! My name is Colin'
函数被 Function.call 调用后的结果:
var myName = 'the global object',
sayHello = function () {
console.log('Hi! My name is ' + this.myName);
},
myObject = {
myName : 'Rebecca'
};
var myObjectHello = sayHello.bind(myObject);
sayHello(); // logs 'Hi! My name is the global object'
myObjectHello(); // logs 'Hi! My name is Rebecca'
函数被对象调用的示例:
var myName = 'the global object',
sayHello = function() {
console.log('Hi! My name is ' + this.myName);
},
myObject = {
myName : 'Rebecca'
},
secondObject = {
myName : 'Colin'
};
myObject.sayHello = sayHello;
secondObject.sayHello = sayHello;
sayHello(); // logs 'Hi! My name is the global object'
myObject.sayHello(); // logs 'Hi! My name is Rebecca'
secondObject.sayHello(); // logs 'Hi! My name is Colin'
在调用深层命名空间中的函数时,我们通常会缓存一个变量指向该函数,以减少代码量。但这样做会改变函数内部 this 的值,导致执行错误的操作。例如:
var myNamespace = {
myObject : {
sayHello : function() {
console.log('Hi! My name is ' + this.myName);
},
myName : 'Rebecca'
}
};
var hello = myNamespace.myObject.sayHello;
hello(); // logs 'Hi! My name is undefined'
因此,若要缓存变量以节省代码量,正确的做法是只缓存到调用该函数的对象为止:
var myNamespace = {
myObject : {
sayHello : function() {
console.log('Hi! My name is ' + this.myName);
},
myName : 'Rebecca'
}
};
var obj = myNamespace.myObject;
obj.sayHello(); // logs 'Hi! My name is Rebecca'
总之,有一个核心原则:谁调用了那个函数,this 就指向谁。