其实我当初学 this 和箭头函数的时候,跟很多人一样,越学越懵:明明函数写在对象里,this 却指向 window;嵌套了 if 块,箭头函数的 this 又不变。后来踩了不少坑、写了很多测试代码,才慢慢摸透其中的规律。
先牢牢的记住:this 的存在范围是明确的:只有 全局作用域 和 普通函数作用域 有独立的 this,块级作用域(如 if、for 的 {} 内)没有自己的 this,其内部的 this 完全继承自外层作用域。
先牢牢的记住:this 的存在范围是明确的:只有 全局作用域 和 普通函数作用域 有独立的 this,块级作用域(如 if、for 的 {} 内)没有自己的 this,其内部的 this 完全继承自外层作用域。
简介
函数分为 普通函数 和 箭头函数,两者的this规则截然不同:
-
普通函数的this遵循"谁调用,指向谁"。即使函数定义在对象内部,比如:
javascriptlet obj = { fn: () => { console.log(this); } }; let fn = obj.fn; fn(); // 此时this指向window(非严格模式下)
因为调用方式变成了无对象前缀的全局调用,this就从原本的obj变成了全局对象。
-
箭头函数没有自己的this,它的this是在定义时继承外层作用域的this,且永远不变。 箭头函数的 this,最终只会继承两种作用域的 this------ 要么是 外层普通函数 的 this,要么是全局作用域的 this。
箭头函数的 this 是 "定义时继承外层作用域的 this",而外层作用域中,只有普通函数作用域和全局作用域有自己的 this(块级作用域没有自己的 this,只能继承外层)。所以无论箭头函数嵌套多少层块级作用域(if/for/{} 等),最终都会 "穿透" 这些块级作用域,找到最近的、有自己 this 的作用域 ------ 要么是某个外层普通函数,要么是全局。
比如:
javascript
function outer() {
this.value = 100; // outer作为构造函数,this指向实例(因new调用)
if (true) { // if块级作用域无自己的this,继承outer的this
const innerObj = {
getValue: () => this.value // 箭头函数继承if块的外层(即outer)的this
};
console.log(innerObj.getValue()); // 输出100
for (let i = 0; i < 1; i++) { // for块级作用域同样继承outer的this
const deeperObj = {
getValueDeeper: () => this.value // 箭头函数仍继承outer的this
};
console.log(deeperObj.getValueDeeper()); // 输出100
}
}
}
const instance = new outer();
这里的箭头函数无论嵌套在多少层块级作用域中,最终继承的都是外层函数outer的this(即实例instance),因此能正确访问this.value。
一、一开始我就错了:以为"所有代码块都有自己的 this"
最开始学的时候,我有个特别傻的误区:觉得只要是个代码块(不管是对象、if 还是 for),里面就该有自己的 this。直到有一次,我在 if 块里写了段代码,彻底颠覆了这个想法:
javascript
// 我当时想:if 块里的 this 应该是"if 自己"吧?
if (true) {
console.log(this); // 结果输出 window(浏览器里)
}
我盯着这个结果看了半天------不对啊,if 块不是个独立的区域吗?怎么 this 还是 window?后来又试了 for 块、甚至直接写个空的 {}
块,发现里面的 this 都跟外层一样。这才慢慢明白:原来只有全局作用域和普通函数作用域,才有真正属于自己的 this;块级作用域(if/for/{}这些)根本没有自己的 this,它的 this 就是"借"外层的------外层是啥,它就是啥。
这一点太关键了!我后来所有的理解,都是从这个"纠正误区"开始的。比如之前看别人写的代码,在 for 循环里用箭头函数,当时不明白为什么 this 不变,后来才懂:for 块没有自己的 this,箭头函数继承的其实是外层函数的 this,跟 for 块没关系。
二、普通函数的 this
搞清楚 this 的"存在范围"后,我开始啃普通函数的 this------这是最常用,也最容易踩坑的地方。一开始我总记不住"谁指向谁",直到有一次把几个场景放在一起对比,突然就开窍了:普通函数的 this,跟"在哪定义"没关系,只跟"谁调用"有关系------谁调用它,this 就指向谁。
我自己踩过两个典型的坑,至今印象很深:
坑1:以为"对象里的函数,this 就一定指向对象"
最开始写对象方法的时候,我想当然觉得:函数在 obj 里,this 肯定是 obj。比如这样:
javascript
const user = {
name: "张三",
sayHi: function() {
console.log(`我是${this.name}`);
}
};
user.sayHi(); // 输出"我是张三"------这时候没问题
但后来我犯了个错:把函数抽出来单独调用,结果懵了:
javascript
const hi = user.sayHi; // 把 user 的 sayHi 赋值给变量
hi(); // 输出"我是undefined"
当时我盯着屏幕想:怎么回事?函数明明是从 user 里拿出来的,this 怎么不指向 user 了?后来才反应过来:调用方式变了!之前是 user.sayHi()
,调用者是 user;现在是 hi()
,没有调用者(或者说调用者是全局 window),所以 this 就指向 window 了。从那以后,我记普通函数 this 就一个标准:先看"怎么调用",找到调用者,this 就指向它。
坑2:用 new 调用时,this 指向"新对象"
刚开始用构造函数的时候,我不明白为什么 this.name
能赋到实例上。比如:
javascript
function Person(name) {
this.name = name; // 这个 this 是谁?
}
const p1 = new Person("李四");
console.log(p1.name); // 输出"李四"
我当时猜:this 是不是指向 Person 函数本身?后来查了资料、自己打印 this 才知道:用 new 调用的时候,JavaScript 会偷偷创建一个空对象,把这个空对象当成 this 传给构造函数,最后再把这个对象返回给实例。所以这里的 this,其实是"新创建的实例 p1"。这也印证了"调用方式决定 this"------用 new 调用,this 就指向新实例。
三、箭头函数
只有全局作用域和普通函数作用域,才有真正属于自己的 this;块级作用域(if/for/{}这些)根本没有自己的 this,它的 this 就是"借"外层的------外层是啥,它就是啥,最终只会继承两种作用域的 this------ 要么是外层普通函数的 this,要么是全局作用域的 this。。
如果说普通函数的 this 是"灵活多变"的,那箭头函数的 this 就是"一根筋"------一旦定了就不改。我一开始总搞混箭头函数和普通函数,直到有一次写定时器,才彻底明白两者的区别:
当时我想在对象方法里用 setTimeout,一开始用普通函数:
javascript
const obj = {
num: 10,
showNum: function() {
setTimeout(function() {
console.log(this.num); // 输出 undefined
}, 1000);
}
};
obj.showNum();
结果不对,this 指向 window 了。后来有人告诉我换成箭头函数:
javascript
setTimeout(() => {
console.log(this.num); // 输出 10
}, 1000);
这时候我才开始琢磨:箭头函数的 this 到底怎么来的?后来写了很多测试代码,比如在 if 块、for 块里嵌套箭头函数,终于总结出一个自己能懂的说法:箭头函数没有自己的 this,它就像一台"复印机"------在定义它的那一刻,把外层作用域的 this 复印下来,之后不管怎么调用,都用这张复印纸,再也不变。
比如之前那个 outer 函数的例子,我自己分析的时候是这么想的:
javascript
function outer() {
this.value = 100; // 这里的 this 是实例(因为用 new 调用)
if (true) {
// 箭头函数在 if 块里定义,但 if 块没有自己的 this
// 所以"复印机"只能复印外层的 this------也就是 outer 的 this(实例)
const getValue = () => this.value;
console.log(getValue()); // 自然输出 100
}
}
const instance = new outer();
而且我还发现一个"坑":千万别把箭头函数当对象方法用。我之前试过一次,把对象里的方法写成箭头函数,结果 this 指向 window,当时还纳闷了半天。后来才懂:对象字面量不是作用域,箭头函数定义时的外层是全局,所以 this 就复印了全局的 this,跟对象没关系。从那以后,我用箭头函数的场景就很明确了:要么是回调函数(比如 setTimeout、map 方法),要么是需要固定 this 的地方,绝对不拿来当对象方法。
四、最后
学完之后,我把所有知识点浓缩成3句自己能记住的口诀,后来遇到 this 相关的问题,就用这三句去套,基本都能解决:
- "谁有 this?" ------只有全局和普通函数有独立 this,块级作用域没有,只能借外层的;
- "普通函数 this 看什么?" ------看"谁调用",调用者是谁,this 就指向谁;
- "箭头函数 this 看什么?" ------看"定义时的外层",复印外层的 this,永远不变。
其实一开始我也觉得 this 很难,但后来发现:很多时候不是知识点难,而是自己一开始走进了误区,比如以为块级作用域有 this,以为箭头函数能当对象方法。等把这些误区一个个踩破,再结合自己写的测试代码,慢慢就理清了逻辑。
现在我写代码的时候,只要用到 this 或者箭头函数,都会先在心里过一遍这三句口诀,基本上不会再出错。希望我的这些心得,能帮你少走一些我当初走的弯路~