本文系统总结JavaScript中this指向的六种核心场景:全局调用、对象方法、DOM事件、构造函数、ES6箭头函数以及通过call/apply/bind强制改变指向。重点解析非严格与严格模式下的差异,以及箭头函数词法作用域对this绑定的影响,并提供解决内部函数this丢失的实用技巧。
JavaScript this指向基础规则
在JavaScript开发中,this的指向机制往往是初学者乃至有经验的开发者容易困惑的难点,也是各类前端面试中的高频考点。为了清晰掌握这一概念,我们需要先明确几个基础规则。
第一,全局变量默认挂载在window对象之下。
第二,在常规函数调用中,this通常指向其调用者。
第三,在ES6引入的箭头函数中,this指向定义时的创建者(词法作用域),而非调用者。
第四,可以通过call、apply和bind这三个方法人为地改变函数的this指向。
接下来,我们将结合具体代码场景对上述规则进行详细分析。
普通函数与严格模式下的this
第一种情况:普通函数调用。
在非严格模式下,
const func = function () {
console.log(this);
const func2 = function () {
console.log(this);
};
func2(); //Window
};
func(); //Window
在严格模式下,
'use strict'
const func = function () {
console.log(this);
const func2 = function () {
console.log(this);
};
func2(); //undefined
};
func(); //undefined
综合第四与第一条规则:func为全局函数,默认挂载于window对象,其this指向调用者window,故输出window对象。但在严格模式下,this禁止指向全局变量window,因此输出undefined(func2在直接调用时默认指向全局window,此属JavaScript设计缺陷;理想设计应为内部函数this绑定至外层函数对应对象。为规避此缺陷,程序员采用变量替代法,约定俗成命名为that,该方法后续将详述)。
对象方法中的this与解决方案
第二种情况:作为对象方法调用。
const user = {
userName: '小张',
age: 18,
selfIntroduction: function () {
const str = '我的名字是:' + this.userName + ",年龄是:" + this.age;
console.log(str);
const loop = function () {
console.log('我的名字是:' + this.userName + ",年龄是:" + this.age);
};
loop(); //我的名字是:undefined,年龄是:undefined
}
};
user.selfIntroduction(); //我的名字是:小张,年龄是:18
根据规则一,this指向其调用者。在selfIntroduction()方法中,调用者是user对象,因此方法内部的this指向父对象user。然而,在selfIntroduction内部定义的loop函数输出undefined,原因在于JavaScript的设计特性:内部函数loop是独立调用的,其this并未绑定到user上。针对这种情况,通常的解决方案是在selfIntroduction方法内部将this缓存下来,传递给内部函数使用。
const user = {
userName: '小张',
age: 18,
selfIntroduction: function () {
const str = '我的名字是:' + this.userName + ",年龄是:" + this.age;
console.log(str);
const that=this;
const loop = function () {
console.log('我的名字是:' + that.userName + ",年龄是:" + that.age);
};
loop(); //我的名字是:小张,年龄是:18
}
};
user.selfIntroduction(); //我的名字是:小张,年龄是:18
经过上述处理后,loop函数的this指向便符合预期。
const user={
userName:'小张',
age:18,
selfIntroduction:function(){
const str='我的名字是:'+this.userName+",年龄是:"+this.age;
console.log(str);
}
};
const other =user.selfIntroduction;
other(); //我的名字是:undefined,年龄是:undefined
const data={
userName:'小李',
age:19,
};
data.selfIntroduction=user.selfIntroduction;
data.selfIntroduction(); //我的名字是:小李,年龄是:19
再看另一段代码:将selfIntroduction方法赋值给全局变量other后调用other()。由于other挂载在全局window对象下,而window对象中不存在userName和age属性,因此输出undefined。第二段代码中,声明了data对象并包含username和age属性。根据规则二,this指向调用者,此时data是selfIntroduction的调用者,因此成功输出了data对象的userName和age属性。
事件处理中的this指向
第三种情况:在HTML中作为事件触发器。
<body>
<div id="btn">点击我</div>
</body>
const btn=document.getElementById('btn');
btn.addEventListener('click',function () {
console.log(this); //<div id="btn">点击我</div>
})
这种情况同样遵循规则二,即this指向调用者。在事件处理函数中,this指向触发事件的事件源元素(event target)。
构造函数中的this绑定
第四种情况:使用new关键字(构造函数)。
const fun=function(userName){
this.userName=userName;
}
const user=new fun('郭德纲');
console.log(user.userName); //郭德纲
new关键字会构造一个对象实例,并将其赋值给user变量。因此,构造函数内部的this指向新创建的user对象,userName随之成为user对象的属性。
ES6箭头函数的this特性
第五种情况:ES6箭头函数。
const func1=()=>{
console.log(this);
};
func1(); //Window
const data={
userName:'校长',
selfIntroduction:function(){
console.log(this); //Object {userName: "校长", selfIntroduction: function}
const func2=()=>{
console.log(this); //Object {userName: "校长", selfIntroduction: function}
}
func2();
}
}
data.selfIntroduction();
回顾开头提到的规则三:在ES6箭头函数中,this指向定义时的创建者,而非调用者。fun1在全局作用域下创建,因此其this指向全局window;而fun2在对象data的方法中创建,其this指向data对象。因此,在func2函数内部,this指向data对象。个人认为,ES6箭头函数的this绑定机制是对前述JavaScript设计缺陷的一种改进。
使用call/apply/bind改变this
第六种情况:改变this的指向。
call、apply和bind这三个方法允许开发者人为地改变函数的this指向。关于这三者的具体区别,将在后续文章中详细解释。此处先以其中一个为例进行说明。
const func=function(){
console.log(this);
};
func(); //window
func.apply({userName:"郭德纲"}); //Object {userName: "郭德纲"}
这三个方法均能改变this指向,区别在于:call和apply在绑定this后会立即执行该方法,而bind方法会返回一个新的可执行函数,不会立即执行。
综上所述,核心要点即开头提到的四点。
1. 全局变量默认挂载在window对象下。
2. 一般情况下,this指向它的调用者。
3. 在ES6的箭头函数中,this指向创建者,并非调用者。
4. 通过call、apply、bind可以改变this的指向。
首次撰写此类技术总结,难免忐忑,担心内容准确性或读者反馈。如有不足之处,欢迎指正。
以上是对JavaScript中this指向问题的总结,希望对大家有所帮助。如有任何疑问,欢迎留言,我会及时回复。