this、箭头函数与普通函数:前端实战避坑指南 | JS 基础语法与数据操作篇

this、箭头函数与普通函数:前端实战避坑指南 | JS 基础语法与数据操作篇
【this/箭头函数/普通函数】+【前端中后台开发】:从【this绑定逻辑】到【落地实操】,彻底搞懂【函数选型】的最佳写法,避开Promise/模板事件/对象方法高频坑!
在这里插入图片描述

📑 文章目录


同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~

(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)

你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?

你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?

就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。

一天只有24小时,时间永远不够用,常常感到力不从心。

技术行业,本就是逆水行舟,不进则退。

如果你也有同样的困扰,别慌。

从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲

这一次,我们一起慢慢来,扎扎实实变强。

不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,

咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。


前言

无论你是刚学 JavaScript 的小白,还是已经写了几年代码的前端,只要在写后台管理系统,大概率都踩过 this 和箭头函数的坑。

这篇文章不讲特别玄学的底层原理,只回答三个问题:

  1. 日常写代码该怎么选?(普通函数 vs 箭头函数)
  2. 为什么这么选?
  3. 坑最容易出在哪里?

⬆ 返回目录


一、一个真实的报错场景

先看一段后台管理系统里常见的代码:

// 表格操作列有个「删除」按钮methods:{handleDelete(id){this.$confirm('确定删除吗?').then(()=>{this.deleteApi(id);// ❌ 报错:Cannot read property 'deleteApi' of undefined});}}

很多人会疑惑:我明明在 methods 里写的,this 怎么会是 undefined

问题在于:this 不是由「你在哪写的」决定的,而是由「谁在调用这个函数」决定的。$confirm().then() 里的回调,是 Promise 内部在调用,普通函数不会自动带上 Vue 实例的 this

如果把 .then() 里的回调改成箭头函数,就不会报错了。后面会详细说明原因。

ps·补充回调函数概念(供新手同学参考)
  • 非回调函数:定义后由你直接调用(fn()),执行时机由你决定;
  • 回调函数:把函数作为参数传递给另一个函数,由这个 “接收方函数” 在特定时机(比如异步操作完成、遍历完成)调用它。
// 1. 普通函数:自己定义、自己调用functionnormalFn(){ console.log("我是普通函数,直接调用就执行");}// 主动调用普通函数normalFn();// 输出:我是普通函数,直接调用就执行// 2. 回调函数:作为参数传递,由其他函数调用functioncallbackFn(){ console.log("我是回调函数,由forEach调用");}// forEach是接收回调的主函数,它会遍历数组时调用回调函数[1,2,3].forEach(callbackFn);// 输出3次:我是回调函数,由forEach调用// 更常见的写法:匿名回调函数(简化版)[1,2,3].forEach(function(item){ console.log("遍历到的元素:", item);// 异步/同步场景都常用});// 异步回调(典型场景:定时器)setTimeout(function(){ console.log("1秒后执行的回调函数");// 1秒后才执行,时机由setTimeout决定},1000);

⬆ 返回目录


二、基础扫盲:this 到底是谁决定的

核心结论 this 由「调用方式」决定,而不是由「定义位置」决定。

调用方式this 指向典型场景
作为对象方法调用该对象obj.fn() → this 是 obj
直接调用 fn()严格模式:undefined;非严格:window孤立的函数调用
new 调用新创建的对象new Foo()
call/apply/bind传入的第一个参数显式指定 this
作为回调传入谁调就指向谁,通常丢 thissetTimeout(fn)、Promise.then(fn)

关键点:当函数被当作回调传给别人时,谁调这个函数,this 就由谁决定。 比如 setTimeout(fn) 里,是浏览器在调 fn,所以 this 通常是 windowundefined,而不是你组件里的 this

⬆ 返回目录


三、箭头函数 vs 普通函数:本质区别

对比项普通函数箭头函数
this有属于自己的 this,由调用方式决定没有自己的 this,使用外层作用域的 this
arguments没有(可用 ...args 替代)
能否 new可以不可以
能否作为构造函数可以不可以

⬆ 返回目录

3.1. this 的区别(案例说明)

普通函数的 this 由调用方式决定,而箭头函数没有自己的 this,会 “继承” 外层作用域的 this

// 案例1:对象方法中的 thisconst person ={name:"张三",// 普通函数作为对象方法sayNameNormal:function(){ console.log("普通函数 this:",this.name);// this 指向调用者 person},// 箭头函数作为对象方法sayNameArrow:()=>{ console.log("箭头函数 this:",this.name);// this 指向外层(全局window),没有name属性}}; person.sayNameNormal();// 输出:普通函数 this: 张三 person.sayNameArrow();// 输出:箭头函数 this: undefined// 案例2:嵌套函数中的 thisconst obj ={num:10,fn:function(){// 普通嵌套函数:this 指向全局(非严格模式)setTimeout(function(){ console.log("普通嵌套函数 this.num:",this.num);// undefined},0);// 箭头嵌套函数:this 继承外层 fn 的 this(即obj)setTimeout(()=>{ console.log("箭头嵌套函数 this.num:",this.num);// 10},0);}}; obj.fn();
解释:
  • 普通函数 sayNameNormalperson 调用,this 就指向 person
  • 箭头函数 sayNameArrow 没有自己的 this,直接用外层(全局)的 this,而全局 thiswindow)没有 name 属性,所以是 undefined
  • 嵌套场景中,箭头函数能 “捕获” 外层函数的 this,这也是实际开发中箭头函数最常用的场景(避免手动绑定 this)。

⬆ 返回目录

3.2. arguments 的区别(案例说明)

普通函数有 arguments 对象(存储传入的所有参数),箭头函数没有,需用剩余参数 ...args 替代。

// 普通函数:有 argumentsfunctionnormalFn(){ console.log("普通函数 arguments:", arguments);// 输出传入的参数集合 console.log("第一个参数:", arguments[0]);}// 箭头函数:无 arguments,用 ...args 替代constarrowFn=(...args)=>{// console.log(arguments); // 直接用会报错:ReferenceError: arguments is not defined console.log("箭头函数 args:", args);// 数组形式存储参数 console.log("第一个参数:", args[0]);};normalFn(10,20,30);// 输出:普通函数 arguments: [Arguments] { '0': 10, '1': 20, '2': 30 }// 输出:第一个参数: 10arrowFn(10,20,30);// 输出:箭头函数 args: [ 10, 20, 30 ]// 输出:第一个参数: 10
解释:
  • arguments 是类数组对象,只能在普通函数中使用;
  • 箭头函数要获取所有参数,必须用 ES6 的剩余参数 ...argsargs 是真正的数组,还能使用 map/filter 等数组方法,比 arguments 更灵活。

⬆ 返回目录

3.3. 能否 new / 作为构造函数(案例说明)

普通函数可以用 new 调用(作为构造函数),箭头函数不行,强行 new 会报错。

// 普通函数:可作为构造函数functionPerson(name){this.name = name;}const p1 =newPerson("李四"); console.log("普通函数构造的实例:", p1.name);// 李四// 箭头函数:不能作为构造函数constArrowPerson=(name)=>{this.name = name;};try{const p2 =newArrowPerson("王五");// 强行new会报错}catch(e){ console.log("箭头函数 new 报错:", e.message);// ArrowPerson is not a constructor}
解释:
  • 构造函数的核心是 new 操作会创建新对象,并把函数的 this 绑定到这个新对象;
  • 箭头函数没有自己的 this,也没有 prototype 属性(构造函数的必要条件),所以无法作为构造函数使用。
小结:
  1. this 指向:普通函数的 this 由调用方式决定,箭头函数继承外层作用域的 this(无自身 this);
  2. 参数获取:普通函数用 arguments,箭头函数无 arguments,需用 ...args 剩余参数;
  3. 构造函数能力:普通函数可 new 作为构造函数,箭头函数不行,强行 new 会报错。

这三个区别是箭头函数和普通函数最核心的差异,其中 this 指向的区别是实际开发中最常遇到、也最需要注意的点。

⬆ 返回目录


四、后台项目里最容易写错的 5 种场景

4.1 场景 1:Element UI / Ant Design 表格里的回调

 // ❌ 错误写法:箭头函数里加 this.,会从 window 找方法,导致 this 失效 <el-table-columnlabel="操作"><templateslot-scope="scope"><el-button@click="() => this.handleEdit(scope.row)">编辑</el-button></template></el-table-column> // ✅ 最优写法:直接传方法引用,Vue 自动绑定组件实例的 this <el-button@click="handleEdit(scope.row)">编辑</el-button> // ⚠️ 能运行但不推荐:箭头函数里不加 this.(Vue 会映射到组件方法),但多一层包装无意义 <el-button@click="(row) => handleEdit(row)">编辑</el-button>

原因:

  • 模板中的事件绑定,Vue 会自动将方法的 this 绑定到组件实例上(而非 window)。

错误写法:箭头函数的 this 固定指向全局上下文(window/undefined),this.handleEdit 会从 window 查找方法(找不到),且方法内部的 this 也会失效;

  • 直接写方法名:Vue 会把模板里的 handleEdit 隐式映射到「组件实例的 methods」,并自动绑定组件的 this,是最安全高效的方式。

结论: 模板事件绑定优先直接写 方法名(参数)(最优解);尽量不要用箭头函数包装(避免多余的函数创建 + 踩 this 坑);若因特殊场景非要用箭头函数,需去掉 this.(如 (row) => handleEdit(row)),但这种写法无必要,仅作为兜底参考。

⬆ 返回目录


4.2 场景 2:Promise / async 里的 this

// ❌ 错误:.then 里用普通函数,this 丢失handleSubmit(){this.validateForm().then(function(res){this.submitForm();// this 是 undefined!});}// ✅ 正确:用箭头函数,继承外层的 thishandleSubmit(){this.validateForm().then((res)=>{this.submitForm();// this 正确指向组件实例});}

原因:.then() 的回调是 Promise 内部调用的,普通函数不会自动绑定组件 this。用箭头函数可以继承 handleSubmit 所在作用域的 this,即组件实例。

结论:Promiseasync/awaitsetTimeout 等异步回调里,需要访问组件/外层 this 时,用箭头函数。

⬆ 返回目录


4.3 场景 3:对象方法 / API 封装

// ❌ 错误:箭头函数作为对象方法,this 指向外层(window)const api ={baseUrl:'/api',getList:()=>{return axios.get(this.baseUrl +'/list');// this.baseUrl 是 undefined!}};// ✅ 正确:用普通函数const api ={baseUrl:'/api',getList(){return axios.get(this.baseUrl +'/list');}};

原因: 箭头函数没有自己的 this,会去外层找。这里的 getList 定义在对象字面量里,外层是全局,this 就是 window(或 undefined),自然拿不到 baseUrl

结论: 对象方法、Class 方法需要用到 this 时,用普通函数,不要用箭头函数。

⬆ 返回目录


4.4 场景 4:事件监听器(addEventListener)

// 场景:监听 window 滚动,组件销毁时需要移除监听// ❌ 错误:箭头函数每次都是新引用,无法正确 removeEventListenermounted(){ window.addEventListener('scroll',()=>this.handleScroll());},beforeDestroy(){ window.removeEventListener('scroll',()=>this.handleScroll());// 移除失败!引用不同}// ✅ 正确:保存同一个函数引用mounted(){this.boundHandleScroll =this.handleScroll.bind(this); window.addEventListener('scroll',this.boundHandleScroll);},beforeDestroy(){ window.removeEventListener('scroll',this.boundHandleScroll);}

原因:removeEventListener 必须传入和 addEventListener 时完全相同的函数引用。每次写 () => this.handleScroll() 都会生成新函数,所以无法正确移除。

结论: 需要手动移除监听时,用 bind 或普通函数,并把引用存到实例上,保证添加和移除用的是同一个函数。

⬆ 返回目录


4.5 场景 5:数组方法的回调(forEachmapfilter 等)

// 在 Vue 组件里methods:{processList(){const list =[1,2,3];// ❌ 错误:普通函数作为 forEach 回调,this 会丢 list.forEach(function(item){this.doSomething(item);// this 是 undefined});// ✅ 正确:箭头函数继承外层的 this list.forEach((item)=>{this.doSomething(item);});}}

原因:forEach 等方法的回调是由数组方法内部调用的,普通函数不会绑定组件 this。用箭头函数可以继承 processListthis

结论:forEachmapfilterreduce 等回调里需要访问外层 this 时,用箭头函数;不需要 this 时,两者都可以。

⬆ 返回目录


五、决策清单:什么时候用谁

可以按下面几条来选:

  1. 对象方法、Class 方法、构造函数 → 用普通函数。
  2. Promise、setTimeout、数组方法等回调里要访问外层 this → 用箭头函数。
  3. Vue 模板事件 → 直接写方法名,或 (arg) => this.method(arg),避免乱包箭头函数。
  4. 需要 arguments → 用普通函数,或箭头函数 + ...args
  5. addEventListener / removeEventListener → 用 bind 或保存同一引用,保证添加和移除是同一个函数。

⬆ 返回目录


六、一句话口诀

  • 普通函数:有「自己的」this,谁调我,this 就指向谁。
  • 箭头函数:没有「自己的」this,用的是「定义时所在作用域」的 this

因此,在需要「继承」外层 this 的场景(例如 PromisesetTimeout 回调),用箭头函数;在对象方法、构造函数等需要「自己的」this 的场景,用普通函数。需要「动态 this」用普通函数,需要「固定外层 this」用箭头函数。

⬆ 返回目录


总结

this 和箭头函数本身不复杂,容易出错的是「在错误场景选错写法」。后台项目里,最容易踩坑的就是:Promise 回调对象方法模板事件事件监听器这几处。记住「谁在调用」「外层 this 是谁」,选普通函数还是箭头函数就不容易错。

⬆ 返回目录

🔍 系列模块导航

📝 JS 基础语法与数据操作

一、《var/let/const:变量与作用域实战选型|JS 基础语法与数据操作篇》

二、《this、箭头函数与普通函数:前端实战避坑指南 | JS 基础语法与数据操作篇》

三、《对象解构赋值:接口数据解包 10 个实战写法|JS 基础语法与数据操作篇》

四、《map/filter/reduce:数组10个常用实战操作|JS 基础语法与数据操作篇》

五、《find/some/every/includes:数组查找与判断实战用法|JS 基础语法与数据操作篇》

六、《sort/localeCompare:对象数组排序与分组实战|JS 基础语法与数据操作篇》

七、《模板字符串 /split/join/ 正则:字符串处理实战|JS 基础语法与数据操作篇》

八、《Date/dayjs:日期时间处理实战|JS 基础语法与数据操作篇》

九、《try/catch/Promise:前端错误处理实战|JS 基础语法与数据操作篇》

十、《import/export:前端模块化实战|JS 基础语法与数据操作篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

💡 更多内容(TS/Vue/工程化等共47篇)已整理成「全体系总目录」👇,收藏后可一站式学习:

📚 系列总览

想系统学习Vue3中后台开发?收藏这份「全体系指南」,47篇干货从基础到工程化全覆盖:

《前端基础实战:JS/TS与Vue体系化扫盲(47 篇完整目录 + 避坑)》

💡 每篇都配套实战场景+避坑指南,帮你摆脱「面向搜索引擎写代码」的尴尬~

⬆ 返回目录


学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。

后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。

关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。

如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。

我是 Eugene,你的电子学友,我们下一篇干货见~

Read more

Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429)

Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429)

Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429) * 引言: * 正文: * 一、高速收费系统的三大核心痛点与数据瓶颈 * 1.1 传统收费模式的效率天花板 * 1.2 数据孤岛导致的 “盲态运营” * 1.3 计费准确性与异常检测难题 * 1.4 优化前核心指标(数据来源:交通运输部 2022 年公开数据 + 某省运营统计) * 二、Java 大数据技术栈选型与架构设计 * 2.1 技术选型核心原则 * 2.2 核心技术栈详解(生产环境验证版) * 2.3 整体架构设计(Java 大数据驱动的收费系统架构) * 三、核心优化方案与 Java 大数据实战实现 * 3.1 实时车流预测与车道动态调度(

By Ne0inhk
SpringBoot+Vue 乡村政务办公系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】

SpringBoot+Vue 乡村政务办公系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】

摘要 随着乡村振兴战略的深入推进,乡村政务管理的信息化需求日益增长。传统的乡村政务办公模式存在效率低下、信息孤岛、数据共享困难等问题,亟需借助现代信息技术实现数字化转型。乡村政务办公系统平台旨在整合乡村政务资源,提高办公效率,促进政务公开,优化村民服务体验。该系统通过信息化手段实现村务管理、政策宣传、帮扶信息管理等功能,为乡村治理现代化提供技术支撑。关键词:乡村振兴、政务信息化、数字治理、村务管理、办公系统。 本系统基于SpringBoot+Vue技术栈开发,采用前后端分离架构,后端使用SpringBoot框架实现RESTful API接口,前端采用Vue.js构建用户界面,数据库选用MySQL存储数据。系统功能涵盖用户权限管理、新闻公告发布、帮扶信息管理、村民信息登记等模块,支持多角色登录和权限控制。接口文档采用Swagger生成,便于开发调试。系统通过高内聚低耦合的设计理念,确保代码可维护性和扩展性,为乡村政务办公提供高效、便捷的解决方案。关键词:SpringBoot、Vue.js、RESTful API、MySQL、Swagger。 数据表设计 帮扶信息数据表

By Ne0inhk
中秋满月皆十六圆?Java实证求解后的真相

中秋满月皆十六圆?Java实证求解后的真相

目录 前言 一、天文上的满月 1、形成原理及定义 2、出现时间及观测 3、文化意义 二、Java模拟月满计算 1、整体实现逻辑 2、主计算方法详解 3、核心天文算法详解 3.1 儒略日计算基础 3.2 时间参数计算 3.3 天文参数计算 3.4 周期项修正计算 4、辅助方法详解 4.1 角度标准化 4.2 日历与儒略日转换 4.3 儒略日转日历 三、近年中秋满月计算及对比 1、近年中秋满月计算 2、近年计算与公布时间对比 四、总结 前言

By Ne0inhk
JAVA多线程并发编程:并发容器与线程协作实战

JAVA多线程并发编程:并发容器与线程协作实战

JAVA多线程并发编程:并发容器与线程协作实战 💡 学习目标:掌握JAVA中常用并发容器的特性与适用场景,理解线程间协作的核心原理,能够运用并发容器和协作工具解决实际并发问题。 💡 学习重点:并发容器与普通容器的区别、ConcurrentHashMap 核心原理、CountDownLatch/CyclicBarrier/Semaphore 的使用、生产者消费者模式实现。 1.1 为什么需要并发容器? 在多线程场景下,普通的集合容器(如 HashMap、ArrayList)是线程不安全的。多个线程同时对其进行读写操作时,会导致数据错乱、ConcurrentModificationException 异常等问题。 ⚠️ 注意事项:即使使用 Collections.synchronizedXXX() 方法包装普通容器,也只是通过 synchronized 实现简单的加锁。这种方式锁粒度较粗,并发性能较低。 ✅ 核心结论:并发容器是JAVA为多线程场景设计的高性能容器。它们通过细粒度锁或无锁算法实现线程安全,能够在保证数据一致性的同时,大幅提升并发访问效率。 1.2 常用并

By Ne0inhk