this 是什么
JavaScript 的 this 是一个让很多人困惑的概念。但它的本质其实很简单:this 是函数被调用时的执行上下文对象。
理解 this 的关键是:不要问"什么是 this",而要问"this 在哪里被绑定的"。this 的值取决于函数被调用的方式,而不是函数定义的位置。
JavaScript 有四种 this 绑定的场景。
场景一:默认绑定
当函数独立调用,不带任何修饰时,this 指向全局对象(在浏览器里是 window,在 Node.js 里是 global)。
function foo() {
console.log(this); // 在浏览器里是 window
}
foo(); // 默认绑定
但注意,严格模式下('use strict'),全局对象不是 this 的默认值,而是 undefined:
'use strict';
function foo() {
console.log(this); // undefined
}
foo();
这是为什么在严格模式下,如果 this 是 undefined 或 window 不符合预期,代码可能会报错。
场景二:隐式绑定
当函数作为对象的属性被调用时,this 指向这个对象。
const obj = {
name: 'obj',
sayName() {
console.log(this.name);
}
};
obj.sayName(); // 输出 'obj',因为 this 指向 obj
关键是看函数被调用时"前面有没有点"。如果有,这个点前面的对象就是 this 的值。
但有一个常见的陷阱:
const obj = {
name: 'obj',
sayName() {
console.log(this.name);
}
};
const sayName = obj.sayName; // 把函数提取出来
sayName(); // 输出 undefined(严格模式)或报错(非严格模式下 this 是 window)
sayName 被提取出来赋值给全局变量后,调用 sayName() 时前面没有点,所以 this 不再指向 obj,而是指向全局对象。
场景三:显式绑定
JavaScript 提供三种方法可以显式绑定 this:call、apply、bind。
call 和 apply:
const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };
function introduce(greeting) {
console.log(greeting + ', I am ' + this.name);
}
introduce.call(obj1, 'Hello'); // 输出 'Hello, I am obj1'
introduce.apply(obj2, ['Hi']); // 输出 'Hi, I am obj2'
两者的区别是参数格式:call 接受逗号分隔的参数列表,apply 接受一个参数数组。
bind:
bind 创建一个新函数,this 被永久绑定到指定值:
const obj = { name: 'obj' };
function introduce(greeting) {
console.log(greeting + ', I am ' + this.name);
}
const boundIntroduce = introduce.bind(obj);
boundIntroduce('Hello'); // 输出 'Hello, I am obj'
boundIntroduce('Hi'); // 输出 'Hi, I am obj'
一旦用 bind 绑定了 this,无论怎么调用都会使用绑定的值。这是创建"预设 this"函数的常用方式。
场景四:new 绑定
当用 new 关键字调用构造函数时,this 指向新创建的实例对象。
function Person(name) {
this.name = name;
}
const person = new Person('Tom');
console.log(person.name); // 'Tom'
new 操作符做了四件事:
- 创建一个新对象
- 把构造函数的
this绑定到新对象 - 执行构造函数(给新对象添加属性)
- 返回新对象
所以在构造函数里,this 指向新创建的实例。
箭头函数的 this
箭头函数不遵循这四种绑定规则。箭头函数的 this 继承自定义时所在的词法环境。
const obj = {
name: 'obj',
// 普通函数,作为对象的方法
sayName() {
console.log(this.name);
},
// 箭头函数
sayNameArrow: () => {
console.log(this.name); // this 继承外层的全局 this
}
};
obj.sayName(); // 输出 'obj'
obj.sayNameArrow(); // 输出 undefined(严格模式)或 window.name(非严格模式)
因为箭头函数的 this 是词法绑定的,它不能被 call、apply、bind 重新绑定。
const obj = { name: 'obj' };
const arrowFn = () => {
console.log(this.name);
};
arrowFn.call(obj); // 仍然输出 undefined,不受 call 影响
这让箭头函数在某些场景下非常方便(如回调函数里需要保持外层的 this),但在另一些场景下会导致问题(如对象方法)。
常见问题和面试题
问题一:setTimeout 里的 this
const obj = {
name: 'obj',
delayedName() {
setTimeout(function() {
console.log(this.name); // 输出 undefined 或 window.name
}, 100);
}
};
setTimeout 回调函数是独立调用的(前面没有点),所以 this 指向全局对象,而不是 obj。
解决方式:
// 方式一:保存 this 引用
delayedName() {
const self = this;
setTimeout(function() {
console.log(self.name);
}, 100);
}
// 方式二:使用箭头函数
delayedName() {
setTimeout(() => {
console.log(this.name); // 箭头函数的 this 继承 delayedName 的 this
}, 100);
}
// 方式三:使用 bind
delayedName() {
setTimeout(function() {
console.log(this.name);
}.bind(this), 100);
}
问题二:类方法里的 this
在 class 语法里,方法的 this 也是动态绑定的:
class Button {
constructor() {
this.text = 'Click me';
}
handleClick() {
console.log(this.text);
}
}
const button = new Button();
const handler = button.handleClick;
handler(); // TypeError!因为 handler 调用时 this 是 undefined
常见解决方式是在构造函数里用 bind 绑定,或者在调用时用箭头函数:
// 方式一:bind
this.handleClick = this.handleClick.bind(this);
// 方式二:箭头函数定义
handleClick = () => {
console.log(this.text);
};
// 方式三:调用时用箭头函数
<button onClick={() => this.handleClick()}>Click</button>
this 的判定优先级
如果一个调用同时满足多条规则,按优先级从高到低是:
new绑定(new Foo())- 显式绑定(
call、apply、bind) - 隐式绑定(
obj.method()) - 默认绑定(独立调用)
function foo() {
console.log(this);
}
const obj = { foo };
obj.foo(); // 隐式绑定,this = obj
obj.foo.call(obj2); // 显式绑定优先级高于隐式,this = obj2
new obj.foo(); // new 绑定优先级最高,this = 新创建的对象
这一章想说的
this 的值取决于函数被调用的方式,不取决于定义位置。四种绑定规则决定了 this 的值:默认绑定(独立调用)、隐式绑定(对象方法调用)、显式绑定(call/apply/bind)、new 绑定。
箭头函数的 this 是词法绑定的,继承定义时的外层 this,不能被显式方法重新绑定。
理解 this 的关键是分析函数调用时的上下文,而不是盯着函数定义看。