引言
你是否曾在深夜调试时,对着一个
undefined或指向了window的this陷入沉思?this是 JavaScript 中最强大但也最容易让人困惑的概念之一。它不像普通变量那样遵循词法作用域,而是在函数被调用时才会被绑定。今天,我们将用清晰的图示和例子,彻底揭开它的神秘面纱。
核心思想:this 的绑定取决于“调用方式”,而非“定义位置”
规则一:默认绑定
场景:独立函数调用。
规则:在非严格模式下,this 指向全局对象(浏览器中是 window);在严格模式下,this 是 undefined。
例子:
function foo() {
console.log(this.abc);
}
var abc = 2; // 全局变量,相当于 window.abc = 2
foo(); // 2
严格模式下的“安全”举措:
"use strict";
function foo() {
console.log(this); // undefined
}
foo(); // TypeError: Cannot read properties of undefined (reading 'a')
规则二:隐式绑定
场景:函数被一个“上下文对象”所拥有并调用。
规则:this 指向这个调用它的对象。
例子:
function bar() {
console.log(this.name);
}
const obj = {
name: 'Aurora',
bar: bar // 函数 bar 被作为 obj 的方法
};
obj.bar(); // 'Aurora'
隐式丢失(常见的坑!) :
const name = 'Global Cat';
const obj = {
name: 'Aurora',
bar: function() {
console.log(this.name);
}
};
// 把方法赋值给一个变量
const func = obj.bar;
func(); // 'Global Cat', 因为此时是默认绑定规则
规则三:显式绑定
场景:使用 call, apply, 或 bind 方法直接指定 this。
规则:this 被强制绑定到你传入的第一个参数对象上。
例子:
function introduce(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
const person1 = { name: 'Harper' };
const person2 = { name: 'Aurora' };
// 1. call (参数逐个传递)
introduce.call(person1, 'Hello'); // "Hello, I'm Harper"
// 2. apply (参数以数组传递)
introduce.apply(person2, ['Hi']); // "Hi, I'm Aurora"
// 3. bind (返回一个绑定了this的新函数)
const boundFunc = introduce.bind(person1, 'Hey');
boundFunc(); // "Hey, I'm Harper"
规则四:new 绑定
场景:使用 new 关键字来调用函数(构造函数)。
规则:this 会绑定到新创建的那个空对象上。
例子:
function Person(name) {
// 1. 创建一个新的空对象 {},并将this指向它
// 2. 执行函数体,为这个新对象添加属性
this.name = name;
// 3. 自动返回这个新对象
}
const me = new Person('Harper');
console.log(me.name); // 'Harper'
优先级与特殊规则
优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数——规则的“例外”
箭头函数没有自己的 this,它内部的 this 继承自定义它时所在的外层作用域。
const obj = {
name: 'Harper',
regularFunc: function() {
console.log('Regular:', this.name); // 'Harper'
},
arrowFunc: () => {
console.log('Arrow:', this.name); // 指向外层(可能是window),不是obj!
}
};
obj.regularFunc();
obj.arrowFunc();
总结
| 绑定规则 | 调用方式 | this 指向 |
|---|---|---|
| 默认绑定 | foo() | 全局对象 / undefined (严格模式) |
| 隐式绑定 | obj.foo() | 调用它的对象 obj |
| 显式绑定 | foo.call(obj) | 指定的对象 obj |
new 绑定 | new Foo() | 新创建的对象实例 |
| 箭头函数 | () => {} | 定义时外层作用域的 this |
结尾
理解
this的关键,就是忘掉它定义在哪,转而问自己:“这个函数是如何被调用的? ” 下次再遇到this的困惑时,请拿出这张“寻宝图”,按照默认、隐式、显式、new绑定的顺序逐一排查,你一定能找到答案。