做前端面试辅导这几年我收到最多的问题不是“这道题答案是什么”而是“面对这么多考点到底哪些才值得深学”。JavaScript知识体系太庞杂了从语言基础到浏览器原理从手写代码到性能优化随便拉一个列表都能列出一百多个点。但如果目的是应对面试而不是系统学习语言本身那核心考点其实就那么十几个关键是怎么把它们串成一个体系。这篇文章就是一份索引帮你把面试中考频最高、区分度最大的知识点按模块组织起来。每个模块会从面试官视角说明考点在测什么再讲清楚底层原理最后落到代码实现或应试策略。适合正在准备前端岗位面试、但不希望被海量散题淹没的读者。我不保证每道题都能押中但如果你能把这份索引里的内容吃透JavaScript相关的面试环节基本不会拉胯。1. 面试前的认知准备面试官到底在考察什么1.1 表面是考知识点实际在考三件事JavaScript面试题从来不是为了检验你会不会背某条定义。面过几十场前端岗位的面试后我发现面试官提每个问题背后都有更深的意图。第一是基础是否扎实。JavaScript很多高级特性异步、原型链、闭包在业务开发里可能一年都用不到一次但面试官需要通过这些点判断你对语言本身的理解深度。一个能讲清楚事件循环为什么这样设计的人和一个只会背输出顺序的人几句话就能分辨出来。第二是是否理解原理而不仅仅是使用。比如用框架写业务很简单但问到“状态更新为什么是异步的”“底层调度是怎么做的”很多人就卡住了。这部分对应的往往就是JavaScript的宏任务、微任务机制。索引中我把这类“用得少但问得多”的原理点都标了出来。第三是解决问题的思维过程。手写题环节面试官重点看的不是你最终写出的代码有多完美而是你面对一个需求时的思考链条先理解输入输出再考虑边界情况再优化实现。这也是为什么本文的每个模块都尽量还原了从问题到答案的推导路径。1.2 这份索引怎么用最有效我不建议把这份索引当单纯的背诵清单。它更合适的用法是“自查表”。你可以先花半小时通读一遍在纸上写下每个知识点你第一反应能说出多少内容。说不清楚、或者只能说出名词说不出的原理的就是你当前的薄弱项。然后针对薄弱项按模块深入去啃每隔几天回来重新自测一次。我整理索引时特意按照面试中提问的逻辑顺序排列先语言基础原型链、作用域、this再异步事件循环、Promise、async/await再函数与对象手写题、继承接着是细节陷阱类型转换、运算符最后是浏览器原理内存、性能。这个顺序和大多数面试从浅入深的提问节奏是匹配的。有一个比较现实的建议重点永远放在高频考点上。比如闭包、事件循环、Promise无论大厂还是中小团队几乎必问。而像“装饰器实现”“Proxy的13种拦截方法”这种偏门题除非你目标面试明确考得很深否则放在次要位置避免本末倒置。2. 语言基础三座大山原型链、作用域、this指向2.1 原型链不是死记硬背是整个对象系统的基石很多初学者学原型链就背一句话“沿着__proto__往上找直到null”。但面试中这道题往往不只是考这一个结论。你需要理解的是JavaScript对象系统的设计逻辑。当访问一个对象的属性时如果对象自身没有就会沿着它的原型链去找。这个机制让多个实例可以共享同一个原型上的方法和属性节省内存也让对象之间可以“继承”行为。经典考题是讲清下面这段代码的查找过程function Person(name) { this.name name; } Person.prototype.sayHi function() { console.log(Hi, Im this.name); }; const p new Person(张三); p.sayHi(); // 可以调用 p.toString(); // 也可以在 Object.prototype 上找到的p.sayHi()的查找过程是先看 p 自身有没有 sayHi没有再看 p 的__proto__也就是 Person.prototype有没有有调用它。而p.toString()是继续沿原型链往上在 Object.prototype 上找到。直到Object.prototype的__proto__为 null查找结束。面试官最容易追问的两个点第一new到底干了什么。可以用三行话概括创建一个新对象把它的原型指向构造函数的 prototype然后让构造函数以这个新对象为 this 执行。注意如果构造函数显式返回一个对象结果就是那个对象而不是新建的对象这个细节很容易被忽略。第二instanceof 的原理。A instanceof B检查的是 B.prototype 是否在 A 的原型链上而不是检查 A 是不是 B“造”出来的。这个理解能帮你解释一些匪夷所思的结果比如[] instanceof Object是 true因为数组的原型链上有 Object.prototype。提示手写代码题经常会把原型链和 instanceof 结合起来考让你实现一个 instanceof。思路就是不断取左边的__proto__跟右边的 prototype 比较。这个题能写对说明你对原型链的掌握是真的到位了。2.2 闭包与作用域面试官最爱深挖的环节闭包是JavaScript面试里出现频率最高的概念没有之一。但我发现一个很有意思的现象大多数人都能说出“函数内部访问外部变量”这个定义但说不清闭包的本质、优缺点和典型场景。从执行上下文的角度来看闭包产生的原因是函数定义时能“记住”它所在的词法作用域即使函数在其作用域之外执行也能访问那个作用域里的变量。内部函数引用了外部函数的变量外部函数执行结束后它的变量对象不会销毁因为被内部函数引用着这个“粘住”外部作用域的函数就叫闭包。面试中的高频考点私有变量。用闭包实现计数器、缓存、模块化隔离这是最经典的应用。function createCounter() { let count 0; return function() { count; return count; }; } const counter createCounter(); counter(); // 1 counter(); // 2这个场景里外部根本无法直接修改 count只能通过返回的函数操作实现了数据封装。循环与闭包的经典问题。这里几乎是必考题版本还越来越新for (var i 0; i 5; i) { setTimeout(() console.log(i), 100); }输出是 5 个 5因为var声明是函数作用域所有定时器的回调共享同一个 i循环结束时 i 已经是 5。改成let就输出 0 到 4因为let是块级作用域每次循环都会创建一个新的绑定。有些面试官会追问“如果用 var怎么不改动声明方式打印出 0-4”标准答案是再加一层闭包把每次循环的 i 作为参数传进去。闭包与内存。闭包本身不是内存泄漏但如果一个闭包长期存活又持有大对象引用垃圾回收就没法回收它。面试中可以说“闭包会延长外部变量的生命周期用完后应主动将不需要的引用置为 null”这句话背后就是垃圾回收机制的可达性分析。我看很多人面试时被问倒不是不知道闭包定义而是无法把闭包和实际场景关联起来。建议准备两三个真实场景比如防抖函数的封装、事件处理器的绑定、或者模块化私有状态面试时随手就能举例。2.3 this指向的四种绑定规则与箭头函数例外this 的指向问题几乎所有面试都会考而且经常和手写代码题结合。核心要掌握四种绑定规则按优先级排列new 绑定构造函数被 new 调用时this 指向新创建的对象。显式绑定通过 call、apply、bind 手动指定 this。隐式绑定方法作为对象属性调用时this 指向该对象。默认绑定非严格模式下指向全局对象严格模式下是 undefined。下面这道题很多面试官会拿来考你对优先级组合的理解function foo() { console.log(this.name); } const obj1 { name: obj1, foo }; const obj2 { name: obj2, foo }; obj1.foo(); // obj1隐式绑定 obj1.foo.call(obj2); // obj2显式绑定优先于隐式绑定 const bound foo.bind(obj1); bound.call(obj2); // obj1bind 的绑定不会被 call 改变 const instance new bound(); // undefined因为 new 绑定优先于 bind 绑定箭头函数是另一个必须讲清楚的点。箭头函数没有自己的 this它继承外层词法作用域的 this并且 call、apply、bind 都无法改变它。这个设计让回调函数里能保持外层 this不再需要 var that this 了。面试里也常考“事件监听器里的 this 和箭头函数里的 this 各指向什么”。我自己的体会是单看规则不难难在综合运用。建议练几道综合题尤其是“箭头函数 定时器 对象方法”组合出现的题把每步的 this 变化过程用文字写出来理解比背结论强太多。3. 异步编程体系事件循环、Promise与async/await3.1 事件循环机制输出顺序题的底层逻辑JavaScript 是单线程语言但又能处理异步任务靠的就是事件循环机制。这个机制是浏览器和 Node.js 都能运行的根基之一也是面试题中输出顺序题的唯一依据。浏览器里事件循环的核心是调用栈、宏任务队列和微任务队列的配合。任务类型典型来源执行时机宏任务script 整体代码、setTimeout、setInterval、I/O、UI 事件每轮事件循环取一个执行微任务Promise.then/catch/finally、queueMicrotask、MutationObserver当前宏任务结束后全部清空执行规则是重点。一次事件循环里先执行一个宏任务通常最开始是 script执行过程中产生的微任务都放进微任务队列宏任务结束后清空当前所有微任务微任务执行过程中产生的新微任务也会在本轮清空清空微任务后浏览器有机会执行 UI 渲染然后取下一个宏任务继续。微任务队列永远优先于宏任务队列。面试中经典输出顺序题一般是这套题的变体console.log(start); setTimeout(() { console.log(timeout); Promise.resolve().then(() console.log(promise in timeout)); }, 0); Promise.resolve().then(() console.log(promise1)); Promise.resolve().then(() console.log(promise2)); console.log(end);输出顺序是 start、end、promise1、promise2、timeout、promise in timeout。因为 script 是第一个宏任务同步代码先执行完然后清空微任务队列里的 promise1 和 promise2再取出下一个宏任务 setTimeout执行它内部的回调产生新的微任务但要等 timeout 这个宏任务结束后再清空。有些面试官会继续问 UI 渲染时机。记住一点微任务队列清空后、下一个宏任务开始前浏览器有机会进行渲染。这也是为什么大量微任务会阻塞渲染、造成页面卡顿。你可以补充“如果微任务无限产生宏任务永远执行不到页面就假死了”这是面试的高分答法。Node.js 端的事件循环多了几个阶段timers、pending callbacks、poll、check、close callbacks结构比浏览器复杂。如果岗位偏后端还可能会问 process.nextTick当前阶段后立即执行优先于 Promise 微任务和 setImmediatecheck 阶段执行的区别。如果岗位偏前端问到浏览器的就够用了。3.2 Promise核心考点状态、链式调用与静态方法Promise 是异步编程的核心也是面试手写题的重点。先看基本概念。Promise 有三种状态pending待定、fulfilled成功、rejected失败。状态只能从 pending 变为 fulfilled 或 rejected一旦改变就不能再变。这个“不可逆”特性消除了回调方式里最头疼的竞态问题保证结果只会被处理一次。链式调用的本质是每个 then 都会返回一个新的 Promise。这个设计让异步操作可以按顺序组合也天然解决了“回调地狱”的缩进问题。面试官一般会追一句“then 里返回值和下一个 then 接收值之间的关系”你需要说清楚then 回调里如果返回一个普通值会被 Promise.resolve 包装传给下一个 then。如果返回一个 Promise内部会等待这个 Promise 状态改变后再继续。如果抛异常会自动变成 rejected被 catch 捕获。这些规则也决定了手写 Promise 时最难的部分就是“处理返回值是 Promise 的情况”。静态方法里Promise.all 和 Promise.race 是最常考的两个。Promise.all等待所有 Promise 全部成功返回结果数组只要有一个失败整体立即进入失败状态失败原因是第一个失败的原因。经典场景是多个并行请求都完成后统一渲染。Promise.race只要有一个 Promise 状态改变整体就跟着改变。经典场景是超时控制比如请求在 3 秒内没完成就报超时。面试中还容易考 Promise.allSettled所有结果都返回不会因某个失败而中断和 Promise.any只要有成功就返回第一个成功全部失败才失败。这些 API 在工程中的适用场景有区别回答时要结合场景说明面试官会比较认可。经常被忽略的一个点是 Promise 的“吞错”问题如果 Promise 内抛错但没有人用 catch 处理这个错误不会直接显示在全局排查起来很麻烦。工程上建议始终给 Promise 链加上 catch或者配置全局的 unhandledrejection 监听。3.3 async/await 与错误处理async/await 本质上只是 Promise 的语法糖它让异步代码看起来像同步代码消除了一大票 .then 链。需要掌握的核心原理是await 会暂停当前 async 函数的执行让出控制权给事件循环等 Promise 状态确定后继续执行。这个“暂停”其实是用生成器加状态机实现的不过面试中一般不会要求到这个深度能说出“基于 Promise、本质是 Generator 语法糖”就够了。错误处理是 async/await 的必考点。核心原则用 try/catch 包裹 await或者给表达式整体加 catch。async function fetchData() { try { const data await api.get(/data); return data; } catch (err) { console.error(请求失败, err); return null; } }另一个高频考点是“多个异步操作的并发与顺序”。// 串行慢 const a await task1(); const b await task2(); // 并行快 const [a, b] await Promise.all([task1(), task2()]);很多候选人会把不依赖前一步的异步操作写成串行面试官就会追问“两个请求没有依赖关系可以怎么优化”。你要回答用 Promise.all 让它们并行执行并且说明这样可以显著减少总耗时。这是工程能力和原理理解的综合体现。还有一个值得准备的细节async 函数返回的是一个 Promise所以内部 return 的值会成为 resolved 的结果throw 的错误会成为 rejected 的原因。这一点在手写题中经常用到比如实现一个 sleep 函数const sleep (ms) new Promise(resolve setTimeout(resolve, ms));4. 函数、对象与继承高频手写题背后的功夫4.1 深拷贝的完整解法与层层深入深拷贝是手写题里的“明星选手”因为它考察面极广递归、类型判断、循环引用、Date 和 RegExp 处理、原型保留几乎把语言基础都串起来了。最基础的答案是用 JSON.parse(JSON.stringify(obj))。很多面试者只停在这一层但面试官的追问马上就会来函数、undefined、Symbol 类型的属性会被忽略。Date 会被转成字符串而不是 Date 对象。RegExp、Map、Set 得不到正确处理。循环引用会直接报错Converting circular structure to JSON。手写递归其实就是处理这些缺点的过程。核心思想是判断值的类型如果是引用类型就递归拷贝如果是基本类型直接返回。一个相对完整的实现至少需要处理循环引用用 WeakMap 记录已拷贝对象、支持数组、Date、RegExp、Map、Set再根据需求决定是否保留原型。我在面试中给过的简化版思路function deepClone(target, map new WeakMap()) { if (target null || typeof target ! object) { return target; } if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target); if (target instanceof Map) { const result new Map(); target.forEach((value, key) result.set(key, deepClone(value, map))); return result; } if (target instanceof Set) { const result new Set(); target.forEach(value result.add(deepClone(value, map))); return result; } if (map.has(target)) return map.get(target); const clone Array.isArray(target) ? [] : {}; map.set(target, clone); Reflect.ownKeys(target).forEach(key { clone[key] deepClone(target[key], map); }); return clone; }用 Reflect.ownKeys 是为了拿到包含 Symbol 在内的所有自身属性。操作中值得注意的点为什么用 WeakMap 而不是 Map因为这样可以避免对原对象产生额外强引用不干扰垃圾回收。这个细节如果能在面试中说出来是个加分项。4.2 继承的几种方案对比与 new 的原理继承在 ES6 时代看起来用 class 就够了但面试官还是偏爱问“不用 class 怎么实现继承”因为这样才考察你对原型链的理解深度。常见方案有四种继承方案原理优点缺点原型链继承子类原型指向父类实例写法简单方法可共享引用类型属性共享无法传参构造函数继承子类中调用父类构造函数属性不共享可传参方法各实例一份浪费内存组合继承上述二者结合方法共享且属性独立父类构造函数执行两次寄生组合式继承用中间对象连接原型兼顾所有优点需要额外封装理解成本高面试中如果让你写寄生组合式继承核心代码其实是让子类原型的原型指向父类原型避免多余调用父类构造函数function inherit(child, parent) { const prototype Object.create(parent.prototype); prototype.constructor child; child.prototype prototype; }这里的 Object.create 是关键函数它创建一个新对象并把新对象的原型指向指定对象。理解了 Object.create就理解了继承的本质复制一条原型链关系而不是去“new 一下”。面试中还容易考 new 的手写实现function myNew(Constructor, ...args) { const obj Object.create(Constructor.prototype); const result Constructor.apply(obj, args); return (typeof result object result ! null) || typeof result function ? result : obj; }这个过程和 new 的内部行为完全一致创建空对象、连接原型、绑定 this 执行、根据返回值决定返回什么。能写好这个就说明你对 new 的理解不是停留在表面。4.3 实战工具函数柯里化、防抖与节流这个模块与其说是原理不如说是手写题的“工具箱”。防抖、节流、柯里化在面试中出现频率极高而且经常以“现场写代码”的形式考察。先看防抖和节流二者的目标完全不同防抖debounce事件停止触发后一段时间后才执行如果期间再次触发就重新计时。适用场景是搜索框输入联想、窗口 resize 停止后统一处理。节流throttle规定时间间隔内最多执行一次适用于滚动事件、鼠标移动、按钮点击防连击。防抖的简单实现function debounce(fn, delay 300) { let timer null; return function(...args) { const context this; clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); }, delay); }; }节流的简单实现function throttle(fn, interval 300) { let canRun true; return function(...args) { if (!canRun) return; canRun false; const context this; fn.apply(context, args); setTimeout(() { canRun true; }, interval); }; }这里的 this 绑定问题非常值得注意。因为返回的函数被调用时内部 this 取决于调用方式直接写 fn(...) 会丢失 this用 fn.apply(context, args) 才能保证原函数体的 this 语义不丢失。这个细节面试官经常看。上面节流是“立即执行型”实际工具库里的 throttle 通常带 leading/trailing 配置能说出“还有首次立即执行、末次延迟执行的变体”会更深入。柯里化是把一个多参数函数变成一系列单参数函数的过程比如 add(1)(2)(3) 等于 6。它考察的核心是闭包和递归function curry(fn, ...args) { if (args.length fn.length) { return fn(...args); } return function(...newArgs) { return curry(fn, ...args, ...newArgs); }; }写这个题最容易忽略的是 fn.length它表示函数定义的形参个数。用剩余参数收集已传入的参数长度不够就继续返回函数长度够了就执行逻辑很清晰。5. 数据类型与运算符陷阱那些容易翻车的细节题5.1 typeof、instanceof 与类型判断的完整方案JS 的类型判断是一个大主题几乎每次面试都会以各种形式出现。typeof 是最基础的类型判断操作符它能区分基本类型和 function但对 null 和引用类型的区分很弱。typeof null 返回 object 是一个历史遗留 bug面试官大概率会问你要能自然地解释“这是第一版 JS 设计时的遗留问题为了兼容性一直没有修复”。instanceof 用于检查某个对象是否是某个构造函数的实例原理是检查构造函数的 prototype 是否出现在对象的原型链上。但它有局限基本类型不能用 instanceof 判断跨全局作用域比如 iframe 里的数组会失灵。操作符/方式适用场景局限typeof快速判断基本类型null 返回 object无法区分具体引用类型instanceof判断对象属于某构造函数无法用于基本类型受原型链变动影响Object.prototype.toString.call精确判断任意类型写法繁琐需要封装工具函数实际开发更推荐用 Object.prototype.toString.call() 做精确类型判断Object.prototype.toString.call(123); // [object Number] Object.prototype.toString.call(null); // [object Null] Object.prototype.toString.call([]); // [object Array]它的原理是读取对象内部的 Symbol.toStringTag 或默认类标识。这也引出一个追问题为什么用 call 而不是直接 toString因为很多对象重写了 toString借 Object.prototype 上的方法并手动指定 this才能拿到想要的标记。很多人面试栽在“为什么不直接 obj.toString()”就差这一点解释。封装一个 type 工具函数是很好的加分项根据 typeof、instanceof 和 Object.prototype.toString 三种手段的适用场景分别处理核心代码就三两行。面试时能现场写出来非常加分。5.2 隐式转换与经典魔鬼考题隐式类型转换是 JS 最容易出幺蛾子的地方面试官爱拿它当“送命题”。比较常见的经典题有[] ![]为什么是 true。null和undefined的相等比较规则。{} []和[] {}的不同结果。0 、 false、1 true的表现。理解的核心是的比较规则。简单版是这样null 和 undefined 相等且只等于对方。两边类型不同时优先转成数字比较。引用类型先通过 ToPrimitive 转成基本类型再参与比较。拿[].toString()来说数组转字符串是空串 所以[] 会转化为 成立。![]是 false[] false就变成 0而转数字是 0所以成立。整个链条要一步步说出来面试官才会认为你真的懂了。有个务实建议实际工作中尽量用 避免踩坑但面试时你得能解释 的规则因为那是语言特性的一部分。哪怕是老手我也不建议在业务代码里依赖晦涩的隐式转换。5.3 浮点精度与安全整数边界问“0.1 0.2 为什么不是 0.3”是经典细节题考的是对 IEEE 754 浮点数表示法的理解。核心原因JavaScript 的 Number 类型使用双精度浮点格式十进制的 0.1 和 0.2 在二进制里是无限循环小数存储时需要舍入产生误差。累加计算后自然不等于 0.3。解决方案一般是先放大为整数再计算或者用 toFixed 处理展示或者引入第三方精度库。还有一个配套考点是 Number.MAX_SAFE_INTEGER、Number.isFinite、Number.isSafeInteger 的作用以及为什么大整数超过 2^53 - 1 后会出现精度问题。ES2020 引入的 BigInt 就是为解决这个问题。面试回答里提一句“超过安全整数范围时用 BigInt”能得到不错评价。我实际用过的方法判断两个浮点数是否相等时不要直接用 看差的绝对值是否小于一个极小值比如 1e-10。需要精确计算金额时先乘 100 转成整数再计算。这两个技巧避免过很多次线上金额显示错乱的问题面试时讲出来会显得经验很实在。6. 浏览器视角内存泄漏、垃圾回收与渲染性能6.1 垃圾回收机制与常见内存泄漏场景前端面试到了中后段经常往浏览器原理方向扩展JavaScript 内存管理是绕不开的一环。先从垃圾回收说起。现代浏览器引擎以 V8 为例主要用标记清除算法垃圾回收器从根对象全局对象、当前调用栈、活动闭包等出发遍历所有可达对象标记它们没被标记的对象会被视为垃圾并回收。定期做标记清理、标记整理、分代回收等优化。闭包和外层引用不会被回收本质是它们仍然“可达”。这解释了为什么闭包会导致变量长期存活也解释了全局变量的危害。面试官常问四种典型内存泄漏场景意外的全局变量给未声明的变量赋值在非严格模式下会挂到全局上永远无法回收。被遗忘的定时器setInterval 或 setTimeout 的回调引用了某些对象即使组件已经销毁回调仍然存活。正确做法是组件卸载时清理定时器。事件监听器未移除DOM 元素被移除但监听器还挂在其他对象上元素就无法被回收。闭包引用大对象一个长期存活的闭包持有大对象引用导致它无法释放。处理方式是把不需要的引用手动置为 null。实际排查内存泄漏时我建议先用浏览器 DevTools 的 Performance 面板录制一段操作看内存曲线是否不断上升而不会回落再用 Memory 面板看堆快照比较两段时间的对象数量和大小。不一定要会看所有细节但至少要懂排查思路否则面试官会认为你只会背概念。6.2 从防抖节流延伸到渲染优化性能话题怎么聊第4章写了防抖节流的代码这里要把它们跟渲染优化串起来讲。面试官考察的往往不只是代码还有“你在工作里什么时候会用到它”。前端性能优化可以按渲染路径来拆解JavaScript 执行时间的缩短、DOM 操作的减少、布局和绘制的优化、资源的加载时机。JavaScript 层面最常见的优化就是防抖节流因为事件处理函数本身可能触发大量重排重绘。比如滚动事件里读取 scrollTop、修改样式如果不节流一秒钟触发几十次浏览器就被拖垮了。再往深走还有几个高频考点虚拟滚动只渲染可视区域的列表项而不是一次性渲染上万条 DOM。面试中聊大数据量列表几乎必问。核心是监听容器滚动计算当前可视区的起点和终点只创建该范围内的 DOM 节点。请求合并与缓存比如框架里连续多次修改数据会触发多次渲染框架层已经做了批处理但手动操作 DOM 的场景需要自己合并。requestAnimationFrame需要在下次渲染前更新 DOM 时用 rAF 会比 setTimeout 更平滑因为 rAF 的回调在浏览器渲染之前执行。有一个自己的体会面试聊性能优化不要只罗列手段要选一个真实场景完整讲清楚优化前后的变化。比如优化过一次长列表渲染从白屏 2 秒降到 200 毫秒这个说服力比背十个优化手段强十倍。7. 高频手写题实战演练7.1 手写 call、apply、bind 三件套这是手写题里最常出现的组合考察点包括this 绑定、参数处理、函数返回值、以及 bind 在 new 时的优先级。call 的实现思路是最巧的把函数临时挂到目标对象的属性上然后用 obj.xxx() 调用this 自然指向目标对象最后删掉临时属性。Function.prototype.myCall function(context, ...args) { context context ?? window; const key Symbol(fn); context[key] this; const result context[key](...args); delete context[key]; return result; };说明一下为什么用 Symbol避免 key 覆盖目标对象上已有属性。面试中如果不用 Symbol至少要想到用一个不太可能重名的 key并记得调用后 delete。apply 和 call 的区别就是参数以数组形式传入实现基本一样。bind 比 call 多一个特点返回一个新的函数这个函数可以作为构造函数使用。所以 bind 实现里要处理“new 时 this 失效”Function.prototype.myBind function(context, ...args) { const fn this; return function newFn(...newArgs) { if (this instanceof newFn) { return fn.call(this, ...args, ...newArgs); } return fn.call(context, ...args, ...newArgs); }; };核心判断是如果 newFn 被 new 调用了this 就是新对象此时应该让 this 生效而不是使用绑定的 context。这一点和原生 bind 行为一致面试官看到你能写出这个判断基本就认可你的理解了。注意手写题环节写代码的同时一定要把思路讲出来。面试官更希望看到你的推导过程而不是默默把代码写完。哪怕代码没完全写对思路清晰也比沉默着写对得分高。7.2 手写 Promise.all 的核心逻辑Promise.all 是手写题里的进阶款能写出来说明你对 Promise 状态机制和异步流程控制都掌握了。我的建议是分步骤写先写逻辑骨架Promise.myAll function(promises) { return new Promise((resolve, reject) { const results []; let remain promises.length; if (remain 0) { resolve(results); return; } promises.forEach((promise, index) { Promise.resolve(promise).then(value { results[index] value; remain--; if (remain 0) { resolve(results); } }, reject); }); }); };几个细节很关键用 Promise.resolve 把非 Promise 值包一层可以支持传入普通值。通过 results[index] 赋值确保结果顺序和传入顺序一致而不是按完成顺序。这一点面试中区分度很高。remain 计数减到 0 才 resolve保证所有 Promise 都成功。有一个失败直接 reject与原生 Promise.all 行为一致。如果面试官让加“超时控制”那就是结合 Promise.race 的考法。用一个 setTimeout 的 Promise 和真实请求竞争谁先改变状态谁决定结果。这个扩展建议准备手写题时练一下。7.3 数组扁平化与去重的多种解法数组相关的手写题覆盖面极广而且解法多样适合考察候选人的“一题多解”能力。数组扁平化常见有三种递进解法递归遇到数组就继续递归。flat 方法ES2019 自带 arr.flat(Infinity)但面试通常希望你会底层思路。循环加栈模拟避免递归深度过大。function flatten(arr) { const result []; const stack [...arr]; while (stack.length) { const item stack.pop(); if (Array.isArray(item)) { stack.push(...item); } else { result.unshift(item); } } return result; }去重也有多种解法从简单到进阶Set 一行版[...new Set(arr)]。双重循环加 splice实现简单但是性能较差。哈希表记录O(n) 复杂度。如果要求保持对象引用且去重对象数组Set 不够用需要自己用 Map 记录唯一标识。面试中考察这类题的意义不是你会多少种写法而是能不能分析每种解法的空间复杂度和时间复杂度以及根据需求选合适方案。建议把复杂度和适用场景一起准备。8. 备考策略如何在一个月内高效武装自己8.1 把散点知识连成网状结构我面过太多候选人也帮很多人做过模拟面试发现大家最大的问题不是不懂而是“知识散”。闭包会、事件循环会、原型链会但一问到“闭包和内存泄漏的关系”就卡住一问到“事件循环里微任务和渲染时机”就含糊。这说明知识之间没有建立连接。所以我的第一条建议是不要按考点孤立记忆按“为什么”去建立联系。每个知识点都问自己三遍为什么直到能解释背后的机制。比如闭包为什么能访问外部变量因为作用域链。作用域链为什么存在因为函数在定义时记录了词法环境。这样顺下去闭包、作用域、内存泄漏就串成了一条线而不是三个孤立答案。8.2 手写题分类训练与项目素材准备手写题要分类练、重复练。我建议把常见手写题分成四类分类高频题目训练要点this 绑定类call、apply、bind、new关注边界情况函数返回值、new 优先级异步控制类Promise 实现、Promise.all/race、sleep关注状态流转和并发控制数据处理类深拷贝、数组扁平化、去重、数组方法实现关注复杂度和特殊类型处理工程工具类防抖、节流、柯里化、事件总线关注调用方式和 this 绑定每类每天练两三题连续两周手感就有了。别贪多重点是每个题都要能边写边讲讲出每一步的逻辑。同时准备两三个完整项目里的深度话题。面试中问“你项目里最有挑战的事”你不能只说是哪个功能而要能一路讲到它涉及什么技术、为什么这么设计、踩过哪些坑、数据上发生了什么变化。比如长列表性能优化、复杂表单状态管理、前端权限控制都是比孤立知识点更有说服力的素材。8.3 模拟面试才是真正的练兵场面试前一周做两到三次模拟面试每次控制在 45 分钟以上最好有人追问。自己安静背书和在压力下快速组织语言完全是两种体验。我见过很多候选人知识储备没问题一紧张就大脑空白这种一定要靠模拟面试来克服。我记得有一次模拟面试候选人把 Promise 原理讲得很透但问到“那你项目里为什么不直接使用 Promise.all 并发请求而是串行请求”他突然愣住了。这个问题的价值不是答案本身而是检验他有没有在真实场景中思考过异步方案。所以准备面试题的时候多想想“这个知识点在我的项目里用得上吗怎么用的”这种连接会让你的回答明显比其他候选人更有层次。希望这份索引能帮你把零散的知识点串成体系。如果里面有一半内容你能不看资料讲清楚前端面试的 JavaScript 部分基本就不会拖后腿了。剩下的就是靠多次模拟面试把表达磨顺上场时稳住心态。