资讯动态

箭头函数与普通函数六大差异:从this绑定到构造函数原理彻底讲透

发布时间:2026/9/17 0:40:01 来源:尧图企业网站定制
前端面试里有个问题几乎每次都能碰到而且几乎每次都能筛掉一批人**箭头函数和普通函数到底有什么区别**别小看这个问题表面上问的是语法实际上考察的是this绑定机制、原型链、构造函数原理、arguments 对象等一整套 JavaScript 核心知识。很多人在项目里天天写箭头函数写得飞起但真被问到“为什么箭头函数不能做构造函数”的时候直接卡壳。这篇文章不打算给你背八股文而是站在面试官的角度把箭头函数从写法到原理彻底拆开讲清楚。我会从最基础的箭头函数写法开始逐步深入到词法this的底层机制、与普通函数的六大差异、class 中箭头函数的使用陷阱最后配上一组高频面试题和避坑实操。不管你是正在准备面试还是已经工作多年想补齐这块短板这篇文章都值得你花十几分钟认真读完。1. 箭头函数写法与语法糖解析1.1 从 function 到 的写法演进箭头函数写法是 ES6 最直观、最容易上手的语法糖。“箭头函数写法”这个热搜词说明大家都在关心怎么写但实际上写法背后藏着的是一整套设计取舍。我们先从最经典的对比开始看// 传统函数写法 function add(a, b) { return a b; } // 箭头函数写法 const add (a, b) a b;看到区别了吗箭头函数把function关键字去掉了参数和函数体之间用连接。如果函数体只有一行表达式return也可以省略箭头函数会自动把表达式的值作为返回值。这就是所谓的“隐式返回”。可能你已经在项目里见过各种写法我这里把箭头函数写法的几种变体全部整理出来方便你一次看全写法场景代码示例说明无参数() 42括号不能省略单参数x x * 2括号可省略多参数(a, b) a b括号必须保留单表达式隐式返回(a, b) a b自动 return 结果多语句显式返回(a, b) { const s a b; return s; }花括号内需手动 return返回对象字面量() ({ name: Jack })必须用圆括号包裹对象解构参数({ name }) name参数直接解构这里有两个最容易被新手写错的地方。第一个是返回对象字面量时必须加圆括号。因为 JavaScript 解析代码时{}会被识别为代码块而不是对象所以() { name: Jack }会返回undefined而不是对象。第二个是单参数省略括号的场景一旦你省略了括号代码的可读性会有所下降团队协作时建议统一风格。1.2 箭头函数不能作为构造函数的底层原因这个知识点是面试题里最常出现的变体之一而且很多人只背了结论不知道原理。箭头函数之所以不能用new来调用核心原因在于箭头函数内部没有[[Construct]]方法。JavaScript 中所有函数在被new调用时引擎会走一套构造流程创建一个新对象把该对象的原型链连接到构造函数的prototype属性上然后把构造函数内部的this绑定到这个新对象上最后如果构造函数没有显式返回对象就返回这个新对象。这套流程依赖一个内部方法[[Construct]]普通函数都有这个方法但箭头函数没有。你可以做这样一个简单的测试const RegularFunction function() { this.name Jack; }; const ArrowFunction () { this.name Jack; }; const obj1 new RegularFunction(); // 正常obj1 { name: Jack } const obj2 new ArrowFunction(); // TypeError: ArrowFunction is not a constructor你还可以从原型的角度验证这个差异。普通函数是Function的实例它有prototype属性箭头函数同样也是Function的实例但它没有prototype属性。这就意味着即便你强行给箭头函数设置prototype也无法改变它不能构造的事实。提示箭头函数的设计初衷是作为轻量级的函数表达式使用它不是普通函数的完全替代品。在面试中可以说出“箭头函数没有[[Construct]]”这个关键点会明显区别于只背结论的候选人。1.3 箭头函数没有 arguments 对象除了不能做构造函数箭头函数还没有自己的arguments对象。这是另一个高频考点也经常在实际开发中坑人。普通函数的arguments对象包含了调用时传入的所有参数它是类数组的。但箭头函数内部访问arguments时它会沿着作用域链向上查找找到的是外层函数的argumentsfunction outer() { const innerArrow () { console.log(arguments); // 输出的是 outer 的 arguments而不是 innerArrow 的 }; innerArrow(1, 2, 3); } outer(a, b); // 输出 Arguments(2) [a, b]看到问题了吗innerArrow(1, 2, 3)明明传了三个参数但箭头函数内部拿到的却是外层outer函数的参数。这在某些需要收集参数的场景会引发隐蔽的 bug。那箭头函数里怎么获取自己的参数呢正确方案是使用 ES6 的剩余参数语法const collect (...args) { console.log(args); // [1, 2, 3]真正的数组 }; collect(1, 2, 3);这里顺带说一个加分项arguments是类数组不是真正的数组使用前往往需要Array.from(arguments)转换而剩余参数...args直接就是真正的数组可以直接使用数组方法。2. 核心机制箭头函数的 this 到底指向谁2.1 词法 this 是箭头函数与普通函数的根本分水岭接下来是重头戏我认为这是箭头函数所有特性中最核心、也是面试官最看重的一点普通函数的this是由调用方式决定的而箭头函数的this是由定义位置决定的。普通函数的this可以用一句话概括谁调用它this就指向谁。对象方法被对象调用this指向对象普通函数被全局调用this指向window严格模式下是undefined事件处理函数被 DOM 调用this指向 DOM 元素。这就是动态this它的值要到函数真正被调用的那一刻才能确定。箭头函数则完全不同。它没有自己的this所谓的this是捕获了定义箭头函数时外层作用域的this。这个this在箭头函数定义的那一刻就被确定了之后无论你怎么调用都不能改变它的指向。这就是词法this也叫静态this。看一个反直觉的经典例子const obj { name: Jack, sayName: function() { console.log(this.name); }, sayNameArrow: () { console.log(this.name); } }; obj.sayName(); // Jack动态 this指向 obj obj.sayNameArrow(); // undefined静态 this指向全局对象这里obj.sayNameArrow()看似通过obj来调用的照理说this应该指向obj但箭头函数完全无视了这种调用方式。它在定义时外层作用域是全局作用域所以this捕获的就是全局的this也就是window。window.name不存在所以输出undefined。2.2 this 不可重新绑定的验证与原理理解了词法this后你还会遇到另一个高频问题箭头函数的this能不能通过call、apply、bind改变答案是不能。普通函数的this可以被这些方法显式指定这一点大家都熟悉。但箭头函数的this在定义时就已经固化这些方法对它无效const arrow () { console.log(this); }; const obj { name: Jack }; arrow.call(obj); // window不是 obj arrow.apply(obj); // window不是 obj arrow.bind(obj)(); // window不是 obj这三个方法也是面试官常用来加码追问的点。背后原因还是那句老话箭头函数没有自己的this它只有词法上捕获的this。当你在箭头函数内部访问this时引擎采用的是查找机制——沿作用域链向上找直到找到一个有this的函数作用域或全局作用域。这里可以做一个类比帮助理解this在普通函数中是一个“谁调用我就听谁的”的变量在箭头函数中则是“出生时家里是什么环境就是什么环境”的烙印无论后面对它做什么都无法改变。这个比喻我在面试辅导时说过很多次反馈都不错。2.3 为什么箭头函数非常适合回调场景理解了词法this之后我们来看实际开发中最大的受益场景回调函数。在箭头函数出现之前处理异步回调中的this是一大痛点。典型的问题出现在setTimeout或事件回调里const obj { name: Jack, greet: function() { setTimeout(function() { console.log(Hello, ${this.name}); }, 1000); } }; obj.greet(); // 输出 Hello, undefined为什么是undefined因为setTimeout的回调函数在全局上下文被调用它的this指向window或undefined拿不到obj.name。传统解决方案有两种。第一种是先把this保存到self或that变量里greet: function() { const self this; setTimeout(function() { console.log(Hello, ${self.name}); }, 1000); }第二种是用bind显式绑定greet: function() { setTimeout(function() { console.log(Hello, ${this.name}); }.bind(this), 1000); }箭头函数直接终结了这类 hack 写法greet: function() { setTimeout(() { console.log(Hello, ${this.name}); }, 1000); }箭头函数在定义时捕获了外层greet方法作用域的this而这个this在obj.greet()调用时已经正确指向obj。所以无论回调函数在何时、何地、以何种方式被调用它拿到的this都是obj。这也解释了一个常见疑惑很多人以为箭头函数适合做对象方法其实恰恰相反。上面例子中sayNameArrow直接定义在对象字面量里它捕获的是全局this而不是对象本身。箭头函数真正擅长的是作为回调函数而不是对象方法。3. 箭头函数与普通函数的六大核心差异3.1 表格对比两函数的主要区别面试时如果让你列举箭头函数和普通函数的区别一个结构清晰的回答能加分不少。下面这份对比表是我整理的完整版对比维度普通函数箭头函数this 绑定动态绑定由调用方式决定词法绑定由定义位置决定构造函数可以 new不能 new无 [[Construct]]arguments有类数组没有继承外层作用域prototype有默认存在没有方法定义语法支持 function* 生成器不支持不能作为生成器函数内部 this 动态修改call/apply/bind 可修改call/apply/bind 无效变量提升function 声明有提升const 声明存在 TDZ这份表格可以作为你面试记忆的主干但光背表格不够面试官往往会往深处追问。比如“没有 prototype 意味着什么”“为什么不能用做生成器”这类延展问题。接下来我会逐个展开说明。3.2 没有 prototype 属性引发的连锁反应箭头函数没有prototype属性这一点很多人知道但不知道它带来的连锁影响。普通函数都有prototype属性里面包含constructor属性指向函数本身。当你使用new调用普通函数时新对象的原型链会连接到这个prototype上。而箭头函数没有prototype自然无法承担构造任务。注意区分prototype和__proto____proto__是实例对象指向其构造函数的prototype的私有属性箭头函数作为对象实例它有__proto__但没有自己的prototype。这个区别偶尔会在面试中出现这里单独提一下。验证方式很简单const fn () {}; console.log(fn.prototype); // undefined console.log(fn.__proto__); // Function.prototype箭头函数也是 Function 的实例3.3 箭头函数不能作为生成器函数普通函数可以用function*语法定义生成器箭头函数则完全不支持。这一点相对冷门但面试中遇到偏题时可能会考// 普通函数可以 function* gen() { yield 1; yield 2; } // 箭头函数会报错 const genArrow *() {}; // SyntaxError: Unexpected token *生成器函数的特点是执行时返回一个迭代器函数体可以暂停和恢复执行这依赖函数独立的执行上下文。箭头函数的定位是轻量级表达式函数不具备独立的this、arguments从设计上就与生成器的需求相悖。所以正确答案是箭头函数不支持用作Generator函数。3.4 变量提升与暂时性死区还有一个容易忽略的差异普通function声明有变量提升可以在声明之前调用而箭头函数是赋值给变量的它的行为取决于变量声明的类型。const声明的箭头函数会进入暂时性死区TDZ在声明之前访问会抛出ReferenceErrorconsole.log(regular()); // 正常输出 regular函数声明被提升 function regular() { return regular; } console.log(arrow()); // ReferenceError: Cannot access arrow before initialization const arrow () arrow;如果你想避免这个问题就不能依赖提升必须确保在调用之前定义箭头函数。这也是箭头函数写法在实际项目中要注意的一个规律把箭头函数定义放在使用位置之前或在模块顶层补充导出。4. 箭头函数在 class 中的使用与陷阱4.1 class 中直接定义箭头函数的效果在 ES6 的 class 中我们可以用箭头函数来定义方法吗可以但要注意它的行为特性和副作用。class Person { constructor(name) { this.name name; } sayName () { console.log(this.name); }; sayNameRegular() { console.log(this.name); } }sayName是一个类字段class field它通过箭头函数的词法this捕获了实例化那一刻的this也就是类的实例。所以无论在什么时候调用person.sayName()this都稳定指向这个实例。这种写法的最大优势是把函数作为回调传递出去时不用担心this丢失。我们前面说过普通函数的方法被单独取出调用时this会丢。箭头函数则不存在这个问题const person new Person(Jack); const sayNameFn person.sayName; sayNameFn(); // Jack箭头函数 this 不会丢 const sayNameRegularFn person.sayNameRegular; sayNameRegularFn(); // TypeError: Cannot read properties of undefined4.2 class 箭头函数方法带来的内存问题不过class 中箭头函数方法也不是免费的午餐称之为“陷阱”毫不为过。用箭头函数定义的类方法实际上是绑定在实例上的属性而不是原型链上的方法。这意味着每个实例都会单独创建一份函数副本。如果你创建了 1000 个实例就会有 1000 个独立的方法对象内存开销明显增大。而普通方法定义在原型上所有实例共享同一个方法。这是 class 设计的基本原则方法与 prototype 相关系节省内存且便于统一修改。另外class 中箭头函数方法还有一个副作用子类继承时会受影响。子类如果没有重新定义该方法它继承的是父类实例创建时生成的函数副本而不是原型链上的共享方法。如果父类实例已经被销毁子类实例通过原型链正常查找原型方法的行为会受限这在某些框架中可能造成问题。所以实际项目中的建议是如果这个方法需要作为回调频繁传递比如 React 组件里的onClick绑定可以在构造函数里用箭头函数或直接使用类字段语法如果只是普通方法坚持用原型方法避免无谓的内存浪费。4.3 何时应该避开箭头函数有经验的开发者会告诉你并非所有地方都用箭头函数就是好代码。下面是几个我实际项目中总结的“避开箭头函数”的场景。对象字面量方法直接定义在对象属性里的箭头函数this会捕获全局对象而不是对象本身这正是我们前面踩过的坑。用普通方法语法sayName() {}更可靠。需要动态 this 的场景比如事件处理函数中需要根据event.target获取动态上下文或者框架中需要动态绑定this的钩子函数使用普通函数能保证灵活性。构造函数需要new来创建实例的场景箭头函数完全不支持。arguments 依赖场景需要读取函数自身调用参数的场景在箭头函数中拿不到arguments必须改为剩余参数写法。生成器函数场景需要用到yield实现惰性求值或状态机逻辑时只能使用function*。我经常在团队代码评审时强调一点箭头函数是工具不是信仰。选择哪种方式取决于你是否需要独立的this和参数对象而不是“看起来更简洁”。5. 面试题演练与最佳回答思路5.1 高频面试题实战解析面试题光看解析不够自己动手写一遍才行。下面这组题目覆盖了箭头函数的核心考点每一道都配上了回答思路。题目一箭头函数的 this 和普通函数的 this 有何不同推荐回答普通函数的this是动态的在函数被调用时根据调用方式确定可以理解为“谁调用我我就指向谁”。箭头函数没有自己的this它在定义时会捕获外层作用域的this作为自己的this之后无法通过任何手段修改。两者的本质差异在于普通函数有独立的this绑定机制箭头函数则完全绕开了这套机制。题目二为什么箭头函数不能作为构造函数推荐回答箭头函数在创建时没有[[Construct]]内部方法new操作符依赖于[[Construct]]来创建实例对象、绑定原型链、执行构造逻辑。箭头函数从设计上就没有这套能力所以不能用作构造函数。补充说明箭头函数也没有prototype属性这也是它无法支撑构造流程的表现之一。题目三下面代码输出什么为什么const obj { name: Jack, fn: () this.name }; console.log(obj.fn());推荐回答输出undefined。箭头函数定义在对象字面量中它捕获的是全局作用域的this而不是obj对象本身。所以this.name访问的是全局name结果为undefined。题目四如何在箭头函数中获取自身的参数推荐回答用剩余参数运算符如(...args) {}。箭头函数没有自己的arguments对象访问arguments会沿作用域链取到外层函数的arguments。剩余参数语法返回真正的数组直接支持数组方法比arguments更灵活。题目五call 和 apply 能否改变箭头函数的 this推荐回答不能。箭头函数的this是词法绑定的在定义时已经确定。call、apply、bind的三个方法都会尝试改变函数的this但箭头函数内部使用的是外层捕获的this这些方法传入的thisArg会被完全忽略。5.2 面试中容易翻车的扩展追问面试官通常不会只停留在概念性问题他们会结合代码来追问更深入的机制。下面这些扩展点值得准备。一个经典的追问是箭头函数this指向的外层作用域究竟怎么理解“外层”这里要明确词法this的捕获规则是沿着作用域链向上找到最近的非箭头函数作用域如果一直找不到就指向全局。箭头函数可以在另一个箭头函数里定义那它的this依然来自最外层那个非箭头函数function outer() { const fn1 () { const fn2 () { console.log(this); }; fn2(); }; fn1(); } outer.call({ id: 1 }); // 输出 { id: 1 }fn1 和 fn2 的 this 都来自 outer 的 this另一个常见的追问是在严格模式下普通函数的this是什么。严格模式下普通函数被全局直接调用时this是undefined不是window。而箭头函数定义在全局作用域时无论是否严格模式它捕获的都是全局对象。这个差异经常引起混淆建议多加留意。还有一个追问是针对bind的既然箭头函数的this不能被修改那bind之后调用箭头函数会发生什么答案是箭头函数完全忽略bind传入的参数依然使用词法this。但注意bind传入的后续参数还是会生效的因为bind的额外参数会被当作预设参数传入函数。这个细节容易出错写成代码可以直观感受const fn (a, b) console.log(a, b); const bound fn.bind(null, 1, 2); bound(); // 输出 1 25.3 答题结构建议三点式回答法我在面试辅导中遇到很多候选人不是不会而是回答的时候东一句西一句没有结构感。在这里分享一个“三点式回答法”适用于大多数箭头函数相关问题。第一点先说结论用一句话概括问题核心。比如“箭头函数没有自己的 this它是词法作用域捕获的”。第二点展开机制补充底层原因的细节比如“因为箭头函数没有 [[Construct]]所以不能 new”“因为箭头函数没有自己的 arguments所以访问时沿作用域链查找”。第三点结合实际应用用一个小例子或场景来佐证结论比如“所以在 setTimeout 回调里箭头函数能稳定拿到外层 this”。这种回答方式的优势在于面试官能快速抓住你的核心观点也能看到你理解机制的深度还能看到你在真实场景中的判断力。比单纯背书效率高得多。6. 箭头函数实际开发中的最佳实践指南6.1 React 组件中最推荐的写法箭头函数在现代前端框架中大量使用React 组件是典型场景。以函数组件为主的项目里事件处理函数和 hooks 中回调的使用频率极高箭头函数几乎是默认选择。举例来说一个典型的 React 函数组件里function Button({ onClick, label }) { const handleClick () { onClick(label); }; return button onClick{handleClick}{label}/button; }handleClick用箭头函数定义配合 hooks 时写法也很自然function SearchBox({ onSearch }) { const [query, setQuery] useState(); const handleChange (event) { setQuery(event.target.value); }; const handleSearch () { onSearch(query); }; return ( div input value{query} onChange{handleChange} / button onClick{handleSearch}搜索/button /div ); }在 React 类组件中箭头函数与事件的配合同样常见。由于 React 事件处理函数需要保持正确的this用箭头函数定义事件处理是省心方案。但也要清楚它带来的内存代价实例越多开销越明显。对于大型列表项组件优先考虑在构造函数中显式绑定普通方法或者在事件回调处直接用箭头函数调用的方式// 推荐 button onClick{() this.handleDelete(id)}删除/button // 而不是每次 render 都重新生成独立函数 // button onClick{this.handleDelete(id)}删除/button6.2 数组方法链式调用中的箭头函数数组的map、filter、reduce等方法是箭头函数的最佳舞台。箭头函数写法简洁可读性强还能配合链式调用写出非常流畅的代码const orders [ { product: 笔记本, price: 5000, count: 2 }, { product: 鼠标, price: 100, count: 5 }, { product: 键盘, price: 300, count: 1 }, ]; // 一行完成过滤和求和 const total orders .filter(order order.price 200) .reduce((sum, order) sum order.price * order.count, 0); console.log(total); // 5000*2 300*1 10300这里要注意一个细节如果用普通函数你可能会被this问题干扰但回调里其实不太需要this。重点在于箭头函数让用户把注意力放在“数据如何变换”上而不是函数本身的上下文。配合解构和默认参数还能把回调写得更加简洁const sumPrices orders .filter(({ price }) price 200) .reduce((sum, { price, count }) sum price * count, 0);6.3 箭头函数虽好别忘可读性代码写出来是给别人看的。箭头函数写法简洁是有代价的过度使用会让代码变成一行难以阅读的“谜语”。我自己见过一些走极端的写法// 能跑但很难读 const r a b c a b c;柯里化在这种写法下确实精致但对团队其他人很不友好。比较好的实践是箭头函数适合简单回调复杂逻辑超过两到三步请用块级函数体加显式return参数超过两个括号就别省略了可读性优先于简洁。还有一个相关的规范值得一提ESLint 的prefer-arrow-callback规则会提示你把非必要的function回调改成箭头函数而arrow-body-style会规范箭头函数体的风格。在团队项目中统一的 lint 规则能避免因为风格问题导致的代码冲突。根据我个人实际编写和评审项目的体会箭头函数写法的最大价值不是“省几个字符”而是帮你彻底绕开了this丢失的深坑。但对应的写出高维护性代码的关键是知道什么时候不使用它——对象方法、构造函数、生成器、依赖 arguments 的场景都应该回到普通函数。只有理解了两者完全不同的设计定位才能在面试时对答如流在代码评审时有理有据地说服别人。最后再分享一个小技巧遇到任何关于this的困惑先问自己“这个函数在哪里定义的”答案往往已经在那里了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价