本文系统总结JavaScript中this的四种核心指向规则:全局变量挂载于window、普通函数指向调用者、构造函数指向实例以及箭头函数继承外层this。通过非严格模式、严格模式、对象方法、事件处理及call/apply/bind等场景的代码示例,深入解析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绑定到其外层函数对应的对象上。为规避此缺陷,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。而loop方法输出undefined的原因,正是前文所述的JavaScript设计缺陷。针对这种情况,通常的做法是在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。在第二段代码中,声明了包含username和age属性的data对象。依据规则二,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指向事件的源对象,即event目标元素。
构造函数与箭头函数的this指向
场景四:使用new关键字(构造函数)
const fun=function(userName){
this.userName=userName;
}
const user=new fun('郭德纲');
console.log(user.userName); //郭德纲
此处不再赘述,new关键字构造了一个对象实例并赋值给user,因此userName成为user对象的属性。
场景五: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指向的总结即为全部内容,希望能提供参考,也希望大家多多支持本站。