JavaScript 中的 this
this 是 JavaScript 中一个非常重要但容易混淆的关键字。它的值并不是在定义时确定的,而是在函数调用时动态绑定的,也就是我们常说的上下文。
1. 什么是 this?
this 是一个上下文相关的关键字,用于引用当前执行上下文中的对象。其具体值取决于函数的调用方式。this 是在运行时绑定的,而不是在编写时绑定的。它的指向取决于函数是如何被调用的,而不是函数是如何定义的。简单来说,this 总是指向调用该函数的对象。
2. this 的绑定规则
JavaScript 中 this 的绑定规则主要包括以下几种情况:
2.1 默认绑定
在全局执行上下文中(在任何函数体外部),this 始终指向全局对象。在浏览器中,全局对象是 window;在 Node.js 环境中,全局对象是 global。
function foo() {
console.log(this);
}
foo(); // 在浏览器中,输出 window
严格模式下,this 为 undefined。
'use strict';
function foo() {
console.log(this);
}
foo(); // undefined
2.2 隐式绑定
当函数作为对象的方法调用时,this 指向调用该方法的对象。
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
obj.greet(); // 输出 "Alice"
注意:如果将方法赋值给一个变量,隐式绑定会丢失,this 会回退到默认绑定规则。
const greet = obj.greet;
greet(); // 在非严格模式下,输出 undefined 或 "window" 的属性
2.3 显式绑定
可以使用 call、apply 和 bind 方法显式指定 this 的值。
call方法:调用函数并指定this,参数以逗号分隔。
function greet(greeting, punctuation) {
console.log(greeting + ' ' + this.name + punctuation);
}
const obj = { name: 'Alice' };
greet.call(obj, 'Hello', '!'); // 输出 "Hello Alice!"
apply方法:与call类似,但参数以数组形式传入。
greet.apply(obj, ['Hi', '.']); // 输出 "Hi Alice."
bind方法:返回一个绑定了指定this的新函数。
const boundGreet = greet.bind(obj, 'Hey');
boundGreet('!'); // 输出 "Hey Alice!"
2.4 new 绑定
使用 new 关键字调用函数时,该函数会作为构造函数执行。此时,this 指向新创建的对象实例。
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出 "Alice"
2.5 箭头函数绑定
箭头函数没有自己的 this,它会继承父作用域的 this。如果在全局作用域中定义箭头函数,那么它的 this 指向全局对象;如果在其他函数内部定义箭头函数,那么它的 this 指向包含它的函数(非箭头函数)的 this。
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name);
}
};
obj.greet(); // undefined,因为 this 指向定义时的外层作用域(通常是全局对象)
3. 特殊场景下的 this
3.1 在回调函数中
回调函数中的 this 指向取决于回调函数是如何被调用的。如果回调函数是作为对象的方法传递的,那么 this 可能不会像你期望的那样指向该对象。通常需要使用 bind、箭头函数或将 this 保存在一个变量中来解决这个问题。
const obj = {
name: 'Alice',
greet() {
setTimeout(function() {
console.log(this.name);
}, 1000);
}
};
obj.greet(); // undefined(普通函数的默认绑定)
// 使用箭头函数解决
const obj2 = {
name: 'Alice',
greet() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
};
obj2.greet(); // 输出 "Alice"
3.2 在事件处理程序中
在 DOM 事件处理程序中,this 默认指向触发事件的元素。
<button id="myButton">Click me</button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // 输出 true
});
</script>
如果使用箭头函数,this 将指向定义时的作用域。
document.getElementById('btn').addEventListener('click', () => {
console.log(this); // 通常是全局对象
});
3.3 在类中
在类方法中,this 默认指向实例对象,但需要注意丢失绑定的情况。
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(this.name);
}
}
const alice = new Person('Alice');
alice.greet(); // 输出 "Alice"
const greet = alice.greet;
greet(); // undefined,因为丢失了绑定
使用 bind 或箭头函数可以避免丢失绑定。
4. 常见面试题
以下是一些与 this 相关的常见面试题及解析:
4.1 全局作用域中的 this
console.log(this);
在浏览器中,输出什么?
解析:输出 window,因为在全局作用域中,this 指向全局对象。
4.2 函数调用中的 this
function foo() {
console.log(this);
}
foo();
在非严格模式下和严格模式下分别输出什么?
解析:
- 非严格模式下,输出
window。 - 严格模式下,输出
undefined。
4.3 对象方法中的 this
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
obj.greet();
输出是什么?
解析:输出 Alice,因为 this 指向调用方法的对象 obj。
4.4 this 丢失问题
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
const greet = obj.greet;
greet();
输出是什么?
解析:输出 undefined,因为方法赋值给变量后,this 指向全局对象(或 undefined 在严格模式下)。
4.5 箭头函数中的 this
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name);
}
};
obj.greet();
输出是什么?
解析:输出 undefined,因为箭头函数没有自己的 this,它继承自定义时的外层作用域(通常是全局对象)。
4.6 构造函数中的 this
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name);
输出是什么?
解析:输出 Alice,因为 this 被绑定到新创建的实例对象。
4.7 call 和 apply 的使用
function greet(greeting) {
console.log(greeting + ' ' + this.name);
}
const obj = { name: 'Alice' };
greet.call(obj, 'Hello');
greet.apply(obj, ['Hi']);
输出是什么?
解析:
- 输出
Hello Alice,因为call显式绑定了this。 - 输出
Hi Alice,因为apply的作用与call类似。
4.8 bind 的使用
const obj = { name: 'Alice' };
function greet() {
console.log(this.name);
}
const boundGreet = greet.bind(obj);
boundGreet();
输出是什么?
解析:输出 Alice,因为 bind 返回了一个绑定了 this 的新函数。