当前位置: 首页 > news >正文

面试官:5年经验还不懂箭头函数?


昨天看代码时,我遇到了一行看起来特别普通的 JavaScript。

普通到什么程度?

我第一眼扫过去,甚至觉得它不配让我停下来。

const data = (x) => x * 2 || 100;

我看完,笑了一下,准备继续往下读。

结果下一秒,我突然卡住了。

等等,data到底是什么?

我当时的第一反应是:“这还不简单?||会返回第一个 truthy 值。”

左边的(x) => x * 2是一个箭头函数。 箭头函数本质上是对象。 对象是 truthy。 所以,data应该就是这个函数本身:(x) => x * 2

至于后面的|| 100

死代码而已。

我甚至自信到,在测试之前就把这套推理写了下来。

然后,我打开了控制台。

let data = (x) => x * 2 || 100;
data(0)

输出:

100

再试一次:

data(5)

输出:

10

那一刻,我沉默了。

data(0)居然返回了100

如果data真的只是(x) => x * 2,那传入0,结果就应该是0。不可能是100

也就是说,我对这行代码的理解,错得很彻底。

我到底错在哪?

我当时是把这行代码读成了这样:

// 我以为 JavaScript 是这么理解的: const data = ((x) => x * 2) || 100;

按照这种读法,左边是一个函数,而函数是 truthy,所以data会直接拿到左边这个函数,右边的100根本不会执行。

逻辑看起来没毛病。

但问题是,JavaScript 根本不是这么解析的。

它真正看到的是:

// JavaScript 实际理解的是: const data = (x) => (x * 2 || 100);

注意,||并不在函数外面。

它在函数体里面。

箭头函数会把右边的整个表达式,都吞进自己的函数体里。

所以,data是一个函数。这个函数返回的是:

x * 2 || 100

当你传入0时:

0 * 2 || 100

也就是:

0 || 100

结果当然是:

100

当你传入5时:

5 * 2 || 100

也就是:

10 || 100

结果就是:

10

所以,我之前脑补出来的“死代码理论”,完全错了。

100不是被丢掉的分支,而是实打实会生效的兜底值。

为什么我们很容易想错?

因为大脑会下意识把=>当成一个普通的二元运算符。

好像它和+*||一样,只是夹在左右两边,用来参与优先级比较。

但它不是。

=>不是普通运算符。

它是箭头函数表达式的一部分,是一个语法边界。

一个简写函数体的箭头函数,语法大概是这样:

ArrowFunction → ArrowParameters => ConciseBody ConciseBody → AssignmentExpression

右边这个AssignmentExpression很“贪”。

只要它没遇到逗号、分号、右括号,或者输入结束,它就会继续往后吃。

它会吃掉:

x * 2 + 1

也会吃掉:

x || y

还会吃掉:

x && y

或者:

x ?? y

甚至三元表达式:

x ? a : b

以及赋值表达式:

y = 5

换句话说,一旦你写下=>,它右边剩下的那整段表达式,都会被当成函数体。

那里已经没有什么“外层的||”了。

||早就被吞进函数里了。

三个最容易踩坑的写法

1. 看起来像死代码的 fallback

const fee = (price) => price * 0.1 || 5;

你可能会把它理解成:

“计算手续费,如果price * 0.1没值,就兜底为5。”

这个理解没错。

但它之所以没错,是因为|| 5在函数体里面。

它绝不是说:

“如果函数本身是 truthy,就把fee赋值成函数,否则赋值成5。”

不是。

fee永远是函数。

只不过这个函数内部有一个 fallback。

2. 被箭头函数吞进去的三元表达式

const greet = (name) => name ? `Hi, ${name}` : "Hello stranger";

这整段三元表达式,都是函数体。

也就是说,它等价于:

const greet = (name) => (name ? `Hi, ${name}` : "Hello stranger");

这里的:不是 label。

?也不是 optional chaining。

它们都属于条件表达式,而这个条件表达式,就是箭头函数的返回值。

同样的坑,只是换了个运算符。

3. async 和 IIFE 风格造成的误读

const fn = async () => fetch('/api') || retry();

千万别把它读成:

“定义一个 async function,或者执行 retry。”

错。

真正的函数体是:

fetch('/api') || retry()

也就是说,理论上如果fetch('/api')返回 falsy,函数就会调用retry()

当然,实际中fetch返回的是 Promise 对象,而 Promise 是 truthy,所以retry()不会因为这个逻辑被触发。

但这里重点不是运行结果,而是解析方式。

|| retry()在函数里面,不在函数外面。

这道面试题到底在考什么?

如果面试官问你:

const data = (x) => x * 2 || 100; data(0) 返回什么?

他不是在考你记不记得||的短路规则。

这个你大概率知道。

他真正想看的是:你知不知道=>不是一个普通二元运算符。

它是语法边界。

它右边的内容,会成为函数体。

一旦你理解了这一点,这段代码就不再玄学了。

如果你写了几年 JavaScript,却一直潜意识里把=>当成和+||一样的东西来分析,那这道题刚好会把这个认知漏洞揪出来。

它不大。

但很真实。

而且,它出现的频率比你想象中高得多。

React 组件里的简写箭头函数、Array.prototype.map里的单行 callback、各种 reducer 里的表达式返回值,都可能藏着类似的误读风险。

我现在的心智模型

每次看到简写函数体的箭头函数:

const f = (args) => SOMETHING;

我都会在脑子里自动把它补成:

const f = (args) => (SOMETHING);

这个小动作非常有用。

它能立刻告诉你:函数体到底从哪里开始,到哪里结束。

||?:&&??这些东西,也会瞬间回到它们正确的位置——在函数体里面,而不是函数外面。

最后

=>不是普通运算符。

它是函数表达式的分隔符。

从它右边开始,一直到下一个逗号、分号,或者表达式结束,基本都属于函数体。

所以,下次有人拿这行代码问你:

const data = (x) => x * 2 || 100;

再问:

data(0)

你就不会再犹豫了。

答案是:

100

而且你现在不只是知道答案。

你知道它为什么是这个答案。

最后:

精通 React 面试:从零到中高级(针对面试回答)

CSS终极指南

Vue 设计模式实战指南

20个前端开发者必备的响应式布局

深入React:从基础到最佳实践完整攻略

python 技巧精讲

React Hook 深入浅出

CSS技巧与案例详解

vue2与vue3技巧合集

http://www.jsqmd.com/news/800841/

相关文章:

  • 基于SpatiaLite与React的英国邮编空间搜索应用架构与实战
  • Windows 环境下 Claude Code 安装与配置完全指南(含国产模型切换)
  • OpenClaw 长期使用避坑指南:环境稳定性维护、数据备份策略、版本兼容处理全方案
  • Windows 11安卓子系统WSA终极指南:开发者问题与功能请求完整解析
  • Intelli开源智能代理框架:从核心概念到生产部署全解析
  • 仅剩72小时可获取的2026终极对比手册(含Prompt工程调优参数表、国产信创环境适配补丁包、等保2.0三级适配验证清单):ChatGPT与Gemini,你选错一个就多花237万年运维成本
  • nv-design:自动化搭建设计师与前端开发者的本地工具链
  • 短视频矩阵系统技术选型:从自研到 SaaS 的成本与收益分析
  • WPF动画避坑指南:Blend路径动画Canvas.Left与RenderTransform的实战选择(附性能对比)
  • 终极指南:如何用GHelper轻松管理华硕笔记本性能与电池健康
  • 规划求解(Solver)实战:利用Excel的Solver工具进行投资组合优化
  • DownKyi音视频分离技术解析:从容器格式到素材提取的专业实践
  • 你做RAG,错在第一步
  • skillpm包管理器:下一代依赖管理工具的设计原理与实战指南
  • 2026年4月硅胶布供应商推荐,灭火毯/高硅氧布/锂电池灭火毯/芳纶布/石英布/高硅氧纤维/保温套,硅胶布供应商哪家可靠 - 品牌推荐师
  • 为什么92%的AI团队Serverless化失败?奇点大会披露的4个反直觉架构断点与实时熔断方案
  • 从SRT算法到现代CPU:为什么你的处理器除法这么快?
  • 高频电路实战:基于Multisim的调幅发射机设计与调试全解析
  • 从游戏角色到人脸分析:聊聊‘摇头、点头、转头’背后的欧拉角与万向节死锁
  • AI驱动的产品探索副驾:如何用系统提示词实现高效用户研究
  • 别再为ESXi证书错误头疼了!巧用Nginx反向代理+FRP,实现HTTPS域名安全访问内网后台
  • FastAPI 最佳实践:构建高性能电商后端
  • 金融/医疗/政务场景紧急适配!SITS 2026 v1.0已开放首批200个白名单接入名额(含FATE兼容桥接器与GDPR合规审计模板)
  • 基于RAG构建生产级知识问答系统:从架构设计到实战优化
  • 【AI时代开发者必修课】:用Perplexity秒级解析Stack Overflow百万级技术帖的底层逻辑
  • AI编码助手安全监控:Agent Shield实现macOS系统级威胁检测与防护
  • 如何免费安装FigmaCN:3步实现Figma中文界面,提升70%设计效率
  • 深度实战:如何通过3步优化BaiduPCS-Go的下载性能
  • 白盒测试/接口测试/自动化测试详解
  • 汽车功能安全需求追溯:ISO 26262标准下的挑战与实践