JavaScript 中 this 的绑定主要分为 5 种情况。
核心原则:普通函数的 this 通常由调用方式决定,而不是定义位置。箭头函数除外。
一、默认绑定
普通函数独立调用时,使用默认绑定。
scss
function foo() {
console.log(this);
}
foo();
- 非严格模式:
this指向globalThis(浏览器中通常是window)。 - 严格模式:
this为undefined。
二、隐式绑定
函数作为对象的方法调用时,this 指向调用该方法的对象。
javascript
const obj = {
name: "Tom",
sayHello() {
console.log(this.name);
}
};
obj.sayHello(); // Tom
注意,隐式绑定可能丢失:
ini
const fn = obj.sayHello;
fn(); // this 不再指向 obj
因为 fn() 是独立调用,不再通过 obj 调用。
① 隐式绑定不仅适用于普通对象,也适用于数组。
② 把对象的方法赋值给变量,会丢失原来的隐式绑定。
js
var length = 1;
function fun() {
console.log(this.length);
}
let arr = [fun, "a", "b"];
arr[0](); // 输出什么?3
let fun2 = arr[0];
fun2(); // 输出什么?1
补充:函数作为参数传递时的 this 绑定
函数作为参数传递时,只传递函数本身,不会保留原来的隐式绑定。
scss
const obj = {
name: "Tom",
sayName() {
console.log(this.name);
}
};
function execute(fn) {
fn();
}
execute(obj.sayName); // 默认绑定,不再指向 obj
原因:execute 内部通过 fn() 独立调用函数,而不是通过 obj 调用。
可以通过 bind 保留原来的 this:
scss
execute(obj.sayName.bind(obj)); // Tom
核心记忆:判断普通函数的 this,看函数实际如何调用,而不是函数从哪里传过来。
三、显式绑定
通过 call、apply、bind 显式指定 this。
scss
function foo() {
console.log(this.name);
}
const obj = { name: "Tom" };
foo.call(obj); // Tom
foo.apply(obj); // Tom
const fn = foo.bind(obj);
fn(); // Tom
区别:
| 方法 | 是否立即执行 | 参数传递 |
|---|---|---|
call |
是 | 逐个传参 |
apply |
是 | 数组传参 |
bind |
否 | 逐个传参,返回新函数 |
四、new 绑定
通过 new 调用构造函数时,this 指向新创建的实例对象。
ini
function Person(name) {
this.name = name;
}
const p = new Person("Tom");
console.log(p.name); // Tom
结合之前学习的 new 执行过程:
- 创建新对象。
- 将新对象的原型关联到构造函数的
prototype。 - 以新对象作为
this执行构造函数。 - 根据构造函数返回值决定最终返回的对象。
五、箭头函数绑定
箭头函数没有自己的 this,而是继承外层词法环境中的 this。
ini
const obj = {
name: "Tom",
sayHello() {
const fn = () => {
console.log(this.name);
};
fn();
}
};
obj.sayHello(); // Tom
这里:
sayHello()是普通方法,this指向obj。fn是箭头函数,捕获外层sayHello的this。- 因此输出
Tom。
箭头函数的 this 无法通过 call、apply、bind 修改。
六、this 绑定的优先级
面试中经常考察多个绑定规则同时存在的情况。
对于普通函数,可以按照下面的优先级判断:
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
例如:
ini
function Person(name) {
this.name = name;
}
const obj = { name: "Jack" };
const BoundPerson = Person.bind(obj);
const p = new BoundPerson("Tom");
console.log(p.name); // Tom
console.log(obj.name); // Jack
即使通过 bind 绑定了 obj,使用 new 时依然会创建新实例,并将 this 指向新实例。
注意:箭头函数不参与上述优先级比较。
七、this 与词法作用域的区别
你刚刚学过词法作用域,这两个概念可以对比理解。
javascript
const name = "Global";
const obj = {
name: "Tom",
foo() {
console.log(name);
console.log(this.name);
}
};
obj.foo();
// Global
// Tom
这里:
name:按照作用域链 查找,找到外层的name。this.name:先确定this指向obj,再读取obj.name。
一句话总结:
- 作用域链:变量在哪里定义,决定去哪里查找。
- this 绑定 :普通函数怎么调用,决定
this指向谁。
这是理解 JavaScript 函数执行机制的两个重要维度。