上下文问题
什么是this?
this 是一个对象,它代表当前正在执行的函数所在的对象。
核心规则
this 的指向本质是:谁调用函数,this 就指向谁。先记住 5 条核心规则(优先级从高到低):
- 箭头函数:this 继承自「定义时的外层作用域」(无自己的 this);
- new 绑定:构造函数中 this 指向新创建的实例;
- 显式绑定:call/apply/bind 强制指定 this;
- 隐式绑定:函数作为对象方法调用,this 指向该对象;
- 默认绑定:独立调用(无上述绑定),非严格模式指向全局,严格模式指向 undefined。
隐式绑定:函数作为对象方法调用
函数通过「对象。方法」的形式调用,this 指向调用该方法的对象(即.前面的对象)。
警告
⚠️ 注意:仅绑定到「直接调用者」,而非整个原型链 / 嵌套对象。
js
// 基础示例
const person = {
name: '张三',
sayHi: function () {
console.log(this.name); // 张三(this 指向 person)
}
};
person.sayHi(); // 调用者是 person
// 方法赋值后独立调用(丢失上下文)
const sayHi = person.sayHi;
sayHi(); // undefined(this 指向 window,window.name 为空)
// 嵌套对象的调用者
const obj = {
a: 1,
inner: {
b: 2,
fn: function () {
console.log(this.a, this.b); // undefined 2(this 指向 inner)
}
}
};
obj.inner.fn();
// 嵌套函数的默认绑定
const obj = {
a: 10,
fn: function () {
function innerFn() {
console.log(this); // window(非严格模式)!!!
}
innerFn(); // 独立调用,而非 obj.innerFn()
}
};
obj.fn();显式绑定:call/apply/bind 强制指定 this
通过 call/apply/bind 手动指定 this,优先级高于隐式绑定。
- call/apply:立即执行函数,第一个参数是 this,区别是参数传递方式(call 传参数列表,apply 传数组);
- bind:返回一个新函数,永久绑定 this(不可被覆盖),需手动调用。
js
const obj1 = {a: 1};
const obj2 = {a: 2};
function fn(x, y) {
console.log(this.a, x + y);
}
fn(10, 20); // undefined 30
// call:显式绑定 this 到 obj1,参数列表传递
fn.call(obj1, 10, 20); // 1 30
// apply:显式绑定 this 到 obj2,数组传递参数
fn.apply(obj2, [10, 20]); // 2 30
// bind:返回新函数,绑定 this 到 obj1(永久)
const bindFn = fn.bind(obj1);
bindFn(10, 20); // 1 30
bindFn.call(obj2, 10, 20); // 1 30(bind 绑定不可被 call 覆盖)
// 特殊:绑定 null/undefined 时,会触发默认绑定,绑定this 到 window
fn.call(null); // undefined NaNnew 绑定:构造函数中的 this
new 绑定,构造函数中 this 指向新创建的实例,优先级高于显式绑定(除 bind 外)。
js
function Person(name) {
this.name = name; // this 指向新实例
console.log(this); // Person { name: '李四' }
}
const p = new Person('李四');
console.log(p.name); // 李四
// 对比:普通调用 vs new 调用
Person('王五'); // 无 new,this 指向 window,window.name = '王五'
console.log(window.name); // 王五箭头函数:无自己的 this,继承外层作用域
箭头函数没有自己的 this,this 继承自外层作用域。
js
// 示例1:箭头函数继承外层 this
const obj = {
a: 10,
fn: function () {
// 普通函数 this 指向 obj
const inner = () => {
console.log(this.a); // 10(继承 fn 的 this)
};
inner();
},
// 坑点:对象方法用箭头函数,this 继承全局
arrowFn: () => {
console.log(this.a); // undefined(this 指向 window)
}
};
obj.fn(); // 10
obj.arrowFn(); // undefined
// 示例2:箭头函数无法被 bind 修改
const arrow = () => console.log(this);
arrow.bind({a: 1})(); // window(无效果)
// 示例3:回调函数中箭头函数解决 this 丢失
setTimeout(function () {
console.log(this); // window(默认绑定)
}, 0);
setTimeout(() => {
console.log(this); // 继承外层(若外层是全局,仍为 window;若外层是函数,继承其 this)
}, 0);练习
练习1:基础 - 普通函数/对象方法的this(入门)
题目
分析以下代码的输出结果,并解释原因:
js
const obj = {
name: '小明',
sayName: function () {
console.log(this.name);
}
};
// 场景1
obj.sayName();
// 场景2
const fn = obj.sayName;
fn();
// 场景3
const obj2 = {name: '小红', sayName: obj.sayName};
obj2.sayName();解答
- 场景1输出:
小明原因:sayName作为obj的方法调用,this指向调用者obj,因此this.name取obj.name。 - 场景2输出:
undefined原因:fn是obj.sayName的引用,执行fn()是独立调用(无上下文),触发默认绑定:非严格模式下this指向window,而window.name未定义,因此输出undefined。 - 场景3输出:
小红原因:sayName作为obj2的方法调用,this指向直接调用者obj2,因此this.name取obj2.name。
练习2:进阶 - 箭头函数/嵌套函数的this(中级)
题目
分析以下代码的输出结果,并解释原因:
js
const obj = {
a: 10,
fn1: function () {
const inner = function () {
console.log(this.a);
};
inner();
},
fn2: function () {
const inner = () => {
console.log(this.a);
};
inner();
},
fn3: () => {
console.log(this.a);
}
};
// 场景1
obj.fn1();
// 场景2
obj.fn2();
// 场景3
obj.fn3();解答
- 场景1输出:
undefined原因:inner是普通函数,执行inner()是独立调用,触发默认绑定,this指向window,window.a未定义,因此输出undefined。 - 场景2输出:
10原因:inner是箭头函数,无自己的this,继承外层函数fn2的this;fn2作为obj的方法调用,this指向obj,因此inner的this也指向obj,this.a为10。 - 场景3输出:
undefined原因:fn3是箭头函数,定义在全局作用域下的对象中,其this继承外层(全局)的this(即window),window.a未定义,因此输出undefined。
练习3:高阶 - 显式绑定/new绑定/优先级(高级)
题目
分析以下代码的输出结果,并解释原因:
js
function Person(name) {
this.name = name;
this.sayName = function () {
console.log(this.name);
};
}
const obj1 = {name: '张三'};
const obj2 = {name: '李四'};
// 实例化构造函数
const p = new Person('王五');
// 场景1
p.sayName.call(obj1);
// 场景2
const bindFn = p.sayName.bind(obj2);
bindFn.call(obj1);
// 场景3
const newFn = new p.sayName();
console.log(newFn.name);解答
- 场景1输出:
张三原因:sayName是普通函数,通过call显式绑定this到obj1,优先级高于隐式绑定(p.sayName的隐式绑定指向p),因此this.name取obj1.name。 - 场景2输出:
李四原因:bind会永久绑定this,且优先级高于call/apply;bindFn已绑定this到obj2,后续call(obj1)无法覆盖,因此输出李四。 - 场景3输出:
undefined原因:new p.sayName()是用new调用sayName函数(而非Person),new绑定优先级最高:此时this指向sayName的新实例,而非p或obj1/obj2;sayName函数内部仅打印this.name,未给新实例赋值name,因此newFn.name为undefined。
练习4:综合 - DOM/定时器/类的this(综合)
题目
补全以下代码,实现:点击按钮后,1秒后控制台输出 「我是[姓名],年龄[年龄]」(姓名/年龄取自 Person 实例),要求正确处理 this 指向。
js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
showInfo() {
// 补全逻辑:1秒后输出「我是[姓名],年龄[年龄]」
}
}
const p = new Person('小张', 20);
const btn = document.getElementById('btn');
btn.onclick = p.showInfo;解答
js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
// 方案1:提前绑定this(推荐)
this.showInfo = this.showInfo.bind(this);
}
showInfo() {
// 定时器回调用箭头函数,继承外层this(Person实例)
setTimeout(() => {
console.log(`我是${this.name},年龄${this.age}`);
}, 1000);
}
}
const p = new Person('小张', 20);
const btn = document.getElementById('btn');
btn.onclick = p.showInfo;解释
- 问题核心:
btn.onclick = p.showInfo会导致showInfo作为 DOM 事件处理函数调用,this默认指向btn,而非Person实例;- 定时器的普通函数回调会导致
this指向window,丢失实例上下文。
- 解决方案:
- 在
constructor中用bind永久绑定showInfo的this到Person实例,确保事件触发时this仍指向实例; - 定时器回调用箭头函数,继承
showInfo的this(已绑定为实例),避免this丢失。
- 在
其他可行方案(不修改类,修改事件绑定):
js
// 方案2:事件绑定时用箭头函数
btn.onclick = () => {
p.showInfo();
};
// 方案3:事件绑定用bind
btn.onclick = p.showInfo.bind(p);总结
- 函数调用和赋值调用,this都指向window
- 对象调用,this指向对象
- new调用,this指向新创建的对象