本节大纲
- this 的 概念描述
- this 的 5 种绑定规则
- 默认绑定(独立调用 / 全局环境)
- 隐式绑定(对象方法调用)
- 显式绑定(call / apply / bind)
- new 绑定(构造函数)
- 箭头函数(词法 this,定义时锁定)
- 绑定优先级与 new + bind 混合演示
- 隐式丢失的 4 种修复方案
- class 中的 this
- 判断 this 的「五步法」速查
- 经典练习题逐题分析
一、概念描述
this 是 JavaScript 中最容易让人困惑的关键字之一,它不是在「函数定义时」决定的,而是在「函数调用时」决定的。一句话记住:谁调用,this 就指向谁(箭头函数除外)
二、this 的 5 种绑定规则
2.1 默认绑定(独立调用 / 全局环境)
浏览器中:全局 this === window,严格模式下 this 是 undefined
js
function showThis() {
// 非严格模式 → window / global
// 严格模式 → undefined
console.log('独立调用 this =', this);
}
showThis();
2.2 隐式绑定(对象方法调用,最常见)
规则:谁调用方法,this 就指向谁(点号前面的对象)
js
'use strict';
const person = {
name: '小明',
sayHi: function () {
console.log('👋 我叫', this.name); // this → person
}
};
person.sayHi(); // 「person」在调用 → this 指向 person
// ⚠️ 陷阱:把方法赋值给变量后再调用,this 会丢失
const lostFn = person.sayHi;
try {
lostFn(); // 严格模式下 this 是 undefined,访问 .name 会报错
} catch (e) {
console.log('❌ 隐式丢失:', e.message);
}
2.3 显式绑定(call / apply / bind)
这三个方法可以「强行指定」this 的指向
call(thisArg, arg1, arg2, ...) 立即调用,参数逐个传
apply(thisArg, arg1, arg2) 立即调用,参数用数组传
bind(thisArg, arg1, ...) 不立即调用,返回一个新函数
js
function introduce(city, hobby) {
console.log(`我是 ${this.name},来自 ${city},喜欢 ${hobby}`);
}
const user = { name: '李四' };
introduce.call(user, '北京', '篮球'); // call:参数逐个传
introduce.apply(user, ['上海', '足球']); // apply:参数用数组
const boundFn = introduce.bind(user, '广州'); // bind:返回新函数
boundFn('游泳'); // 之后再调用
2.4 new 绑定(构造函数)
使用 new 调用时,this 指向「新创建的实例对象」
new 做了 4 件事:
① 创建一个空对象 {}
② 把 this 指向这个新对象
③ 执行函数体,给 this 添加属性
④ 返回 this(除非函数显式返回另一个对象)
js
function Person(name, age) {
this.name = name;
this.age = age;
}
const p1 = new Person('张三', 18);
console.log('new 出来的对象:', p1); // { name: '张三', age: 18 }
2.5 箭头函数(词法 this,定义时锁定)
🔑 箭头函数没有自己的 this!
它的 this 是「定义时」从外层作用域继承来的(词法 this)
call / apply / bind 也无法改变箭头函数的 this
js
const arrowObj = {
name: '王五',
// 普通方法:this → arrowObj
normalFn: function () {
console.log('普通函数 this.name =', this.name); // 王五
// 内部箭头函数:this 继承自 normalFn 的 this,仍是 arrowObj
const arrow = () => {
console.log(' └─ 箭头函数 this.name =', this.name); // 王五
};
arrow();
},
// ⚠️ 直接把方法写成箭头函数,this 不再指向 arrowObj!
arrowFn: () => {
console.log('对象上的箭头函数 this.name =', this && this.name); // undefined
}
};
arrowObj.normalFn();
arrowObj.arrowFn();
// call/apply/bind 都改不了箭头函数的 this
const arrowFn2 = () => console.log('箭头函数被 call 后 this =', this);
arrowFn2.call({ name: 'X' });
三、绑定优先级
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定(obj.fn) > 默认绑定(fn)
箭头函数不参与上面的规则,仅看「定义时外层的 this」
js
function priorityFn() {
console.log('this.name =', this.name);
}
const objA = { name: 'A', fn: priorityFn };
const objB = { name: 'B' };
objA.fn(); // 隐式绑定 → 'A'
objA.fn.call(objB); // 显式 > 隐式 → 'B'
// 演示 2:new > bind
function PFn(name) { this.name = name; }
const Bound = PFn.bind({ name: 'bind对象' });
const inst = new Bound('newName');
console.log('new > bind →', inst.name); // 'newName',new 优先级最高
四、隐式丢失的 4 种修复方案
js
const counter = {
count: 100,
// ❌ 错误:普通函数作回调,this 丢失
bad() {
setTimeout(function () {
console.log('❌ this.count =', this && this.count);
}, 50);
},
// ✅ 方案 1:箭头函数(推荐,最简洁)
fix1() {
setTimeout(() => console.log('✅ 箭头修复:', this.count), 100);
},
// ✅ 方案 2:bind 显式绑定
fix2() {
setTimeout(function () {
console.log('✅ bind 修复:', this.count);
}.bind(this), 150);
},
// ✅ 方案 3:用 that / self 暂存
fix3() {
const that = this;
setTimeout(function () {
console.log('✅ that 暂存:', that.count);
}, 200);
},
// ✅ 方案 4:用 setTimeout 第三参数把值传进去
fix4() {
setTimeout(function (c) {
console.log('✅ 参数传值:', c);
}, 250, this.count);
}
};
counter.bad();
counter.fix1();
counter.fix2();
counter.fix3();
counter.fix4();
五、class 中的 this
js
class Dog {
constructor(name) {
this.name = name; // this → 新创建的实例
}
// 普通方法:通过实例调用时,this 指向实例
bark() {
console.log(`🐶 ${this.name} 在汪汪叫`);
}
// ⚠️ 把方法当回调传出去,this 也会丢失
barkLater() {
setTimeout(this.bark, 500); // ❌ this 会变成全局/undefined
}
// ✅ 箭头函数定义为类字段,自动绑定 this
barkArrow = () => {
console.log(`🐶(箭头)${this.name} 在汪汪叫`);
}
}
const dog = new Dog('旺财');
dog.bark(); // ✅ this → dog
// dog.barkLater(); // ❌ this 丢失
setTimeout(dog.barkArrow, 600); // ✅ 类字段箭头函数永远绑定 this
六、判断 this 的「五步法」🪜
① 是箭头函数吗? → 是:看「定义时外层」的 this
② 用 new 调用了吗? → 是:this 是新实例
③ 用 call/apply/bind? → 是:this 是指定的对象
④ 是 obj.fn() 形式? → 是:this 是 obj(点号前面那个)
⑤ 上面都不是? → 默认绑定(window / undefined)
七、经典练习题逐题分析
js
// ---------- 题 1:隐式绑定,谁调用指向谁 ----------
const q1A = {
name: 'A',
fn() { return this.name; }
};
const q1B = { name: 'B', fn: q1A.fn };
console.log('Q1 q1B.fn() =', q1B.fn());
// 答案:'B'
// 分析:第④步 → 点号前面是 q1B,this 指向 q1B
// ---------- 题 2:箭头函数继承外层 this ----------
const q2 = {
name: '我',
get() {
return () => this.name; // 箭头继承 get 的 this
}
};
console.log('Q2 q2.get()() =', q2.get()());
// 答案:'我'
// 分析:q2.get() 调用时 this 是 q2,返回的箭头函数继承这个 this
// ---------- 题 3:方法当回调,隐式丢失 ----------
const q3 = {
name: 'C',
fn() { console.log('Q3 this.name =', this && this.name); }
};
setTimeout(q3.fn, 300);
// 答案:undefined(严格模式)/ 全局(非严格)
// 分析:q3.fn 被「拿出来」当回调 → 独立调用 → 默认绑定
// ---------- 题 4:new + bind 混合(new 优先级更高) ----------
function Q4(name) { this.name = name; }
const Q4Bound = Q4.bind({ name: 'BIND' });
const q4Inst = new Q4Bound('NEW');
console.log('Q4 q4Inst.name =', q4Inst.name);
// 答案:'NEW'
// 分析:new 绑定 > 显式绑定,new 时 bind 的对象被忽略
// ---------- 题 5:箭头无法被 call 改变 ----------
const q5Arrow = () => this;
console.log('Q5 q5Arrow.call({x:1}) === q5Arrow() →', q5Arrow.call({ x: 1 }) === q5Arrow());
// 答案:true
// 分析:箭头函数没有自己的 this,call/apply/bind 都改不了