浅解 JavaScript 中的 this
什么是 this?
this是 JavaScript 中的一个关键字,它指向当前代码执行时的“上下文”对象。简单来说,this表示“谁在调用这个函数”或“这个函数属于哪个对象”。- 关键点:
this的值不是固定的,它在运行时根据函数的调用方式动态决定。 - ES6 影响:普通函数的
this由调用上下文决定,而 ES6 引入的箭头函数(=>)的this固定为定义时外层作用域的this(词法作用域)。 - 目标:通过本篇内容,你将理解
this的四种绑定规则、箭头函数的特殊行为,并通过示例和实践掌握其用法。
this 的四种绑定规则
JavaScript 中的 this 有四种主要绑定规则,优先级从高到低为:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。以下逐一讲解,每种规则配多个示例。
1. 默认绑定(Default Binding)
-
定义:当函数独立调用(不是作为对象的方法或通过其他绑定方式)时,
this指向全局对象(浏览器中是window,Node.js 中是global)。在严格模式("use strict")下,this为undefined。 -
场景:直接调用函数、函数作为回调但未指定上下文。
-
示例:
-
独立函数调用:
function sayHello() { console.log(this); // window(非严格模式)或 undefined(严格模式) } sayHello(); -
嵌套函数问题:
function outer() { function inner() { console.log(this); // window 或 undefined } inner(); } outer(); -
严格模式:
"use strict"; function test() { console.log(this); // undefined } test();
-
-
注意:
- 默认绑定是最低优先级,常导致意外的
this值。 - 在非严格模式下,
this指向全局对象可能引发变量污染。
- 默认绑定是最低优先级,常导致意外的
-
实践:运行以上代码,观察
this的值;在严格模式和非严格模式下对比输出。
2. 隐式绑定(Implicit Binding)
- 定义:当函数作为对象的方法调用时,
this指向调用该方法的对象(即点号.前的对象)。 - 场景:对象方法调用、链式调用。
- 示例:
- 对象方法:
const user = { name: "Alice", greet: function() { console.log(this.name); } }; user.greet(); // Alice(this 指向 user) - 链式调用:
const app = { config: { setting: "dark", show: function() { console.log(this.setting); } } }; app.config.show(); // dark(this 指向 app.config) - 丢失隐式绑定:
const user = { name: "Bob", greet: function() { console.log(this.name); } }; const fn = user.greet; // 提取方法 fn(); // window.name 或 undefined(丢失 user 上下文,变为默认绑定)
- 对象方法:
- 注意:
- 隐式绑定依赖调用时的对象。如果方法被单独提取(如赋值给变量),
this会丢失,退化为默认绑定。 - 常见于回调函数(如
setTimeout或事件监听器)中。
- 隐式绑定依赖调用时的对象。如果方法被单独提取(如赋值给变量),
- 实践:创建一个对象,调用其方法,观察
this;将方法赋值给变量后调用,验证this丢失。
3. 显式绑定(Explicit Binding)
- 定义:通过
call、apply或bind方法显式指定函数的this值。 - 场景:需要强制设置
this,如借用方法或修复上下文。 - 方法说明:
call(thisArg, arg1, arg2, ...):立即调用函数,指定this和参数。apply(thisArg, [arg1, arg2, ...]):立即调用函数,参数以数组传递。bind(thisArg):返回新函数,this固定为指定值。
- 示例:
- 使用 call:
function greet() { console.log(this.name); } const user = { name: "Alice" }; greet.call(user); // Alice(this 强制绑定到 user) - 使用 bind:
const user = { name: "Bob" }; const boundGreet = function() { console.log(this.name); }.bind(user); boundGreet(); // Bob(this 固定为 user) - 修复丢失的 this:
const user = { name: "Charlie", greet: function() { console.log(this.name); } }; setTimeout(user.greet, 1000); // window.name 或 undefined setTimeout(user.greet.bind(user), 1000); // Charlie(绑定 user)
- 使用 call:
- 注意:
bind创建新函数,call和apply立即调用。- 显式绑定优先级高于隐式绑定。
- 实践:用
call或bind修复一个回调函数的this丢失问题;尝试用apply传递数组参数。
4. new 绑定
- 定义:当函数作为构造函数通过
new调用时,this指向新创建的对象。 - 场景:创建对象实例,面向对象编程。
- 示例:
- 基本构造函数:
function User(name) { this.name = name; console.log(this); // User { name } } const user = new User("Alice"); // this 指向新对象 - 结合对象方法:
function Car(model) { this.model = model; this.show = function() { console.log(this.model); }; } const myCar = new Car("Toyota"); myCar.show(); // Toyota(this 指向 myCar)
- 基本构造函数:
- 注意:
new绑定的优先级最高。- 构造函数通常以大写字母开头(如
User),以示区分。
- 实践:创建一个构造函数,实例化对象并调用方法,观察
this的值。
ES6 箭头函数与 this
- 定义:箭头函数(
=>)不绑定自己的this,而是继承定义时外层作用域的this(词法作用域)。 - 用途:
- 适合需要固定
this的场景,如回调函数(map、setTimeout)。 - 不适合需要动态
this的场景,如对象方法或事件监听器。
- 适合需要固定
- 关键点:
- 箭头函数的
this在定义时确定,无法通过call、apply或bind改变。 - 没有自己的
arguments或prototype,不能用作构造函数。
- 箭头函数的
- 示例:
- 固定 this:
const user = { name: "Alice", greet: function() { setTimeout(() => console.log(this.name), 1000); // Alice(继承 user 的 this) } }; user.greet(); - 对比普通函数:
const user = { name: "Bob", greet: function() { setTimeout(function() { console.log(this.name); // window.name 或 undefined }, 1000); } }; user.greet(); - 嵌套箭头函数:
const group = { title: "Team", members: ["Alice", "Bob"], show: function() { this.members.forEach(member => { console.log(`${this.title}: ${member}`); // Team: Alice, Team: Bob }); } }; group.show(); - 箭头函数误用:
const button = { text: "Click me", handleClick: () => { console.log(this.text); // undefined(this 指向 window/global) } }; button.handleClick();
- 固定 this:
- 注意:
- 箭头函数适合回调函数,但不适合定义对象方法或事件处理函数。
- 不能用
new调用箭头函数:const Arrow = () => {}; new Arrow(); // TypeError: Arrow is not a constructor
常见问题与解答
- 问:为什么
this在不同场景下值不同?- 答:
this的值由函数调用方式决定,遵循四种绑定规则(new > 显式 > 隐式 > 默认)。
- 答:
- 问:如何避免
this丢失?- 答:
- 使用
bind固定this:const user = { name: "Alice", greet: function() { console.log(this.name); } }; setTimeout(user.greet.bind(user), 1000); // Alice - 使用箭头函数继承外层
this:const user = { name: "Alice", greet: function() { setTimeout(() => console.log(this.name), 1000); } }; user.greet(); // Alice - 将
this保存到变量:const user = { name: "Alice", greet: function() { const self = this; setTimeout(function() { console.log(self.name); }, 1000); } }; user.greet(); // Alice
- 使用
- 答:
- 问:箭头函数什么时候不该用?
- 答:不适合需要动态
this的场景,如对象方法或事件监听器:const obj = { value: 42, getValue: () => console.log(this.value) // undefined(this 指向 window/global) }; obj.getValue();
- 答:不适合需要动态
- 问:如何调试
this的值?- 答:
- 在函数中添加
console.log(this)检查。 - 使用浏览器开发者工具(F12),设置断点观察
this。 - 示例:
function test() { console.log(this); } test(); // 检查输出
- 在函数中添加
- 答:
记忆技巧
- 类比:把
this想象成“电话的接听者”:- 默认绑定:没人接听,电话响在全局(
window或undefined)。 - 隐式绑定:对象是“主人”,
this指向它(点号前的对象)。 - 显式绑定:你强行指定接听者(
call、apply、bind)。 - new 绑定:新建一个“接听者”对象(新实例)。
- 箭头函数:像“录音电话”,固定指向定义时的主人(外层
this)。
- 默认绑定:没人接听,电话响在全局(
- 口诀:
this看调用,对象点号优先,new最强,箭头锁定外层。
实践建议
- 测试不同场景:
- 写一个对象,包含普通函数和箭头函数方法,对比
this的输出:const obj = { name: "Test", normal: function() { console.log(this); }, arrow: () => console.log(this) }; obj.normal(); // { name: "Test", ... } obj.arrow(); // window 或 undefined - 用
setTimeout测试普通函数和箭头函数的this差异:const user = { name: "Alice", greet: function() { setTimeout(function() { console.log(this); }, 1000); // window 或 undefined setTimeout(() => console.log(this), 1000); // user } }; user.greet();
- 写一个对象,包含普通函数和箭头函数方法,对比
- 修复 this 问题:
- 找一段丢失
this的代码(如回调函数),用bind或箭头函数修复:const user = { name: "Bob", greet: function() { setTimeout(this.greet.bind(this), 1000); // Bob } }; user.greet();
- 找一段丢失
- 小项目:
- 写一个计数器对象,包含
increment方法,用普通函数和箭头函数实现,对比this行为:const counter = { count: 0, increment: function() { setTimeout(() => { this.count++; console.log(this.count); }, 1000); } }; counter.increment(); // 1
- 写一个计数器对象,包含
- 社区互动:
- 在 X 平台搜索 #JavaScript 或 #this,查看开发者分享的
this相关问题。 - 分享你的代码(如上面的计数器),求反馈或提问。
- 在 X 平台搜索 #JavaScript 或 #this,查看开发者分享的
综合示例:结合 ES6 特性
以下是一个综合示例,展示 this 在普通函数、箭头函数和对象中的行为:
const team = {
name: "Developers",
members: ["Alice", "Bob"],
showMembers: function() {
// 普通函数:this 指向 team
console.log(`Team: ${this.name}`);
// forEach 回调用箭头函数,this 继承 team
this.members.forEach(member => {
console.log(`${this.title}: ${member}`);
});
},
showLater: function() {
// 普通函数在 setTimeout 中丢失 this
setTimeout(function() {
console.log(this.name); // undefined 或 window.name
}, 1000);
// 箭头函数保留 this
setTimeout(() => console.log(this.name), 1000); // Developers
}
};
team.showMembers();
// 输出:
// Team: Developers
// Developers member: Alice
// Developers member: Bob
team.showLater(); // Developers(箭头函数)
总结
this的核心:由函数调用方式决定,遵循四种绑定规则(new > 显式 > 隐式 > 默认)。- 箭头函数:固定
this为外层作用域,适合回调,不适合动态this场景。