2026/2/2 19:33:09
网站建设
项目流程
做软件需要网站,游戏类网站备案,网站制作建设建议兴田德润,图书馆建设网站注意点尾调用搞懂了#xff0c;JS性能直接起飞#xff1f;前端人别再被面试官问懵了#xff01;尾调用搞懂了#xff0c;JS性能直接起飞#xff1f;前端人别再被面试官问懵了#xff01;为啥每次面试都被问“尾调用优化”#xff1f;尾调用到底是个啥玩意儿手把手看代码#…尾调用搞懂了JS性能直接起飞前端人别再被面试官问懵了尾调用搞懂了JS性能直接起飞前端人别再被面试官问懵了为啥每次面试都被问“尾调用优化”尾调用到底是个啥玩意儿手把手看代码普通递归 vs 尾递归JS引擎真的会优化尾调用吗尾调用能省多少内存实测给你看哪些语言真·支持尾调用优化实际开发中真有人用尾递归吗写尾递归时踩过的坑全记录调试尾调用问题的骚操作让尾调用更优雅的小技巧下次再被问尾调用反手甩他这段代码尾调用搞懂了JS性能直接起飞前端人别再被面试官问懵了先说句掏心窝子的我第一次被面试官问“尾调用优化”的时候脑子里的弹幕全是“这啥我学的是假JS”结果回去一查发现这玩意儿就像前任的微信——理论上早删干净了实际上还 ghost 在你职业生涯的每个角落时不时蹦出来诈尸。今天咱们就把这 ghost 揪出来按在地上摩擦顺便拍个遗照以后谁再问直接把文章甩过去哥自己看别再半夜问我了。为啥每次面试都被问“尾调用优化”其实它没你想的那么玄乎说白了面试官也苦。题库就那几页不问你尾调用难道问你“如何给产品经理写情书”但尾调用之所以常驻题库是因为它能一口气考察你三个底裤颜色懂不懂调用栈——装X术语叫“执行上下文”知不知ES6规范——毕竟官方文档写过“本规格鼓励但并不要求实现尾调用优化”有没有被栈溢出坑过——被坑过的眼神都带着沧桑一眼就能对上暗号所以问尾调用问“你究竟写没写过递归到爆栈的代码”。你要是只背概念面试官秒懂兄弟回去等通知吧。尾调用到底是个啥玩意儿不是所有递归都能叫尾调用别瞎套概念先放一段人话版定义尾调用就是函数的最后一步动作是“打个电话给别人并且自己立刻挂电话”。官方腔在函数返回阶段最后一个求值动作是“返回另一个函数的运行结果”。注意两个关键词最后一步return 后面除了函数调用啥也不能有1 都不行运行结果必须直接返回不能加料不能套娃不能return f(x) 1举个例子真假尾调用一眼看穿// 假的最后一步还做了加法引擎说你先别走functionfakeTail(n){if(n1)return1;returnnfakeTail(n-1);// 还有加法栈帧得留着}// 真的最后一个动作return 另一个函数调用当前栈帧可以原地爆炸functionrealTail(n,acc1){if(n1)returnacc;returnrealTail(n-1,acc*n);// 干净得像刚洗的秋裤}看到区别没假尾调用还要回来做加法引擎怕你把之前的栈帧扔掉后找不到回家的路只能留着。真尾调用把“后续没屁事”写在脸上引擎才敢把当前栈帧当场火化省出内存去刷抖音。手把手看代码普通递归 vs 尾递归一眼看出区别再也不怕白板写算法咱们拿阶乘开刀毕竟这老哥数学简单适合演示。先上普通递归经典爆栈款// 普通递归内存随 n 线性增长n 太大直接 stack overflowfunctionfact(n){if(n1)return1;returnn*fact(n-1);}console.log(fact(10));// 3628800没问题console.log(fact(1e5));// 浏览器Uncaught RangeError: Maximum call stack size exceeded再来尾递归版把乘法提前甩给累加器// 尾递归理论上只占用一个栈帧functionfactTail(n,acc1){if(n1)returnacc;returnfactTail(n-1,acc*n);}console.log(factTail(1e5));// 3628800...000数字太长但浏览器还是爆了等等不是说尾调用不爆栈吗别急这就引出一个大坑规范写了浏览器没实现。继续往下看。JS引擎真的会优化尾调用吗ES6标准写了但浏览器我选择性执行摊手2015 年 ES6 落地尾调用优化Proper Tail CallsPTC写进草案引擎厂商看完集体“哦”了一声然后——就没有然后了。到今天2026 年Chrome 和 Firefox 依旧默认关闭 PTC理由很政治正确报错堆栈会丢失调试难度999性能收益在真实业务里并不显著有了迭代器、async/await尾递归需求肉眼可见地下滑唯一亲儿子 Safari 倒是开了但前端圈有句老话“Safari 是新的 IE”。你敢只兼容 Safari 上线产品经理就敢让你兼容老板的新 MacBook。所以在浏览器里写尾递归该爆还是爆。真·优化得靠显式语法ES2022 引入的return.continue——哦不对那叫return.continue的提案早黄了。目前唯一靠谱的是Trampoline蹦床手动把递归撸成循环后面给代码。尾调用能省多少内存实测给你看栈溢出不存在的——前提是你写对了为了让你眼见为实我连夜写了段测试在 Node 20 里跑Node 也是 V8和 Chrome 一个德行默认不开 PTC// 测试工具不断加大 n看最大能跑到多少functiontest(fn,name){for(leti100;;i*10){try{constt0performance.now();constrfn(i);constcostperformance.now()-t0;console.log(${name}n${i}结果${r}耗时${cost.toFixed(2)}ms);}catch(e){console.error(${name}n${i}直接炸,e.message);break;}}}// 普通递归test(fact,普通递归);// 尾递归test(factTail,尾递归);输出节选普通递归 n100 结果9.3326e157 耗时0.15ms 普通递归 n1000 结果Infinity 耗时0.20ms 普通递归 n10000 结果Infinity 耗时1.30ms 普通递归 n100000 结果炸Maximum call stack size exceeded 尾递归 n100 结果9.3326e157 耗时0.12ms 尾递归 n1000 结果Infinity 耗时0.18ms 尾递归 n10000 结果Infinity 耗时1.10ms 尾递归 n100000 结果炸Maximum call stack size exceeded看明白没尾递归在 V8 里一样爆因为没优化。所以“省内存”目前只是理论福利生产环境别指望引擎救你自救吧。哪些语言真·支持尾调用优化JS、Python、Java 各自的态度大不同别被误导Scheme / Haskell尾调用是基本人权不写尾递归你都不好意思跟人打招呼JavaJIT 层面做了一波受限优化但官方文档一句没提全靠玄学PythonGuido 老叔直言“Python 不该写递归”默认不支持想玩请装 PyPyGo故意不做理由是“要保持堆栈完整方便排查 bug”JS规范写了浏览器懒得做Node 给你个--harmony-tailcalls旗子跑起来还各种隐形 bug所以别在简历里写“精通 JS 尾调用优化”面试官真让你跑个 1e6 递归当场社死。实际开发中真有人用尾递归吗函数式编程老炮儿天天用业务仔我 for 循环不香实话实说业务代码里几乎看不到。为啥浏览器不优化写了也白写可读性对大部分同事不友好维护成本1真·大数据量场景上来就分页、流式、WebWorker谁还递归但在函数式库里尾递归蹦床是标配Ramda、Immutable 内部大量蹦床保证不爆栈Redux 中间件里各种 compose理论深度 1e4 层靠蹦床保命所以写业务你可以 continue看源码你得懂。不然同事甩给你一段trampoline(f)(1e5)你还以为他在调用外星 API。写尾递归时踩过的坑全记录参数顺序错、返回值忘改、以为优化了其实没优化……累加器位置写反// 错误acc 乘到了 n 上结果直接升天returnfactTail(n-1,n*acc);// 应该 acc*n写反成 n*acc逻辑对了但阅读反人类中途 console.logfunctionlogTail(n,acc1){if(n1)returnacc;console.log(n);// 只要不是纯函数引擎就不敢优化尾调用失效returnlogTail(n-1,acc*n);}任何额外操作都会让尾调用灰飞烟灭包括 console、赋值、await。async/await 混搭asyncfunctionasyncTail(n,acc1){if(n1)returnacc;returnasyncTail(n-1,acc*n);// 看似尾调用实则返回 Promise栈帧一样留}Promise 内部还要回溯堆栈async 函数天生与尾调用优化八字不合。调试尾调用问题的骚操作怎么判断是不是真的尾调用了控制台断点组合拳安排上Step 1在函数入口console.trace()看调用栈长度是否随递归暴涨Step 2在 return 语句打断点栈帧如果只剩一个说明优化成功如果还能看见长长的“anonymous”优化失败Step 3用 Node 跑--inspectChrome DevTools 里看 Call Stack 是否扁平Step 4Safari 直接开 PTC对比同一段代码在 Safari 和 Chrome 的栈深度一目了然终极杀器写个计数器统计递归深度如果深度 1那就是真·尾优化如果深度随输入线性涨就是假把式。让尾调用更优雅的小技巧累加器模式、封装工具函数、配合 async/await 的奇思妙想蹦床模式——递归转循环自己掌握命运// 蹦床外壳把函数变成迭代functiontrampoline(fn){returnfunctiontrampolined(...args){letresultfn(...args);while(typeofresultfunction){resultresult();// 循环代替递归栈帧恒定}returnresult;};}// 写成分步函数返回 thunkfunctionfactTramp(n,acc1){if(n1)returnacc;return()factTramp(n-1,acc*n);// 返回新函数不是直接调用}constsafeFacttrampoline(factTramp);console.log(safeFact(1e6));// 再大的数也不爆就是跑得慢注释() ...是 Thunk蹦床的核心思想——用循环消费 Thunk 链表内存 O(1)。封装高阶函数业务代码零感知// 通用尾递归工厂functiontailRec(fn){returntrampoline(functionstep(...args){constcontfn(...args);returntypeofcontfunction?cont:()cont;// 统一返回 thunk});}// 业务仔只写逻辑不用管蹦床constsumtailRec(functionsumLoop(n,acc0){if(n0)returnacc;return()sumLoop(n-1,accn);});console.log(sum(1e6));// 用法跟普通函数一样同事看不出端倪async/await 的伪尾调用虽然 async 函数本身无法 PTC但你可以把递归甩给事件循环手动yieldasyncfunctionasyncFact(n,acc1){if(n1)returnacc;awaitnull;// 让出线程栈清空returnasyncFact(n-1,acc*n);}await null把剩余逻辑放进微任务每次递归栈都清零代价是速度变慢但胜在不爆栈适合 CLI 脚本。下次再被问尾调用反手甩他这段代码顺便问问“你项目里真用过吗”坏笑白板代码别写那种“浏览器跑不通”的尾递归直接上蹦床版面试官如果看不懂你就给他科普反向收割好感consttrampolinefn(...args){letresfn(...args);while(typeofresfunction)resres();returnres;};constfacttrampoline(functionf(n,acc1){if(n1)returnacc;return()f(n-1,n*acc);});console.log(fact(5));// 120写完补一句“当然生产环境我直接用 for 循环性能高、可读性强、还能断点调试——尾调用留给编译器去愁吧。”潜台词我懂原理但不盲目炫技务实主义正是前端人该有的自我修养。写到这儿字数已经超标我的咖啡也凉透了。尾调用这玩意儿理解归理解别神话。下次面试官再问你就把这篇文章甩过去让他也体验一把被尾递归支配的恐惧。哥先撤了还得去改需求——for 循环真香。欢迎来到我的博客很高兴能够在这里和您见面希望您在这里可以感受到一份轻松愉快的氛围不仅可以获得有趣的内容和知识也可以畅所欲言、分享您的想法和见解。推荐DTcode7的博客首页。一个做过前端开发的产品经理经历过睿智产品的折磨导致脱发之后励志要翻身农奴把歌唱一边打入敌人内部一边持续提升自己为我们广大开发同胞谋福祉坚决抵制睿智产品折磨我们码农兄弟专栏系列点击解锁学习路线(点击解锁知识定位《微信小程序相关博客》持续更新中~结合微信官方原生框架、uniapp等小程序框架记录请求、封装、tabbar、UI组件的学习记录和使用技巧等《AIGC相关博客》持续更新中~AIGC、AI生产力工具的介绍例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结《HTML网站开发相关》《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容入坑前端或者辅助学习的必看知识《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客共同构建用户界面。通过操作DOM元素、响应事件、发起网络请求等JS使页面能够响应用户行为实现数据动态展示和页面流畅跳转是现代Web开发的核心《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法同时收集精美的CSS效果代码用来丰富你的web网页《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素通过JavaScript及其提供的绘图API开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力使得前端绘图技术更加丰富和多样化《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅《python相关博客》持续更新中~Python简洁易学的编程语言强大到足以应对各种应用场景是编程新手的理想选择也是专业人士的得力工具《sql数据库相关博客》持续更新中~SQL数据库高效管理数据的利器学会SQL轻松驾驭结构化数据解锁数据分析与挖掘的无限可能《算法系列相关博客》持续更新中~算法与数据结构学习总结通过JS来编写处理复杂有趣的算法问题提升你的技术思维《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术涉及软件开发、网络建设、系统维护等领域的知识《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理只要是从事信息化相关行业的人员都应该掌握这些信息化的基础知识可以不精通但是一定要了解避免日常工作中贻笑大方《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧提升自我能力与面试通过率扩展知识面《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等《photoshop相关博客》持续更新中~基础的PS学习记录含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结日常开发办公生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具丰富阅历给大家提供处理事情的更多角度学习了解更多的便利工具如Fiddler抓包、办公快捷键、虚拟机VMware等工具吾辈才疏学浅摹写之作恐有瑕疵。望诸君海涵赐教。望轻喷嘤嘤嘤非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益纵其简陋未及渊博亦足以略尽绵薄之力。倘若尚存阙漏敬请不吝斧正俾便精进