
JavaScript 中的 this 关键字:前端开发者必懂的作用域指针指南
- JavaScript 中的 this 关键字:前端开发者必懂的作用域指针指南
- 引言:为什么 this 总让人又爱又恨
- 揭开 this 的神秘面纱:它到底是谁?
- this 绑定规则全解析:默认、隐式、显式与 new 绑定
- 箭头函数中的 this:特立独行的例外派
- 严格模式对 this 的影响:别再被 undefined 吓到
- 常见 this 陷阱实战复盘:那些年我们踩过的坑
- 调试 this 值的实用技巧:console.log 不是唯一答案
- this 在事件处理、回调和类方法中的真实表现
- 如何优雅地控制 this:bind、call、apply 的妙用场景
- 框架中的 this 行为差异:React、Vue 里你需要注意什么
- this 相关面试高频题拆解:不只是背规则,更要讲清楚逻辑
- 让 this 为你所用:写出更清晰、可维护的代码习惯
- 彩蛋:一个“this 安全”的通用 throttle 实现
- 收个尾
JavaScript 中的 this 关键字:前端开发者必懂的作用域指针指南
“this 就像一只猫,你以为你抱住了它,下一秒它就蹿到窗帘顶上,还顺带打翻你的水杯。”
——某位被 this 折磨到凌晨三点的匿名前端
引言:为什么 this 总让人又爱又恨
第一次被 this 坑,是在一个风平浪静的周五傍晚。
我兴高采烈地写下 setTimeout(this.tick, 1000),结果控制台无情地抛出 undefined is not a function。那一刻,我深刻体会到:
this 不是指向“我写代码的地方”,而是指向“函数被调用时的那个家伙”。
于是,我痛定思痛,把 this 的脾气秉性从头到尾撸了一遍。今天,我把这段血泪史揉进一篇长文,附带足量代码、注释、吐槽与彩蛋,陪你一起把这只猫驯成乖巧的布偶。
揭开 this 的神秘面纱:它到底是谁?
先放一句人话:this 就是“当前执行上下文”的一个动态指针。
它既不是函数本身,也不是函数所在的词法作用域,而是**“函数被调用时,前面有没有对象”**的晴雨表。
// 例 1:单身狗模式(默认绑定)
function whoAmI() {
console.log(this === window); // 浏览器里 true;Node 里换成 globalThis
}
whoAmI(); // 没人叫我,我只能抱 window 的大腿
// 例 2:有对象模式(隐式绑定)
const girl = {
name: '小美',
callMe() {
console.log(`我是${this.name}`);
}
};
girl.callMe(); // 我是小美 → this 指向 girl
// 例 3:被拐跑模式(隐式丢失)
const stolen = girl.callMe;
stolen(); // 我是undefined → 前面没对象,默认绑定 window
看完这三段,先别急着背规则,记住一句话:“this 不看定义时,只看调用时。”
this 绑定规则全解析:默认、隐式、显式与 new 绑定
下面四条规则,面试答卷里出现频率堪比“手写 Promise”,但实战里它们更像四把瑞士军刀——用对了切水果,用错了切手指。
1. 默认绑定:没人要我,我就 window
function defaultTest() {
'use strict'; // 开启严格模式后,this 会变成 undefined
console.log(this);
}
defaultTest(); // 非严格:window;严格:undefined
2. 隐式绑定:点前面是谁,我就指向谁
const obj = {
num: 42,
getNum() {
return this.num;
}
};
console.log(obj.getNum()); // 42
3. 显式绑定:call、apply、bind 强行指婚
const alice = { name: 'Alice' };
const bob = { name: 'Bob' };
function intro(age, city) {
console.log(`${this.name}, ${age}, ${city}`);
}
// call:逗号罗列参数
intro.call(alice, 18, 'Hangzhou'); // Alice, 18, Hangzhou
// apply:数组打包参数
intro.apply(bob, [20, 'Shenzhen']); // Bob, 20, Shenzhen
// bind:返回永久绑定的新函数
const aliceForever = intro.bind(alice);
aliceForever(22, 'Shanghai'); // Alice, 22, Shanghai
4. new 绑定:生娃优先级别最高
function Dog(name) {
// 底层伪代码:this = Object.create(Dog.prototype)
this.name = name;
// 底层伪代码:return this
}
const teddy = new Dog('Teddy');
console.log(teddy.name); // Teddy
优先级口诀:new > 显式 > 隐式 > 默认。
背不会?抄十遍,再写三个 demo,肌肉记忆就上来了。
箭头函数中的 this:特立独行的例外派
箭头函数天生“没 this”,它里面的 this 跟外层词法作用域共穿一条裤子。
说人话:箭头函数的 this 在定义时就拍板,往后余生都不会变心。
function Timer() {
this.seconds = 0;
// 版本 1:普通函数,this 跑丢
setInterval(function () {
this.seconds++; // 悲剧:this 指向 window
console.log(this.seconds); // NaN
}, 1000);
// 版本 2:箭头函数,this 锁定外层
setInterval(() => {
this.seconds++; // 安心:this 就是 Timer 实例
console.log(this.seconds); // 1 2 3 4...
}, 1000);
}
new Timer();
再补一刀:箭头函数不能用 call、apply、bind 改 this,它们只能改寂寞。
const arr = () => console.log(this);
arr.call({ name: 'fake' }); // 依旧打印外层 this,改不动
严格模式对 this 的影响:别再被 undefined 吓到
'use strict' 就像给代码加了安检门:
- 默认绑定不再甩锅给 window,而是堂堂正正给你 undefined。
- 减少奇奇怪怪的隐式 bug,也让引擎优化空间更大。
function sloppy() {
console.log(this); // window
}
function strict() {
'use strict';
console.log(this); // undefined
}
sloppy();
strict();
实战建议:项目一律开启严格模式,ESModule 默认就是严格,无需手写。
老项目搭 ESLint,规则里加 "strict": ["error", "global"],一步到位。
常见 this 陷阱实战复盘:那些年我们踩过的坑
坑 1:方法回调 = 隐式丢失
class Counter {
count = 0;
inc() {
this.count++;
}
register() {
// 错误示范:把方法当回调直接传
setTimeout(this.inc, 0); // inc 丢失 this
console.log(this.count); // 0
}
}
new Counter().register();
修复三件套:
- 箭头函数
- bind 硬绑定
- 类字段 + 箭头函数(最香)
// 方案 1:bind 硬绑定
setTimeout(this.inc.bind(this), 0);
// 方案 2:类字段箭头函数(提案语法,Babel/TS 都支持)
class Counter {
count = 0;
inc = () => this.count++;
}
坑 2:forEach 里 this 不翼而飞
const box = {
arr: [1, 2, 3],
sum: 0,
calc() {
this.arr.forEach(function (v) {
this.sum += v; // 报错:this 是 window
});
}
};
修复:forEach 第二参数传 this
this.arr.forEach(function (v) {
this.sum += v;
}, this); // 第二参数把 this 塞进去
坑 3:解构赋值 = 隐式丢失 plus
const { getNum } = obj;
console.log(getNum()); // 报错:this 丢失
修复:要么 bind,要么别解构。
调试 this 值的实用技巧:console.log 不是唯一答案
-
** debugger 大法**
在函数第一行写debugger;,Chrome 断点面板里直接看this指向谁,比 console 肉眼靠谱。 -
console.trace 追调用栈
一眼看出函数被谁调用,this 指向就水落石出。 -
VSCode + TS 类型提示
给函数写 JSDoc:
/**
* @this {{name:string}}
*/
function show() {
console.log(this.name);
}
鼠标 hover 立即看到 this 类型,妈妈再也不用担心我瞎猜。
- 单元测试快照
把 this 值序列化进快照,回归测试自动 diff,坑一次永绝后患。
this 在事件处理、回调和类方法中的真实表现
1. DOM 事件处理
button.addEventListener('click', function (e) {
console.log(this === e.currentTarget); // true
});
注意:如果用了箭头函数,this 就指向定义时的上下文(大概率是 window),基本没人想这么干。
2. Promise 回调
const api = {
token: 'abc',
fetch() {
return fetch('/api')
.then(function (res) {
console.log(this.token); // undefined
});
}
};
修复:箭头函数或在外层缓存 this。
fetch()
.then((res) => console.log(this.token)); // abc
3. React class 组件
class Input extends React.Component {
state = { val: '' };
// 不绑定,this 是 undefined(因为 React 调用时前面没对象)
handleChange(e) {
this.setState({ val: e.target.value });
}
render() {
return <input onChange={this.handleChange} />;
}
}
React 官方三选一:
- 构造函数里 bind
- 类字段箭头函数
<input onChange={(e) => this.handleChange(e)} />内联箭头(性能差一点点,但写 demo 爽)
如何优雅地控制 this:bind、call、apply 的妙用场景
场景 1:一次性借用方法
const fakePush = Array.prototype.push;
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
fakePush.call(arrayLike, 'c'); // 长度变 3,arrayLike[2] === 'c'
场景 2:科里化函数
function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2); // 永久固化 a=2
console.log(double(5)); // 10
场景 3:异步队列保持上下文
class Worker {
do(task) {
setTimeout(task.bind(this), 100); // 保证 task 里 this 是 Worker
}
}
框架中的 this 行为差异:React、Vue 里你需要注意什么
React 函数组件 + Hook
函数组件根本没有 this,官方逼你告别 this 绑架,拥抱闭包。
但闭包也有“陈旧值”坑,别高兴太早:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 如果 count 是 0,永远 +1
}, 1000);
return () => clearInterval(id);
}, []); // 依赖写 [],导致 count 闭包在 0
}
修复:要么加依赖,要么写 setCount(c => c + 1)。
Vue 2.x 选项式 API
methods 里的 this 自动指向组件实例,因为 Vue 内部帮你 bind 了。
但别写成箭头函数,不然 this 就跑到外层去了。
methods: {
// 正确
normal() { console.log(this.msg); },
// 错误
arrow: () => console.log(this.msg) // undefined
}
Vue 3 组合式 API
setup() 里也没有 this,官方推荐用 getCurrentInstance 拿组件上下文,但普通场景基本用不到。
this 相关面试高频题拆解:不只是背规则,更要讲清楚逻辑
题 1:下面输出多少?为什么?
const obj = {
value: 1,
get() { return this.value; },
getArrow: () => this.value
};
console.log(obj.get()); // ?
console.log(obj.getArrow()); // ?
const fn = obj.get;
console.log(fn()); // ?
答案 & 解释
obj.get()→ 1(隐式绑定,this 是 obj)obj.getArrow()→ undefined(箭头函数 this 取外层,通常是 window,window 没有 value)fn()→ undefined(隐式丢失,默认绑定,非严格模式 window,严格模式 undefined)
题 2:手写一个 Function.prototype.bind polyfill
if (!Function.prototype.bind) {
Function.prototype.bind = function (context, ...presetArgs) {
const fn = this; // 调用 bind 的函数
if (typeof fn !== 'function') throw new TypeError();
function boundFn(...laterArgs) {
// new 绑定优先级更高
const isNew = this instanceof boundFn;
const ctx = isNew ? this : context;
return fn.apply(ctx, [...presetArgs, ...laterArgs]);
}
// 维护原型链
boundFn.prototype = Object.create(fn.prototype);
return boundFn;
};
}
题 3:React 组件输出什么?
class App extends React.Component {
state = { n: 0 };
inc() { this.setState({ n: this.state.n + 1 }); }
render() {
return <button onClick={this.inc}>{this.state.n}</button>;
}
}
答案:点击按钮报错 Cannot read property 'setState' of undefined。
修复:构造函数加 this.inc = this.inc.bind(this) 或改成类字段箭头函数。
让 this 为你所用:写出更清晰、可维护的代码习惯
-
默认用箭头函数,必要时 bind
在 class 组件、定时器、Promise 链等场景,优先类字段箭头函数,省去 bind 的心智负担。 -
解构赋值前先想清楚 this
const { method } = obj; // 丢 this
const method = obj.method.bind(obj); // 保留 this
-
团队协作写 lint 规则
ESLint +@typescript-eslint/no-this-alias关闭冗余const self = this,强制箭头函数,风格统一。 -
注释写清 this 指向
复杂工具函数,用 JSDoc@this标注,让后人秒懂。 -
单元测试覆盖 this 场景
用 Jest 快照或expect(fn.thisValues[0]).toBe(instance)断言,防止重构时 this 跑偏。
彩蛋:一个“this 安全”的通用 throttle 实现
/**
* 节流函数,保证 this 和事件参数都正确透传
* @param {Function} fn
* @param {number} delay
* @returns {Function} 节流后的函数
*/
function throttle(fn, delay) {
let timer = null;
return function throttled(...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args); // 显式绑定,保证 this 不被丢失
timer = null;
}, delay);
};
}
// 使用
const $input = document.querySelector('input');
$input.addEventListener('input', throttle(function (e) {
console.log(this.value, e.target.value); // 双向安心
}, 300));
收个尾
this 就像前端路上的“成年礼”:
第一次被它坑得鼻青脸肿,第二次学会 bind、箭头、严格模式,第三次开始写框架、写 lint 规则、写单元测试,最后回头一看——
this 还是那个 this,只是我们已经不再是当年的我们。
愿你在未来的代码里,不再 console.log(this) 疑神疑鬼,而是胸有成竹地写下:
this.setState?.({ confident: true }),然后笑着保存文件,去楼下买杯奶茶。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!



1221

被折叠的 条评论
为什么被折叠?



