深入理解JavaScript中this关键字的指向规则
本文详细解析JavaScript中this关键字的指向机制。通过普通函数、闭包、构造函数、call/apply以及DOM事件五大场景,阐明this始终指向函数运行时所在的对象这一核心原则,并提供缓存this等常见问题的解决方案。
JavaScript this关键字概述
本文旨在深入探讨JavaScript中this关键字的核心机制。如果读者在阅读后能够准确判断this的指向,那么撰写此文的努力便有了意义。
理解this的关键在于牢记:this始终指向函数运行时所在的对象,而非函数被创建时的对象。简而言之,谁调用了该函数,this就指向谁。
接下来,我们将通过几种典型场景来分析this的具体指向情况。
普通函数中的this指向
1、普通函数中的this
无论this出现在何处,首要任务是确定函数执行时的上下文环境。
var name="全局";
function getName(){
var name="局部";
return this.name;
};
alert(getName());
当this出现在全局作用域的函数getName中时,该函数的执行环境位于全局对象上。
alert(getName());
显然,函数getName所属的对象是全局对象,即window,因此this指向window。此时getName返回的this.name实际上等同于window.name,所以alert弹出的结果是“全局”。
那么,如果this出现在局部作用域的函数中,它的指向会发生什么变化呢?
var name="全局";
var xpg={
name:"局部",
getName:function(){
return this.name;
}
};
alert(xpg.getName());
在此场景中,函数getName并非处于全局环境,而是处于xpg对象的环境中。无论this身处何地,关键在于找到函数执行时的位置。此时函数getName的执行环境位于xpg对象内部。
alert(xpg.getName());
显然,函数getName所属的对象是xpg,因此this指向xpg对象。此时getName返回的this.name实际上等同于xpg.name,所以alert弹出的结果是“局部”。
通过一个例子来巩固这一概念:
var someone = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var other = {
name: "Tom",
showName: someone.showName
}
other.showName(); //Tom
虽然this关键字是在someone.showName中声明的,但在执行时调用者是other.showName,因此this指向当前执行对象other,最终alert弹出的是other.name。
闭包中的this处理
2、闭包中的this
闭包机制较为特殊,此处简要说明:闭包是指在一个函数内部创建另一个函数,且内部函数访问了外部函数的变量。当this与闭包结合时,情况会变得复杂。
var name="全局";
var xpg={
name:"局部",
getName:function(){
return function(){
return this.name;
};
}
};
alert(xpg.getName()());
此时this处于困境中,它位于getName函数内部的匿名函数里,而该匿名函数调用了外部变量name,从而构成了闭包。无论this身处何处,关键在于找到函数执行时的位置。此时不能依据getName的执行环境判断,而应依据匿名函数的执行环境判断。
function (){
return this.name;
};
显然,匿名函数所属的对象是window,因此this指向window。此时匿名函数返回的this.name实际上等同于window.name,所以alert弹出的结果是“全局”。
那么,如何在闭包中让this指向xpg对象呢?方法是缓存this。
var name="全局";
var xpg={
name:"局部",
getName:function(){
var that=this;
return function(){
return that.name;
};
}
};
alert(xpg.getName()());
在getName函数中定义变量that=this,此时getName函数的执行环境位于xpg对象中。
alert(xpg.getName());
此时this指向xpg对象,因此that也指向xpg对象。在闭包的匿名函数中返回that.name,此时返回的that.name实际上等同于xpg.name,因此alert弹出“局部”。
构造函数与call/apply的影响
3、new关键字创建新对象
在new关键字后的构造函数中,this指向由该构造函数创建的新对象:
function Person(__name){
this.name = __name; //这个this指向用该构造函数构造的新对象,这个例子是Bob对象
}
Person.prototype.show = function(){
alert(this.name); //this 指向Person,this.name = Person.name;
}
var Bob = new Person("Bob");
Bob.show(); //Bob
4、call与apply中的this
在JavaScript中,能够强制改变this指向的莫过于call与apply方法。它们就像this的管理者,指定this指向哪里,this就必须指向哪里。当没有参数时,当前对象默认为window。
var name="全局";
var xpg={
name:"局部"
};
function getName(){
alert(this.name);
}
getName(xpg);
getName.call(xpg);
getName.call();
其中this位于函数getName中。无论this身处何处,关键在于找到函数执行时的位置。此时函数getName的执行环境位于全局对象。
getName(xpg);
显然,函数getName所属的对象是window,因此this指向window对象。此时getName返回的this.name实际上等同于window.name,所以alert弹出的结果是“全局”。
接下来,call与apply登场,因为this必须听从它们的指令。执行getName.call(xpg);其中,call指定this的指向为xpg对象,由于this被迫在xpg中执行,此时this指向xpg对象,this.name实际上等同于xpg.name,因此alert弹出“局部”。
eval与全局环境下的this
5、eval中的this
对于eval函数,其执行时似乎没有指定当前对象,但实际上其this并非指向window,因为该函数执行时的作用域是当前作用域,等同于在该行将内部代码嵌入。下面的例子说明了这个问题:
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
eval("alert(this.name)");
}
};
Bob.showName(); //Bob
6、没有明确的当前对象时的this
当没有明确的执行时当前对象时,this指向全局对象window。例如对于全局变量引用的函数上我们有:
var name = "Tom";
var Bob = {
name: "Bob",
show: function(){
alert(this.name);
}
}
var show = Bob.show;
show(); //Tom
你可能也能理解成show是window对象下的方法,所以执行时的当前对象是window。但在局部变量引用的函数上,却无法这样解释:
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var Tom = {
name: "Tom",
showName: function(){
var fun = Bob.showName;
fun();
}
};
Tom.showName(); //window
在浏览器中,setTimeout、setInterval和匿名函数执行时的当前对象是全局对象window,这条可以看作上一条的一个特殊情况。
var name = "Bob";
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
setTimeout(this.showName, 1000);
}
};
nameObj.waitShowName();
因此在运行this.showName的时候,this指向了window,所以最后显示了window.name。
DOM事件中的this应用
7、dom事件中的this
(1)你可以直接在dom元素中使用
<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />
分析:对于dom元素的一个onclick(或其他如onblur等)属性,它为所属的html元素所拥有,直接在它触发的函数里写this,this应该指向该html元素。
(2)给dom元素注册js函数 a、不正确的方式
<script type="text/javascript">
function thisTest(){
alert(this.value); // 弹出undefined, this在这里指向??
}
</script>
<input id="btnTest" type="button" value="提交" onclick="thisTest()" />
分析:onclick事件直接调用thisTest函数,程序就会弹出undefined。因为thisTest函数是在window对象中定义的,所以thisTest的拥有者(作用域)是window,thisTest的this也是window。而window是没有value属性的,所以就报错了。
<input id="btnTest" type="button" value="提交" />
<script type="text/javascript">
function thisTest(){
alert(this.value);
}
document.getElementById("btnTest").onclick=thisTest; //给button的onclick事件注册一个函数
</script>
分析:在前面的示例中,thisTest函数定义在全局作用域(这里就是window对象),所以this指代的是当前的window对象。而通过document.getElementById(“btnTest”).onclick=thisTest;这样的形式,其实是将btnTest的onclick属性设置为thisTest函数的一个副本,在btnTest的onclick属性的函数作用域内,this归btnTest所有,this也就指向了btnTest。其实如果有多个dom元素要注册该事件,我们可以利用不同的dom元素id,用下面的方式实现:
document.getElementById("domID").onclick=thisTest; //给button的onclick事件注册一个函数。
分析:在前面的示例中,thisTest函数定义在全局作用域(这里就是window对象),所以this指代的是当前的window对象。而通过document.getElementById(“btnTest”).onclick=thisTest;这样的形式,其实是将btnTest的onclick属性设置为thisTest函数的一个副本,在btnTest的onclick属性的函数作用域内,this归btnTest所有,this也就指向了btnTest。其实如果有多个dom元素要注册该事件,我们可以利用不同的dom元素id,用下面的方式实现:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" />
<input id="btnTest2" type="button" value="提交2" />
<script type="text/javascript">
function thisTest(){
this.value="提交中";
}
var btn=document.getElementById("btnTest1");
alert(btn.onclick); //第一个按钮函数
var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第二个按钮函数
</script>
其弹出的结果是:
//第一个按钮
function onclick(){
thisTest()
}
//第二个按钮
function thisTest(){
this.value="提交中";
}
因为多个不同的HTML元素虽然创建了不同的函数副本,但每个副本的拥有者都是相对应的HTML元素,各自的this也都指向它们的拥有者,不会造成混乱。为了验证上述说法,我们改进一下代码,让button直接弹出它们对应的触发函数:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest(this)" />
<input id="btnTest2" type="button" value="提交2" onclick="thisTest(this)" />
<input id="btnTest3" type="button" value="提交3" onclick="thisTest(this)" />
<input id="btnTest4" type="button" value="提交4" onclick="thisTest(this)" />
<script type="text/javascript">
function thisTest(obj){
alert(obj.value);
}
</script>
从上面的结果你一定理解的更透彻了。By the way,每新建一个函数的副本,程序就会为这个函数副本分配一定的内存。而实际应用中,大多数函数并不一定会被调用,于是这部分内存就被白白浪费了。所以我们通常都这么写:
//js事件 添加 EventUtil.addEvent(dom元素,事件名称,事件触发的函数名) 移除EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名)
var EventUtil = new eventManager();
//js事件通用管理器 dom元素 添加或者移除事件
function eventManager() {
//添加事件
//oDomElement:dom元素,如按钮,文本,document等; ****** oEventType:事件名称(如:click,如果是ie浏览器,自动将click转换为onclick);****** oFunc:事件触发的函数名
this.addEvent = function(oDomElement, oEventType, oFunc) {
//ie
if (oDomElement.attachEvent) {
oDomElement.attachEvent("on" + oEventType, oFunc);
}
//ff,opera,safari等
else if (oDomElement.addEventListener) {
oDomElement.addEventListener(oEventType, oFunc, false);
}
//其他
else {
oDomElement["on" + oEventType] = oFunc;
}
}
this.removeEvent = function(oDomElement, oEventType, oFunc) {
//ie
if (oDomElement.detachEvent) {
oDomElement.detachEvent("on" + oEventType, oFunc);
}
//ff,opera,safari等
else if (oDomElement.removeEventListener) {
oDomElement.removeEventListener(oEventType, oFunc, false);
}
//其他
else {
oDomElement["on" + oEventType] = null;
}
}
}
这是因为我们使用了函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针。这样写一个函数,调用的地方给它分配一个(指针)引用,这样效率就高很多。当然,如果你觉得这样注册事件不能兼容多种浏览器,可以写下面的注册事件的通用脚本:
正像注释写的那样,要注册dom元素事件,用EventUtil.addEvent(dom元素,事件名称,事件触发的函数名)即可,移除时可以这样写:EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名),这是题外话,不说了。以上就是本文的全部内容,希望通过这篇文章大家更加了解javascript的this关键字,大家共同进步。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
