我是怎么把 JavaScript 的 this 和箭头函数彻底搞明白的——个人学习心得

其实我当初学 this 和箭头函数的时候,跟很多人一样,越学越懵:明明函数写在对象里,this 却指向 window;嵌套了 if 块,箭头函数的 this 又不变。后来踩了不少坑、写了很多测试代码,才慢慢摸透其中的规律。

先牢牢的记住:this 的存在范围是明确的:只有 全局作用域普通函数作用域 有独立的 this,块级作用域(如 if、for 的 {} 内)没有自己的 this,其内部的 this 完全继承自外层作用域。
先牢牢的记住:this 的存在范围是明确的:只有 全局作用域普通函数作用域 有独立的 this,块级作用域(如 if、for 的 {} 内)没有自己的 this,其内部的 this 完全继承自外层作用域。

简介

函数分为 普通函数箭头函数,两者的this规则截然不同:

  • 普通函数的this遵循"谁调用,指向谁"。即使函数定义在对象内部,比如:

    javascript 复制代码
    let 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 相关的问题,就用这三句去套,基本都能解决:

  1. "谁有 this?" ------只有全局和普通函数有独立 this,块级作用域没有,只能借外层的;
  2. "普通函数 this 看什么?" ------看"谁调用",调用者是谁,this 就指向谁;
  3. "箭头函数 this 看什么?" ------看"定义时的外层",复印外层的 this,永远不变。

其实一开始我也觉得 this 很难,但后来发现:很多时候不是知识点难,而是自己一开始走进了误区,比如以为块级作用域有 this,以为箭头函数能当对象方法。等把这些误区一个个踩破,再结合自己写的测试代码,慢慢就理清了逻辑。

现在我写代码的时候,只要用到 this 或者箭头函数,都会先在心里过一遍这三句口诀,基本上不会再出错。希望我的这些心得,能帮你少走一些我当初走的弯路~

相关推荐
右子6 小时前
React 编程的优雅艺术:从设计到实现
前端·react.js·mobx
清灵xmf7 小时前
npm install --legacy-peer-deps:它到底做了什么,什么时候该用?
前端·npm·node.js
超级大只老咪7 小时前
字段行居中(HTML基础语法)
前端·css·html
IT_陈寒7 小时前
Python开发者必看!10个高效数据处理技巧让你的Pandas代码提速300%
前端·人工智能·后端
只_只8 小时前
npm install sqlite3时报错解决
前端·npm·node.js
FuckPatience8 小时前
Vue ASP.Net Core WebApi 前后端传参
前端·javascript·vue.js
数字冰雹8 小时前
图观 流渲染打包服务器
服务器·前端·github·数据可视化
JarvanMo8 小时前
Flutter:我在网上看到了一个超炫的动画边框,于是我在 Flutter 里把它实现了出来
前端
returnfalse8 小时前
前端性能优化-第三篇(JavaScript执行优化)
前端·性能优化