JavaScript 中 this 的指向规则:从代码实践到原理解析
在 JavaScript 中,this是一个特殊的关键字,其指向一直是前端开发者绕不开的难点。与作用域链(由编译阶段决定)不同,this的指向由函数的调用方式在运行阶段动态决定。本文将结合具体代码案例,详细解析this在不同场景下的指向规则。
一、this 与自由变量:本质区别
在讨论this之前,需要先明确它与 “自由变量” 的核心差异:
- 自由变量:指在函数内部使用、但既不是函数参数也不是局部变量的变量,其查找依赖词法作用域链(由函数声明时的位置决定,属于编译阶段确定的静态规则)。
- this:其指向不依赖声明位置,而是由函数被调用时的方式决定(属于运行阶段的动态规则)。
这一差异使得this成为 JavaScript 中灵活但也容易混淆的特性。
二、this 的指向规则与代码实践
1. 普通函数调用:指向全局对象(严格模式下为 undefined)
当函数以普通方式(非对象方法、非构造函数等)调用时,this默认指向全局对象(浏览器环境中为window)。在严格模式('use strict')下,this为undefined,以避免全局变量污染。
代码示例
<script>
var myObj = {
name:'极客时间',
showThis:function(){
this.name='极客邦' // this指向由调用方式决定
console.log(this);
}
}
var foo = myObj.showThis; // 将函数引用赋值给全局变量foo
foo(); // 普通函数调用:this指向window
</script>
解析:
myObj.showThis是对象的方法,但当我们将其赋值给全局变量foo后,foo()的调用属于普通函数调用。- 此时
this指向全局对象window,因此this.name = '极客邦'实际修改的是window.name,console.log(this)输出window对象。
2. 作为对象方法调用:指向调用对象
当函数作为对象的方法被调用时,this指向该对象本身,这是面向对象编程中访问对象属性的核心方式。
代码示例
<script>
var myObj = {
name:'极客时间',
showThis:function(){
console.log(this); // this指向调用对象myObj
}
}
myObj.showThis(); // 作为对象方法调用
</script>
解析:
myObj.showThis()中,函数showThis被myObj调用,因此this指向myObj,console.log(this)输出myObj对象(包含name: '极客时间')。
3. 使用 call/apply:强制绑定 this
call和apply方法允许手动指定函数执行时的this指向,第一个参数即为this的目标指向。
代码示例
<script>
let bar = {
myName:'极客邦',
test1:1
}
function foo() {
this.myName='极客时间' // this指向被call/apply指定的对象
}
// foo.call(bar); // 与apply效果一致,参数传递方式不同
foo.apply(bar);
console.log(bar); // 输出 {myName: '极客时间', test1: 1}
</script>
解析:
foo.apply(bar)强制将foo函数的this指向bar对象,因此this.myName = '极客时间'实际修改了bar的myName属性。- 最终
bar对象的myName从'极客邦'变为'极客时间'。
4. 构造函数调用:指向实例对象
当函数通过new关键字作为构造函数调用时,this指向新创建的实例对象。
代码示例
<script>
function CreateObj() {
console.log(this);
this.name='极客时间'
}
var myObj = new CreateObj();
</script>
解析:
new操作会创建一个新对象,将构造函数的this指向该对象,并执行构造函数内的代码(如this.name = '极客时间'会给实例添加name属性)。
5. 事件处理函数:指向绑定事件的元素
在 DOM 事件处理中,事件处理函数的this指向触发事件的 DOM 元素(即事件绑定的元素)。
代码示例
<a href="#" id="link">点击我</a>
<script>
document.getElementById('link').addEventListener("click",function(){
console.log(this); // this指向<a>元素
})
</script>
解析:
- 当点击
<a>标签时,事件处理函数被触发,此时this指向该<a>元素,console.log(this)会输出该 DOM 节点的信息。
6. 综合对比:作用域与 this 的区别
代码示例
<script>
'use strict';
var bar = {
myName: "time.geekbang.com",
printName: function() {
console.log(myName); // 自由变量,查找全局作用域的myName
console.log(bar.myName); // 直接访问对象属性
console.log(this); // 由调用方式决定
console.log(this.myName); // 访问this指向的对象的myName
}
}
function foo() {
let myName = '极客时间' // 局部变量,不影响全局
return bar.printName
}
var myName='极客邦'; // 全局变量
var _printName = foo();
_printName(); // 普通调用:this指向window(严格模式下为undefined)
bar.printName(); // 对象方法调用:this指向bar
</script>
解析:
printName中的myName是自由变量,从全局作用域查找,值为'极客邦'。_printName()是普通函数调用,严格模式下this为undefined,因此this.myName会报错。bar.printName()中this指向bar,因此this.myName为'time.geekbang.com'。
三、总结
JavaScript 中this的指向完全由函数的调用方式决定,核心规则可归纳为:
| 调用方式 | this 指向 |
|---|---|
| 普通函数调用 | 全局对象(严格模式下为 undefined) |
| 作为对象方法调用 | 调用该方法的对象 |
| 使用 call/apply 调用 | 手动指定的第一个参数 |
| 构造函数(new)调用 | 新创建的实例对象 |
| DOM 事件处理函数 | 绑定事件的 DOM 元素 |
理解this的关键在于明确函数 “如何被调用”,而非 “在哪里被声明”。通过多实践不同场景下的代码,可以更直观地掌握this的动态指向特性。