JavaScript中的this关键字
本文讲解 JavaScript 中
this的指向规则。this的值取决于函数的调用方式,而非定义位置。主要涵盖默认、隐式、显式、new绑定和箭头函数五种情况及其优先级。
我们来系统地梳理一下 JavaScript 中 this 的指向规则。这是 JavaScript 中一个非常重要但又容易混淆的概念。
核心原则:this 的值取决于函数被调用的方式(the call-site),而不是函数被定义的地方。
我们可以将 this 的指向规则归纳为以下五种情况,并按优先级从低到高排列。
1. 默认绑定 (Default Binding)
当一个函数作为普通函数独立调用,没有被任何对象拥有,也没有通过 call, apply, bind 指定时,就会使用默认绑定。
- 非严格模式 (Non-Strict Mode):
this指向全局对象(在浏览器中是window,在 Node.js 中是global)。 - 严格模式 (Strict Mode):
this的值为undefined。
示例:
function sayHi() {
console.log(this);
}
sayHi(); // 非严格模式下,输出 window 对象
// 严格模式下 ('use strict'),输出 undefined
常见陷阱: 将对象的方法赋值给一个变量后独立调用。
const person = {
name: 'Alice',
sayName: function() {
console.log(this.name);
}
};
const greet = person.sayName;
greet(); // 非严格模式下,this 指向 window,输出 undefined (因为 window.name 通常为空)
// 严格模式下,this 是 undefined,会报错:Cannot read property 'name' of undefined
在这个例子中,greet() 调用时,它是一个独立的函数调用,丢失了与 person 对象的关联,因此应用了默认绑定规则。
2. 隐式绑定 (Implicit Binding)
当函数作为对象的一个方法被调用时,this 指向这个对象。简单来说,就是看调用函数时,点 (.) 前面是谁,this 就是谁。
示例:
const person = {
name: 'Bob',
sayName: function() {
console.log(this.name); // this 指向 person 对象
}
};
person.sayName(); // 输出 "Bob"
链式调用中的隐式绑定:
const obj = {
a: 1,
b: {
a: 2,
fn: function() {
console.log(this.a);
}
}
};
obj.b.fn(); // 输出 2
在 obj.b.fn() 调用中,fn 是被 obj.b 调用的,所以 this 指向 obj.b。
3. 显式绑定 (Explicit Binding)
通过 call(), apply(), bind() 方法,我们可以强制指定函数内部 this 的指向。
function.call(thisArg, arg1, arg2, ...)- 立即调用函数。
- 第一个参数
thisArg就是this的指向。 - 后面的参数逐个传递给函数。
function.apply(thisArg, [argsArray])- 立即调用函数。
- 第一个参数
thisArg是this的指向。 - 第二个参数是一个数组,数组中的元素会作为参数传递给函数。
function.bind(thisArg, arg1, ...)- 不会立即调用函数,而是返回一个新函数。
- 这个新函数的
this被永久地绑定到了thisArg。 bind也可以预先设置部分参数。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person1 = { name: 'Charlie' };
const person2 = { name: 'David' };
// 使用 call
greet.call(person1, 'Hello', '!'); // 输出 "Hello, Charlie!"
// 使用 apply
greet.apply(person2, ['Hi', '.']); // 输出 "Hi, David."
// 使用 bind
const greetCharlie = greet.bind(person1, 'Good morning');
greetCharlie('?'); // 输出 "Good morning, Charlie?"
4. new 绑定 (new Binding)
当一个函数使用 new 关键字来调用时,这个函数被称为构造函数。new 绑定会改变 this 的指向。
使用 new 调用函数时,会自动执行以下步骤:
- 创建一个全新的空对象。
- 这个新对象的
[[Prototype]](即__proto__)被链接到构造函数的prototype对象。 - 这个新对象被绑定为函数调用的
this。 - 如果函数没有显式返回一个对象,那么
new表达式会自动返回这个新创建的对象(this)。
示例:
function Person(name) {
// this 被 new 绑定到了一个新创建的空对象上
this.name = name;
this.age = 30;
// 隐式返回 this
}
const eve = new Person('Eve');
console.log(eve.name); // 输出 "Eve"
console.log(eve.age); // 输出 30
在这种情况下,this 指向由 new 创建的实例对象 eve。
5. 箭头函数 (Arrow Functions)
箭头函数是 ES6 中引入的一个重要特性,它彻底改变了 this 的规则。
- 箭头函数没有自己的
this。 - 它会捕获其定义时所在上下文(词法作用域)的
this值作为自己的this。 - 一旦绑定,
this的值就不会再改变。call,apply,bind对箭头函数无效。
这使得箭头函数在处理回调函数时非常有用。
示例对比:
// 传统函数的问题
const student = {
name: 'Frank',
courses: ['Math', 'Science'],
printCourses: function() {
setTimeout(function() {
// 这里的 this 指向 window (默认绑定),因为 setTimeout 的回调是独立调用的
console.log(this.name + ' is taking ' + this.courses); // 导致错误
}, 1000);
}
};
// student.printCourses(); // 非严格模式下会输出 " is taking undefined"
// 使用箭头函数解决
const student_arrow = {
name: 'Frank',
courses: ['Math', 'Science'],
printCourses: function() {
// 箭头函数定义时,外层作用域是 printCourses 函数
// printCourses 的 this 指向 student_arrow (隐式绑定)
// 所以箭头函数捕获的 this 就是 student_arrow
setTimeout(() => {
console.log(this.name + ' is taking ' + this.courses);
}, 1000);
}
};
student_arrow.printCourses(); // 1秒后输出 "Frank is taking Math,Science"
优先级总结
当多种规则同时适用时,this 指向的优先级如下:
new 绑定 > 显式绑定 (bind) > 隐式绑定 > 默认绑定
箭头函数的 this 规则是独立的,它不遵循上述优先级链,因为它根本没有自己的this。如果一个 this 已经被箭头函数捕获,那么任何方式都无法再改变它。
总结表格
| 调用方式 | this 指向 |
示例 |
|---|---|---|
| 普通函数调用 | 全局对象 (window) 或 undefined (严格模式) |
myFunction() |
| 对象方法调用 | 调用该方法的对象 (点前面的对象) | obj.myMethod() |
call/apply/bind |
显式指定的第一个参数 | myFunc.call(someObj) |
new 构造函数 |
新创建的实例对象 | new MyConstructor() |
| 箭头函数 | 定义时所在词法作用域的 this |
const fn = () => { console.log(this) } |
记住这个优先级和这五种场景,你就能准确判断绝大多数情况下 this 的指向了。