STUDY / FE
下载题目 · 下载答案 · 核验报告

FRONTEND / INTERVIEW NOTES

前端面试题清单

290道题 · 25 个主题

点击题目查看参考答案。手写题以解题思路为主,核验范围见报告。

01 / 25

高频手写精选

13 题
  1. 实现 Promise.all

    Promise.all:将可迭代对象转成数组,空数组立即兑现 [];逐个 Promise.resolve,按原下标保存结果,计数归零后兑现;任一拒绝立即拒绝,其他任务不会自动取消。

  2. JSON2DOM = react的render函数

    JSON2DOM:递归创建元素;文本节点用 createTextNode,元素用 createElement,属性需区分 className、事件和普通属性,再递归追加 children。React 的 render 还包含协调、状态与更新,不能等同于简单 JSON2DOM。

  3. 树形结构转成列表

    树转列表:深度优先遍历,把节点的 children 拆掉,输出 {...node, parentId};记录访问过的节点以防循环引用,保留顺序。

  4. 列表转成树形结构

    列表转树:先用 Map(id → 节点副本) 建索引,再按 parentId 挂到父节点 children;根节点单独收集。时间 O(n),需约定孤儿节点和重复 id 的处理。

  5. Array.prototype.flat

    flat(depth):递归或显式栈遍历;遇数组且剩余深度大于 0 则展开,否则加入结果。原生 flat 会跳过稀疏数组空位且不修改原数组。

  6. instanceof

    instanceof:先检查右侧对象的 Symbol.hasInstance,若存在可调用方法就由它决定结果;否则右侧必须是函数,沿左侧值的原型链查找该函数的 prototype。自定义方法也可能让基本值返回 true。

  7. call apply bind

    call/apply/bind:call 逐项传参,apply 传数组状参数,bind 返回固定 this 与前置参数的新函数。手写版还须处理严格模式、构造调用、原型和 length,临时属性方案并不完全等价。

  8. Array.prototype.map

    map:遍历 0..length-1,只在索引存在时调用回调 callback.call(thisArg, value, index, array),输出等长数组并保留空位;先检查回调可调用。

  9. 用正则实现模板字符串

    模板字符串替换:如 /\{\{\s*([\w.]+)\s*\}\}/g 匹配 {{user.name}},回调用安全的路径读取函数取值;若插入 HTML,需做上下文转义,不能 eval。

  10. lodash.get

    lodash.get:把点号、方括号或数组路径解析为键序列后逐层读取;最终解析值为 undefined 时返回默认值,路径不存在也同样返回默认值,不必区分两者。此题只读数据,不应额外执行路径表达式。

  11. 深拷贝

    深拷贝:普通结构优先 structuredClone;手写时用 WeakMap 处理循环引用,并针对 Date、RegExp、Map、Set、数组等逐类复制。函数、DOM 节点和原型行为须单独约定。

  12. 寄生组合式继承

    寄生组合继承:Child.prototype = Object.create(Parent.prototype, {constructor:{value:Child,writable:true,configurable:true}});在 Child 中 Parent.call(this, ...) 初始化实例属性,避免重复调用父构造函数。

  13. 发布订阅者模式

    发布订阅:维护 Map<事件, Set<回调>>;on 加入并返回退订函数,emit 用快照遍历,off 删除。约定回调抛错是否中断后续订阅者。

02 / 25

实现 Promise (hot)

7 题
  1. 完整实现Promise A+

    Promise/A+:实现 pending/fulfilled/rejected 三态、只能首次落定、异步执行 then 回调和 then 返回新 Promise;核心 resolvePromise 要处理 thenable、递归吸收、自解析拒绝与防止多次调用。A+ 不规定 finally/all,完整原生兼容还需遵循 ECMAScript 微任务语义。

  2. 实现Promise.prototype.finally

    finally:若 onFinally 不是函数,直接透传原结果;否则通过 then 在兑现/拒绝两条路径调用它,等待其返回值落定后继续传原值或原错误,清理失败则以新错误覆盖。简化版可用 Promise.resolve(onFinally()),完整兼容还须考虑 Promise 子类的 Symbol.species。

  3. 实现Promise.allSettled

    allSettled:对每项 Promise.resolve,按输入顺序存 {status:'fulfilled',value} 或 {status:'rejected',reason},全部完成才兑现;空输入兑现 []。

  4. 实现 Promise.race

    race:对每项 Promise.resolve(item).then(resolve,reject);第一个落定结果决定输出。空输入保持 pending。

  5. 实现 Promise.prototype.catch

    catch:等价于 this.then(undefined, onRejected);会返回一个新的 Promise。

  6. Promise.resolve

    resolve:Promise.resolve(value) 吸收 thenable;同构造器的 Promise 可直接返回。手写时防止 thenable 的多次落定和自引用。

  7. Promise.reject

    reject:Promise.reject(reason) 返回已拒绝的新 Promise,不吸收传入的 Promise。

03 / 25

Promise 周边场景题(hot)

31 题
  1. 交通灯

    交通灯:把每个灯封装成 await sleep(duration) 后切换状态,外层 while (!signal.aborted) 顺序运行;离开页面时取消计时器。

  2. 封装异步的fetch,使用async await方式来使用

    异步 fetch:async function request(url, options){ const r=await fetch(url,options); if(!r.ok) throw new Error(String(r.status)); return r.json(); };调用方用 try/catch,并支持 signal。

  3. repeat(console.log, 5, 1000)

    repeat(fn,5,1000):循环 5 次,每次 await sleep(1000) 后调用 fn;明确是否应立即执行第一次,并返回可取消句柄。

  4. 封装一个工具函数输入一个promiseA返回一个promiseB如果超过1s没返回则抛出异常如果正常则输出正确的值

    一秒超时:Promise.race([promiseA, timeoutPromise]) 可让等待方超时,但不能中断原任务;若能控制请求,应传 AbortSignal.timeout(1000)。

  5. 请求5s未完成就终止

    五秒未完成终止:创建 AbortController,setTimeout(() => controller.abort(), 5000),传 signal 给 fetch,finally 清除定时器;不要只做 Promise.race。

  6. 实现一个sleep函数

    sleep(ms):new Promise(resolve => setTimeout(resolve, ms));如需取消,再监听 AbortSignal 并清除定时器。

  7. js每隔一秒打印1,2,3,4,5

    每秒打印 1 到 5:for(let i=1;i<=5;i++){ await sleep(1000); console.log(i); },要立即输出 1 则把打印放到等待之前。

  8. 使用 setTimeout 实现 setInterval

    setTimeout 模拟 setInterval:在回调结束后再次 setTimeout(tick, interval);返回取消函数。此方式不会重叠,但间隔包含回调耗时。

  9. promise实现图片异步加载

    图片异步加载:new Promise((resolve,reject)=>{const img=new Image(); img.onload=()=>resolve(img); img.onerror=reject; img.src=url;});先绑事件再设 src。

  10. 使用Promise封装AJAX请求

    Promise 封装 AJAX:创建 XMLHttpRequest,注册 load/error/timeout/abort,open/send;只把允许的 HTTP 状态当成功,错误时拒绝,支持取消与超时。

  11. 我们能反过来使用 setinterval 模拟实现 settimeout 吗?

    setInterval 模拟 setTimeout:首次触发时立即 clearInterval(id) 再执行回调;可实现近似效果,但计时精度和页面节流仍受环境影响。

  12. 异步任务:依次发送3次网络请求,拿到服务器数据

    三次串行请求:for (const input of inputs) { const response = await fetch(...); results.push(await response.json()); };后一请求需要前一结果时用其结果构造参数。

  13. 实现网络请求超时判断,超过三秒视为超时

    三秒请求超时:fetch(url,{signal:AbortSignal.timeout(3000)});区分超时、用户取消和 HTTP 非 2xx 响应。

  14. promise中断请求

    Promise 无通用“中断”;Promise 只是结果容器。请求取消要用 AbortController 并把 signal 传给可取消的底层 API。

  15. 给定一系列的api,测量上传速度(实现的时候用的GET请求)并选择一个加载时间最短的api

    测量 API:对每个候选发独立请求,以 performance.now() 差值计时,处理超时/错误,再按成功请求耗时选最小;GET 测的是延迟与下载,不等于上传速度。上传速度须用实际 POST/PUT 字节数除以耗时。

  16. settimeout系统补偿时间

    补偿计时:记录计划执行时间 next += interval,每次使用 setTimeout(tick, Math.max(0,next-performance.now()));后台节流无法完全补偿。

  17. setTimeout准时

    setTimeout 无法保证“准时”;可按绝对时间重排定时器并在回调中计算漂移,主线程阻塞或浏览器节流时仍会迟到。

  18. 并发多个请求,返回先得到response的。函数输入为url数组,输出为第一个返回的response的结果

    并发请求取最先成功/最先完成需区分:Promise.race 取最先落定,Promise.any 取最先成功;请求完成后可取消其他 fetch。

  19. JS异步数据流,实现并发异步请求,结果顺序输出

    并发发起、顺序输出:先 const promises=urls.map(request),再按顺序 for (const p of promises) console.log(await p);提前为每项附拒绝处理,避免未处理拒绝。

  20. Promise串行

    Promise 串行:tasks.reduce((p,task)=>p.then(()=>task()), Promise.resolve());tasks 必须是函数,不能是已启动的 Promise。

  21. 处理高并发, 100 条数据,带宽为 10, 跑满带宽

    100 项、并发 10:维护最多 10 个 worker,各 worker 从共享下标领取下一任务,完成后继续领取;结果按输入下标存储,最后 await Promise.all(workers)。

  22. 设计一个简单的任务队列, 要求分别在 1,3,4 秒后打印出 "1", "2", "3";

    1、3、4 秒后打印:如果是相对起点,分别设置三个 setTimeout;若指相邻间隔,则顺序 await sleep(1000/3000/4000),需先确认题意。

  23. 实现有并行限制的 Promise 调度器

    并行限制调度器:队列保存任务函数,active < limit 时启动,任务在 finally 中递减 active 并启动下一项;add 返回该任务结果 Promise。

  24. 实现 Scheduler

    Scheduler:与上一题相同,核心是不超过并发上限、每项结果和错误独立传回,并处理空队列与同步抛错。

  25. 有并发限制的Promise.all(ts类型)

    TypeScript 版限流 all:签名可用 function limitAll<T>(tasks: Array<() => Promise<T>>, limit:number): Promise<T[]>;校验 limit > 0,worker 并发领取索引,按原顺序写结果,任一失败拒绝。

  26. 实现 如果上一次的没请求完,之后的就无响应

    忽略后续请求:用 inFlight 锁;若已在请求则直接返回既定结果(如 undefined),否则 try { return await request() } finally { inFlight=false }。需明确“无响应”是忽略还是复用当前 Promise。

  27. 使用 Promise 实现每隔三秒输出时间

    每隔三秒输出时间:循环 await sleep(3000); console.log(new Date());提供停止标志或 AbortSignal 以释放定时器。

  28. 使用 Promise 改写回调地狱

    回调地狱:先把每个 Node 风格回调接口包装为 Promise,再用 async/await 依赖顺序执行,并在 try/catch 统一处理失败。

  29. 设计一个函数,该函数的参数为可同时发送请求的大小,返回一个函数,该函数的参数为要请求的url。 实现的效果为,同时发送n个请求,当有请求返回后往请求队列里push新的请求,并输出刚刚结束的请求的返回值

    返回限流请求函数:内部维护 queue 和 active;调用时把 {url,resolve,reject} 入队,空位立即发请求,完成后输出结果、落定调用方 Promise 并继续取队列。

  30. Promise.retry 超时重新请求,并在重试一定次数依然失败时输出缓存内容

    重试与缓存:每次用独立超时信号请求,失败后最多重试 N 次,可指数退避;全部失败时返回事先存在的缓存,否则抛最后错误。非幂等写操作不能盲目重试。

  31. 写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b 的时间,然后写一个 myClear,停止上面的 mySetInterVal

    递增间隔:保存次数 n,每次执行后安排下一次 a+n*b 毫秒;myClear 设置停止标志并 clearTimeout(id)。首轮是等待 a 后执行。

04 / 25

JavaScript 常考手写题

77 题
  1. 产生一个不重复的随机数组

    假设从 [min,max] 取 n 个不重复整数:验证 n <= max-min+1,可先建候选数组再 Fisher–Yates 洗牌并取前 n 项;避免无限随机重抽。

  2. await async 如何实现

    async/await 可理解为把生成器的每一步包装成 Promise:next(value) 得到 {value,done},未结束时 Promise.resolve(value).then(step,throwStep),结束时兑现返回值。

  3. 使用递归完成 1 到 100 的累加

    sum(n)=n<=0?0:n+sum(n-1),所以 sum(100)=5050;实际大范围求和可用 n*(n+1)/2 避免栈溢出。

  4. 打印出 1~10000 以内的对称数

    遍历 1 到 10000,把数字转字符串并与反转字符串比较;“对称数”按十进制回文数解释,若 10000 包含与否按题目约定。

  5. 实现一个字符串匹配算法indexOf

    indexOf(text,pattern):外层枚举起点 0..text.length-pattern.length,内层逐字符比较,首次全匹配返回起点;空模式返回 0,无匹配返回 -1。

  6. 请实现一个模块 math,支持链式调用math.add(2,4).minus(3).times(2);

    链式 math:对象持有当前值,add/minus/times 修改值并返回 this,用 value() 取结果;示例需约定 add(2,4) 为初始加法,结果 (2+4-3)*2=6。

  7. 手写用 ES6proxy 如何实现 arr[-1] 的访问(滴滴2020)

    new Proxy(arr,{get(target,key,receiver){if(typeof key==='string' && /^-[1-9]\d*$/.test(key)){const i=target.length+Number(key);return i>=0?Reflect.get(target,String(i),receiver):undefined} return Reflect.get(target,key,receiver)}});必须先排除 Symbol 键,数组迭代等操作才不会因 Number(Symbol) 抛错。

  8. 有一堆整数,请把他们分成三份,确保每一份和尽量相等(11,42,23,4,5,6 4 5 6 11 23 42 56 78 90)

    三组尽量等和属于三路划分,通常是组合优化题。小数组可回溯搜索并剪枝,最小化三组和的最大差;大数组可按数值降序放入当前最小和组作近似,需说明不保证最优。

  9. 之字形打印矩阵

    假设按行“之”字打印:偶数行从左到右、奇数行从右到左;若题目指对角线之字遍历,需另定遍历方向。

  10. 数组中的最大值

    非空数组用 Math.max(...arr) 或 reduce((max,x)=>Math.max(max,x));大数组避免展开参数过多,空数组需约定返回值。

  11. 用尾递归实现斐波那契数列

    尾递归 fib(n,a=0,b=1){return n===0?a:fib(n-1,b,a+b)};JS 引擎不普遍保证尾调用优化,大 n 建议改循环或 BigInt。

  12. 实现简单路由

    简单路由:监听 popstate,调用 history.pushState 后主动渲染当前 location.pathname 对应视图;直达路径需服务器回退到 index.html。

  13. 封装一个localstorage的setItem和getItem方法

    setItem 用 JSON.stringify,getItem 用 JSON.parse;包装 try/catch 处理禁用存储、配额与坏 JSON,并约定缺失值为 null。

  14. 1-1000回文数

    同第 4 题,限定 1 到 1000:String(n) === [...String(n)].reverse().join('')。

  15. 随机生成字符串

    若用于口令/令牌,用 crypto.getRandomValues 取随机字节,并通过拒绝采样映射到字符表以避免取模偏差;Math.random 仅适合非安全用途。

  16. 判断一个字符串是否为驼峰字符串, judge('ByteDance','BD') -> true judge('Bytedance','BD') -> false

    按大写字母提取缩写:[...s].filter(ch=>/[A-Z]/.test(ch)).join('')===abbr;ByteDance 得 BD,Bytedance 得 B。此定义仅适用于 ASCII 驼峰。

  17. 压缩字符串

    例如游程编码:连续相同字符写成“字符+次数”,aaabb → a3b2;需约定数字或分隔符出现在原文时如何消歧。

  18. Map 场景题:按 categories 将商品归类,输出每个分类下的商品名称

    原讨论给出商品 {name,categories} 数组,目标是按分类反向归组。用 Map<分类, 商品名称数组>,遍历每个商品的每个分类并追加名称,最后转为 [{name:'热门',categories:['可乐','苹果']}, ...];分类首次出现的顺序可由 Map 保留。

  19. 输入50a6we8y20x 输出50个a,6个we,8个y,20个x

    需要给多字符片段定义分界,否则 50a6we8y20x 有歧义。若约定“数字后直到下一段数字前全是重复片段”,用 /\d+\D+/g 分段,分别取数量和片段后 repeat。

  20. 手写defineProperty

    原讨论实际在探讨用 Object.defineProperty 实现属性监听:遍历对象自有键,为每个键定义 get 收集依赖、set 在值变化时通知订阅者,并递归监听新赋入的对象。需说明新增/删除属性和部分数组操作无法仅靠现有属性的 getter/setter 自动捕获;若题意是“复刻原生 defineProperty”,纯 JS 无法完全实现。

  21. String string 值一样返回true Object Object 返回true function function 都是声明的一个新的变量 返回false

    String 原始值按值相等;对象、函数按引用相等。两个不同对象即使结构相同也不相等;同一引用的对象或函数相等,故原题“Object Object 返回 true”要明确是否同一引用。

  22. 对输入的字符串:去除其中的字符'b';去除相邻的'a'和'c'

    先删除所有 b,再反复删除相邻 ac,直到稳定;若删除会产生新邻接,可用栈在线处理,每见 c 且栈顶为 a 则弹出。

  23. 用一行代码,将数组中的字符串和字符串对象(new String(123))直接判定出来

    arr.filter(x => typeof x === 'string' || x instanceof String);跨 realm 字符串对象时 instanceof 不可靠,可用 Object.prototype.toString.call(x)==='[object String]'。

  24. 实现 before(n, fn):fn 最多执行 n 次,此后返回最后一次结果

    按原讨论中的示例,闭包记录次数和最近结果;前 n 次调用执行 fn 并保存结果,之后直接返回最近结果。注意这与 lodash 的 before(n,fn) 只执行前 n−1 次不同,必须按本题示例实现。

  25. 实现一下 console.log

    原讨论中的实现是包装调用:function myLog(...args){ console.log(...args) };若要求从零实现浏览器 DevTools 控制台,纯 JS 无法做到,需区分这两种题意。

  26. 实现(5).add(3).minus(2)功能

    可给 Number.prototype 定义 add/minus 返回普通数,但污染全局原型且与装箱有关;更稳妥是 num(5).add(3).minus(2).value(),结果 6。

  27. 将十进制数字转为二进制数字字符串

    非负安全整数可用 n.toString(2);手写时不断取 n%2 并整除 2,逆序拼接,0 返回 '0'。负数的补码位宽需另外指定。

  28. 封装remove child.remove()销毁自身

    child.remove() 已是标准 DOM API;兼容写法 child.parentNode?.removeChild(child)。删除节点前应解除应用自行注册的外部监听或资源。

  29. 字符串中字母的出现次数

    遍历字符串,用 Map 或对象计数;“字母”是否含 Unicode、是否区分大小写需约定。

  30. 输出一个等腰三角形

    高度 n 的第 i 行输出 n-i 个空格加 2*i-1 个星号;用 repeat 拼每行。

  31. 实现一个函数a,使其奇数次调用时返回1,偶数次调用时返回2(不能使用全局变量)

    闭包保存调用次数:const a=(()=>{let n=0;return ()=>++n%2?1:2})();无需全局变量。

  32. 从数组中找出最接近目标数的值

    给定非空数组 values 与目标数 target,遍历并保留 Math.abs(x-target) 最小的元素;原讨论的例子目标为 37,[3,56,56,23,7,76,-2,345,45,76,3] 的结果是 45。距离相同保留先出现者,空数组需另定返回或抛错。

  33. 不用循环求和

    若求 1 到 n,直接 n*(n+1)/2;若是任意数组且禁用循环语句,可用递归或 reduce,但底层仍会遍历。

  34. 连续赋值操作

    a=b=c=1 从右向左赋值;a.x = a = {} 会先确定左侧引用,再计算右侧赋值,可能导致旧对象的 x 指向新对象。需给出具体表达式才能唯一推导。

  35. 输入一串字符串,根据字符串求出每个字母的数量并返回结果对象。(数字为1时可省略

    遍历字符串计频并返回对象,例 aab → {a:2,b:1};“数字为 1 可省略”暗示输出可能是编码字符串,原题格式不完整。

  36. 创建包含10个1的数组 多种方法

    Array(10).fill(1)、Array.from({length:10},()=>1)、[...Array(10)].map(()=>1);注意 Array(10).map(()=>1) 因空位不会执行回调。

  37. 将 ['zm', 'za', 'b', 'lm', 'ln', 'k'] 按首字母分组,并将组内字符串排序

    遍历字符串,以首字母为键收集到对象或 Map,再对每个分组调用 sort();示例输出 {b:['b'],k:['k'],l:['lm','ln'],z:['za','zm']}。原讨论已补充这一目标。

  38. 将升序整数数组 [0,1,2,4,5,7,13,15,16] 压缩为 ["0->2", "4->5", "7", "13", "15->16"]

    输入必须是升序整数数组:扫描每段连续的 +1 区间,记录起点和终点;单个数输出自身字符串,连续段输出 起点->终点。给定输入应得到题目所示五段;空数组返回 [],重复数如何处理需另定。

  39. ['ab', 'c', 'ab', 'd', 'c'] => ['ab1', 'c1' ,'ab2', 'd', 'c2']

    先计每个值总出现次数,再遍历维护已出现次数;仅重复的值追加序号,得到题示结果。

  40. 移除空属性

    过滤对象条目;“空”需定义。常见仅删 null/undefined/'':Object.fromEntries(Object.entries(obj).filter(([,v])=>v!==null&&v!==undefined&&v!==''))。

  41. 判断两个对象是否相等

    先定义浅比较还是深比较。深比较需处理类型、键集合、数组顺序、循环引用、Date/Map/Set 和原型;通常用配对 WeakMap 避免无限递归。

  42. 一个数组,找出每个数组元素右侧第一个比当前数大的数的下标,时间复杂度O(N)

    单调递减栈保存未找到答案的下标;遍历新元素时反复弹出比它小的下标并填当前下标,剩余填 -1;时间 O(n)。

  43. 寻找出现次数最多的三个标签

    用 Map 计频,再按次数降序、平局按字典序排序取前 3;数据量大可用容量 3 的最小堆。

  44. 素数

    n>1 且不存在 2..sqrt(n) 的整数因子即素数;可跳过偶数,生成区间素数可用埃氏筛。

  45. 实现日期格式化函数

    用 Date 取年月日时分秒,padStart(2,'0') 补齐,按令牌替换;明确使用本地时区还是 UTC,避免把月份从 0 开始当成 1。

  46. 实现 jsonp

    JSONP 仅能 GET:创建唯一全局回调名,拼入 URL 查询参数,插入 <script>;回调或出错/超时后清理脚本和全局函数。只能向可信源使用,响应会执行脚本。

  47. 将 www.baidu.taobao.com 按点号分段反转为 com.taobao.baidu.www

    原讨论要求反转点号分隔的域名片段,而不是反转字符或 URL 路径:www.baidu.taobao.com → com.taobao.baidu.www。可用 split('.').reverse().join('.');若禁用这些方法,就从右向左扫描片段并拼接。

  48. 解析 URL Params 为对象

    new URL(url).searchParams 解析,重复键可用 getAll 保留数组;URLSearchParams 会处理百分号解码。不能把 ?a=1&a=2 默默丢成单值。

  49. 调用计数器(支持重置)

    闭包持有计数,count() 增加并返回值,count.reset() 将其设回初值;不暴露可随意修改的全局状态。

  50. 颜色生成

    原题未说明颜色格式。若生成随机 #RRGGBB,从 crypto.getRandomValues(new Uint8Array(3)) 取 3 字节,各自转两位十六进制后拼接;若仅装饰用途也可用 Math.random。

  51. JavaScript怎么清空数组

    arr.length=0 会原地清空并影响同一数组的其他引用;arr=[] 只是让当前变量指向新数组;splice(0) 也会原地清空。

  52. 判断A、B数组的包含关系(值和数量),A属于B返回1,B属于A返回2,两者相等返回0,其他返回-1

    用计数 Map 比较每个值出现次数。两边计数完全相同返回 0;A 计数均不大于 B 返回 1;B 属于 A 返回 2;否则 -1。对象元素按引用比较。

  53. 对象的合并

    浅合并用 {...a,...b} 或 Object.assign({},a,b),后者覆盖同名键;深合并需单独规定数组、空值和循环引用策略,避免原型污染。

  54. 实现一个可无限延伸的数组

    若题意是按需得到无限序列,可用生成器 function* numbers(){let i=0;while(true) yield i++},逐次 next() 而不一次存入内存;原讨论也有用 Proxy 为越界索引提供默认值的不同理解。JS 数组本身不能真正占用无限内存,仍需明确目标接口。

  55. 将按空格分列、换行分行的多行字符串转为二维数组

    原讨论的输入是以换行分行、连续空白分列的数字文本,可用 s.trim().split(/\r?\n/).map(line=>line.trim().split(/\s+/)) 得到字符串二维数组;若要数字,再对每项用 Number 转换。空输入需单独返回 []。

  56. 请实现一个通用的Array解构赋值

    JS 已有 [a,b]=iterable;手写通用解构要遵守迭代协议、默认值、剩余元素和迭代器关闭规则。仅针对数组可按下标取值,但不等价于通用解构。

  57. 数组合并

    浅合并 [...a,...b] 或 a.concat(b);是否去重、是否深拷贝元素另行约定。

  58. 数组交集,并集,差集

    集合语义先用 const sa=new Set(A), sb=new Set(B) 去重;交集 new Set([...sa].filter(x=>sb.has(x))),并集 new Set([...sa,...sb]),差集 new Set([...sa].filter(x=>!sb.has(x)))。若题目要求保留重复次数,则改用计数 Map。

  59. 多维数组全排列

    多维数组笛卡尔积:arrays.reduce((acc,arr)=>acc.flatMap(prefix=>arr.map(x=>[...prefix,x])),[[]]);任一维为空则结果为空。

  60. 判断对象是否存在循环引用

    DFS 用“当前递归路径”WeakSet 检测回边;退出节点时删除。仅用全局 visited 会把共享引用误判为循环。

  61. 找出数组中最小的 k 个数,并保持它们在原数组中的相对顺序

    先取最小 k 个数的多重计数,再原顺序遍历数组,遇到仍需的值就加入并减计数;排序阈值法时间 O(n log n),可用选择算法优化。

  62. 逆对象扁平

    假设把点路径键还原为嵌套对象:按 . 分割路径逐层创建对象并赋值;要处理数组索引、路径冲突并拒绝 __proto__/constructor/prototype。

  63. 对象扁平化

    DFS 遍历对象并把路径连接成 a.b 键;数组可选择索引路径。循环引用、点号本身出现在键中、空对象都需约定表示法。

  64. 实现 执行一次的函数

    once(fn) 用闭包存 called 与 result,第一次 fn.apply(this,args),之后返回缓存值;若函数抛错,通常不标记成功,以便下次重试。

  65. 链式调用

    方法返回 this 或新包装对象以继续调用,最后用 value() 取结果;需明确是可变链还是不可变链。

  66. 偏函数

    偏函数先保存部分参数,调用时合并后续参数并以调用时 this 调原函数;如 partial(fn,...bound){return function(...rest){return fn.apply(this,[...bound,...rest])}}。

  67. 实现管道函数

    管道函数按从左到右执行:fns.reduce((value,fn)=>fn(value),initial);要支持异步时用 Promise 链。

  68. 手写事件代理(委托)

    在共同父节点监听事件,使用 event.target.closest(selector) 找匹配子元素,并确认它仍属于父节点;适用于动态子元素,卸载时移除监听。

  69. 数据类型判断

    typeof 识别基本值与函数,但 typeof null==='object';对象具体类型可用 Object.prototype.toString.call(value),跨 realm 和 Symbol.toStringTag 需注意。

  70. 类数组转数组

    Array.from(arrayLike) 或 Array.prototype.slice.call(arrayLike);前者同时支持可迭代对象并可映射。

  71. 预加载

    图片预加载可创建 Image 并设 src,关键资源可用 <link rel="preload">;仅预加载确实马上会用的资源,避免争抢带宽。

  72. 图片懒加载

    优先原生 <img loading="lazy">;需自定义进入视口行为时用 IntersectionObserver,加载后取消观察并处理失败占位。

  73. 数组去重

    基本值去重 Array.from(new Set(arr));对象按引用去重,按字段去重用 Map(key → item) 并明确保留首项还是末项。

  74. 防抖 节流

    防抖在连续触发停止一段时间后执行;节流在窗口内最多执行一次。实现时保存计时器/上次时间,提供 cancel,并处理首次与末次执行需求。

  75. 函数组合 compose redux-saga koa 洋葱模型

    普通 compose(f,g)(x)=f(g(x)) 从右往左;Koa 洋葱模型是中间件 await next() 前后包裹下游,需防止 next 被调用两次。

  76. sum(x,y)和sum(x)(y)

    可让 sum(x,y) 在第二参存在时直接返回和,否则返回 y=>x+y;判断参数个数用 arguments.length,避免把合法的 0 当作缺失。

  77. curry柯里化

    柯里化把多参数函数拆成逐步接收参数的函数,收集到原函数形参个数后执行;默认参数、剩余参数会影响 fn.length,需约定完成条件。

05 / 25

设计模式相关

1 题
  1. 观察者模式

    观察者模式:被观察者持有观察者集合,状态变化时逐个通知;与发布订阅相比,观察者通常直接订阅特定对象,发布订阅常通过事件中心解耦双方。退订和通知中抛错策略要明确。

06 / 25

树-场景题(hot)

9 题
  1. DOM2JSON

    DOM2JSON:递归读取 nodeType;文本节点输出文本,元素节点输出小写标签、选定属性和 childNodes。避免读取事件处理器、私有数据或过大的 DOM 子树。

  2. 计算目录树的深度

    树深度:空树为 0,叶子为 1,depth(node)=1+max(depth(child));极深树可用显式栈避免调用栈溢出。

  3. 树形结构获取路径名

    路径名:DFS 时传入祖先路径数组,到目标节点返回 [...path,node.name];需要返回所有匹配时不要找到首项就终止。

  4. 对象树遍历

    对象树遍历:DFS 可用栈,BFS 可用队列;对每个节点处理后遍历 children,用 WeakSet 防循环。

  5. 获取树对象属性

    “获取树对象属性”缺少目标结构。若按指定键收集属性,遍历节点并 result.push(node[key]),未定义值按需求跳过。

  6. 查找json中的children路径

    查找 children 路径:递归访问子节点,维护索引或名称路径;命中目标后返回路径,重复目标时需决定返回一个还是全部。

  7. 对象字符串转化成树形结构

    对象字符串转树:若输入是 JSON 字符串,先 JSON.parse,再验证节点结构;若是缩进文本,按缩进层级用栈构树。原题缺少样例,无法确定格式。

  8. 判断有无符合路径和 -> 打印所有路径

    DFS 累加沿途数值,命中目标和时输出当前路径;负数存在时不能仅凭当前和大于目标剪枝。

  9. 获取树结构中的name:getName

    DFS 遍历每个节点,收集 name;若仅取目标节点名称,应先说明按 id、路径还是其他条件查找。

07 / 25

实现 JS 原生方法

20 题
  1. Array.prototype.forEach

    forEach:转换接收者为对象,取长度快照,只访问存在的索引,调用 callback.call(thisArg,value,index,object);返回 undefined。

  2. Array.prototype.filter

    filter:同样跳过空位,回调为真时将原值追加到新数组;结果数组是密集的。

  3. Array.prototype.reduce

    reduce:有初始值从索引 0 开始;无初始值时查找首个存在的索引作为累加器,空数组且无初始值抛 TypeError。

  4. Array.prototype.fill

    fill(value,start,end):把负索引换算成从末尾计,范围截在 [0,length],逐个写入原数组并返回原数组;会填补空位。

  5. Array.prototype.includes

    includes:按 SameValueZero 比较,因此 NaN 可找到,+0/-0 相等;空位按 undefined 处理,支持负起始索引。

  6. Array.prototype.push

    push(...items):从当前 length 起写入各项,更新长度并返回新长度;数组接近最大长度时可能抛错。

  7. Array.prototype.unshift

    unshift(...items):从后向前移动旧元素,再写入新元素,返回新长度;需保留原有空位。

  8. Array.prototype.copy

    标准数组原型没有 copy 方法;可能指 copyWithin:把数组内区间复制到目标位置,重叠时选择正确方向,原地修改并返回数组。

  9. Array.prototype.getLevel

    标准数组原型没有 getLevel;若指数组嵌套深度,可递归求 1+max(子数组深度),空数组/非数组的基准层数先约定。

  10. 实现es6的set集合

    Set 可用 Map 模拟增删查与迭代,值比较需用 SameValueZero 且维持插入顺序;直接使用对象键会把不同对象混为一谈。

  11. 实现es6的map集合

    Map 可用原生 Map 作为说明其行为的参考;纯 JS 完整复刻需对象键身份、插入顺序、迭代器与删除再插入语义,不能只用普通对象代替。

  12. String.prototype.zpadStart

    zpadStart 非标准方法;若意为左补零,String(value).padStart(targetLength,'0'),超过长度保持原字符串。

  13. Object.assign

    Object.assign 逐个源对象拷贝自身可枚举的字符串与 Symbol 键到目标,会触发 getter/setter,浅拷贝并返回目标;null/undefined 源跳过。

  14. Object.is

    Object.is 类似严格相等,但 NaN 与自身相等,+0 与 -0 不相等;可用 x===y ? x!==0 || 1/x===1/y : x!==x && y!==y。

  15. JSON.stringify

    JSON.stringify 要处理转义、数组和对象、toJSON、replacer、缩进、循环引用及 undefined/BigInt 等规则;不宜声称简单递归版等价于原生。

  16. JSON.parse

    JSON.parse 需词法分析与递归下降解析字符串、数字、布尔、null、数组和对象,并拒绝非法语法;绝不使用 eval 解析不可信 JSON。

  17. trim

    trim:删除字符串两端的 ECMAScript 空白和换行字符,不改变中间内容;/^\s+|\s+$/g 是常见近似实现。

  18. 实现new

    new:创建以 Constructor.prototype 为原型的对象,用该对象调用构造函数;若构造函数返回对象/函数就采用返回值,否则返回新对象。箭头函数不能被 new。

  19. String.prototype.repeat

    repeat(n):验证 n 为非负有限数并转整数,重复字符串 n 次;可用倍增拼接减少次数,过大输出应抛范围错误。

  20. String.prototype.includes

    字符串 includes(search,position) 可用 indexOf(search,position)!==-1,但需拒绝正则入参,并处理位置转换与空串。

08 / 25

JS 库函数实现

1 题
  1. lodash.chunk

    chunk(array,size):要求 size 为正整数,循环 i += size 并 slice(i,i+size);空数组返回 [],不修改输入。

09 / 25

JS 工具函数

1 题
  1. 判断一个对象是否是isPlainObject

    isPlainObject:先排除 null 和非对象,再检查原型为 Object.prototype 或 null;跨 realm 对象需要更谨慎,不能仅看 constructor === Object。

10 / 25

手写 nodejs 模块

1 题
  1. promisify

    promisify(fn):返回函数,在原参数后追加 Node 风格 (err,value)=>err?reject(err):resolve(value) 回调;调用原函数时保留 this,同步抛错也应拒绝。

11 / 25

正则相关

9 题
  1. 正则判断手机号,qq,颜色,邮箱

    手机号、QQ、颜色、邮箱只能按明确业务规则写正则;例如 CSS 六位十六进制色 ^#[0-9a-fA-F]{6}$。手机号号段会变化,邮箱完整规范不适合单个简短正则,优先格式初筛加实际验证。

  2. 字符串的大小写取反

    遍历字符,将小写字母转大写、大写转小写,其他不变;只针对 ASCII 可用 /[a-zA-Z]/g。

  3. 检验字符串首尾是否含有数字

    ^\d|\d$ 测首或尾为数字;若要求首尾都为数字,用 ^\d.*\d$ 并单独处理单字符数字。

  4. 去除字符串空格

    只去两端空格用 trim();删除全部空白可用 replace(/\s/g,''),仅普通空格用 / /g。

  5. 去除字符串中的字母

    ASCII 字母用 replace(/[A-Za-z]/g,'');Unicode 字母可考虑 /\p{L}/gu,需目标环境支持。

  6. 正则 驼峰转换

    驼峰转下划线:先处理连续大写缩写与单词边界,再 /([a-z0-9])([A-Z])/g 插 _ 并转小写;仅简单正则会把 XMLHttp 拆错。

  7. 对象key的驼峰转下划线

    递归转换对象键,同时保留数组结构;只处理自身属性,遇循环引用用 WeakMap,并约定键冲突时如何处理。

  8. 判断字符串中是否存在连续的三个数

    若指三个连续数字字符,用 /\d{3}/;若指数值连续递增如 123,需逐位比较相邻数字差是否为 1。

  9. ((2+3)+(3*4))+2---->['(2 + 3)+(3 * 4)', '2 + 3', '3 * 4']

    用栈匹配左右括号,记录每对括号对应的原串范围,从内到外抽取;只用正则难以处理任意嵌套层数,输出的空格格式另定。

12 / 25

排序算法

10 题
  1. 冒泡排序

    冒泡:反复比较相邻元素,逆序则交换;一轮无交换可提前结束。平均/最坏 O(n²),稳定。

  2. 选择排序

    选择:每轮找到剩余元素最小值并与当前位置交换;O(n²),通常不稳定。

  3. 快速排序

    快排:选枢轴,把小于与大于枢轴的元素分区,再递归两边;平均 O(n log n),最坏 O(n²),原地版通常不稳定。

  4. 插入排序

    插入:依次把当前元素插入前面已排序部分;O(n²),近乎有序时接近 O(n),稳定。

  5. 归并排序

    归并:二分递归排序后合并两个有序序列;O(n log n),额外 O(n) 空间,可稳定。

  6. 基数排序

    基数:按个位、十位等从低到高做稳定分桶;适用于整数或固定编码键,时间与位数及基数有关,负数需分开处理。

  7. 计数排序

    计数:统计有限整数范围内各值频次,前缀和可用于稳定输出;时间 O(n+值域),值域大时不适合。

  8. 希尔排序

    希尔:按递减 gap 对各组做插入排序,最后 gap=1;性能依赖 gap 序列,通常不稳定。

  9. 桶排序

    桶:把值映射到若干桶,桶内排序后依次拼接;分布均匀时较快,偏斜时可能退化,需明确桶边界。

  10. 堆排序

    堆:建最大堆后反复把堆顶换到末尾并下沉;O(n log n)、额外 O(1) 空间、不稳定。

13 / 25

自定义 Hooks 手写题

7 题
  1. Hooks怎么封装手势逻辑

    手势 Hook 在 pointerdown/move/up/cancel 记录起点、位移和时间,计算方向/速度,设阈值避免误触;在 effect 清理监听,并考虑触屏滚动冲突与键盘替代操作。

  2. Hooks实现移动端的滑动轮播插件

    轮播 Hook 管理当前索引、拖动位移和动画状态;手势结束按距离/速度切页,索引循环,自动播放计时器在悬停、失焦及卸载时暂停/清理。

  3. 如何用 Hooks 模拟 componentDidMount 与componentWillUnmount

    useEffect(()=>{ setup(); return cleanup; },[]) 对应挂载后的副作用和清理;开发模式 Strict Mode 可能执行“建立→清理→建立”检查,不能假设只运行一次。

  4. 实现一个useBodyScrollLock ,当出现弹窗时 阻止背景滚动

    弹窗打开时在 effect 保存 body.style.overflow 原值并设 hidden,清理时恢复;多个弹窗并存需共享锁计数,移动端还要处理滚动穿透与焦点管理。

  5. ts 实现 hooks: useInterval

    useInterval(callback,delay) 用 ref 保存最新回调,effect 随 delay 创建/清除 interval;delay=null 暂停,避免闭包读到旧状态。

  6. 实现 useQuery

    useQuery 需先定义是读取 URL 参数还是数据查询。URL 场景可订阅路由变化并从 URLSearchParams 派生值;数据场景需缓存键、请求状态和失效规则。

  7. 实现useRequest

    useRequest 至少返回 {data,error,loading,run,cancel};每次请求持有 AbortController 或版本号,卸载/新请求时防止旧结果覆盖,处理错误与重复提交。

14 / 25

组件设计题(Vue/React/JS均可)

13 题
  1. 全选

    全选:源状态是已选 id 集合;全选取当前可选项 id,反选移除它们;“部分选择”状态从数量派生,并设置原生 checkbox 的 indeterminate。

  2. 轮播图

    轮播图:索引、上一张/下一张、自动播放和交互暂停;按钮可键盘操作,有可访问名称;动画尊重减少动态效果偏好。

  3. 根据response渲染table

    表格:校验响应结构,列配置决定渲染,稳定 id 作 key;提供加载、空、错误、分页状态,不将不可信字段当 HTML 注入。

  4. 歌词滚动功能(hot)

    歌词滚动:解析时间戳并按时间排序,用二分查找当前播放时间对应行;只在行变化时滚到可视区域,用户手动滚动时暂停自动跟随。

  5. 实现一个 百度搜索框 (搜索提示)

    搜索提示:输入防抖,给每次请求版本号或取消前次请求,确保只展示最新结果;键盘上下/回车/Escape、空结果与 combobox/listbox 语义要完整。

  6. 实现todos

    Todos:任务 id、文本、完成状态为核心状态;支持增删改查与过滤,持久化时处理坏数据;列表项用稳定 id。

  7. 计时器或倒计时组件

    倒计时:记录结束时刻,用 Date.now() 差值计算剩余,而不是每秒减 1;卸载清理 timer,切后台后回来仍能校准。

  8. 设计一个公会入驻信息提交页

    入驻提交页:把字段分组、必填与格式验证、保存草稿、提交中禁重复、失败可恢复;上传材料和隐私告知依业务要求设计。

  9. 编码实现宽高不相同图片的自适配排列

    不同尺寸图片:保留原始宽高比,可用 CSS Grid/Masonry 或按原尺寸计算布局;图片预留尺寸避免 CLS,懒加载并限制单张最大宽度。

  10. 列表子元素顺序反转

    反转列表顺序:若只改变显示顺序用 [...items].reverse(),不要原地改 props;交互和屏幕阅读顺序应与视觉顺序一致,谨慎使用 flex-direction:row-reverse。

  11. 遍历树组件

    树组件:递归节点组件或扁平可见节点列表,按 id 管理展开/选中;大树用虚拟列表,键盘上下/左右导航和 aria-expanded 要支持。

  12. 选项卡

    选项卡:管理 active id,按钮用 role=tab、内容用 tabpanel,箭头键切换焦点并支持 Home/End;切换时决定是否保留面板状态。

  13. 拖拽

    拖拽:原生 Drag and Drop 或 Pointer Events;更新列表必须依据稳定 id,提供键盘移动方式、拖拽预览、取消和越界处理。

15 / 25

HTML CSS 手写题

1 题
  1. 实现圆形环状进度条

    环形进度条可用 SVG <circle> 的 stroke-dasharray/stroke-dashoffset 按比例绘制;提供文本百分比与 role="progressbar"、aria-valuenow/min/max,不要只靠颜色表达进度。

16 / 25

JavaScript 与浏览器原理

10 题
  1. 事件循环中,宏任务、微任务和浏览器绘制的执行顺序是什么?

    一轮事件循环取一个任务执行,随后清空微任务队列,再有机会更新渲染;实际调度由宿主环境决定,不能认为每个宏任务之后必定绘制。

  2. 持续追加微任务为什么会让页面无法响应输入?如何处理长任务?

    微任务会在渲染前持续清空,递归追加可能使输入和绘制饿死;把大工作分片并主动让出主线程,可用任务队列、scheduler.yield()(有兼容性处理)或 Worker。

  3. 闭包如何形成?在实际项目中怎样排查闭包导致的内存占用?

    函数保留创建处词法环境即形成闭包;内存排查用 Heap Snapshot 比较保留对象与引用链,检查未清理监听、计时器和长生命周期缓存。

  4. 原型链、class 和继承之间是什么关系?

    class 是基于原型的语法;实例沿 [[Prototype]] 链查找方法,extends 建立实例和构造器继承链,方法通常放在原型而非每个实例上。

  5. 箭头函数与普通函数的 this、arguments 和构造行为有何不同?

    箭头函数词法绑定 this,无自己的 arguments,不能作构造函数;普通函数的 this 由调用方式决定,可用 new,有自己的 arguments。

  6. ESM 与 CommonJS 的加载、导出和循环依赖行为有何区别?

    ESM 静态声明、实时绑定且支持异步模块;CommonJS 运行时 require、导出对象,循环依赖可能得到尚未初始化的部分导出。两者互操作依运行环境。

  7. 浏览器从输入 URL 到页面可交互,经历了哪些关键步骤?

    URL 解析后检查缓存/解析 DNS,建立连接与 TLS,发请求收响应;浏览器解析 HTML/CSS、加载关键资源、布局绘制并执行影响交互的脚本。真实顺序含并行与缓存分支。

  8. 浏览器如何构建 DOM/CSSOM,并完成布局、绘制与合成?

    HTML 构建 DOM、CSS 构建 CSSOM,再形成渲染树;计算样式与布局、绘制图层并合成。样式/几何变化可能触发重算或重排,transform 常只需合成。

  9. 如何用 DevTools 定位内存泄漏、长任务和频繁重排?

    Performance 面板看长任务和 Layout,Memory Heap Snapshot 看保留链,Performance Monitor 看节点/堆增长;用可复现步骤和前后对比确认原因。

  10. localStorage、sessionStorage、IndexedDB 和 Cookie 应如何选择?

    Cookie 随请求发送,适合少量会话数据并设安全属性;localStorage 持久同步,sessionStorage 按标签页会话,IndexedDB 异步且适合较大结构化数据。敏感令牌避免暴露给脚本。

17 / 25

TypeScript

8 题
  1. any、unknown 和 never 分别适用于什么场景?

    any 跳过检查,仅边界迁移时谨慎用;unknown 是安全的未知值,使用前须收窄;never 表示不可能值,可做穷尽检查。

  2. 如何用类型守卫和判别联合安全地收窄联合类型?

    用 typeof、in、自定义谓词或判别字段收窄联合;判别联合给每种分支不同字面量 kind,避免直接断言类型。

  3. 如何用 never 检查联合类型的分支是否穷尽?

    switch(value.kind) 的 default 中写 const unreachable: never = value;新增联合成员时编译器会指出遗漏分支。

  4. 泛型约束、条件类型和 infer 如何用于设计可复用 API?

    T extends Constraint 限制泛型能力;条件类型按类型关系选择分支,infer 从匹配形状提取内部类型,例如从函数提取返回值。

  5. 实现 Partial、Pick、Omit、ReturnType 和 Awaited 中任意两个工具类型。

    例如 type MyPartial<T>={[K in keyof T]?:T[K]};type MyPick<T,K extends keyof T>={[P in K]:T[P]}。原生工具类型还涉及一些细节,面试应说明边界。

  6. interface 与 type 在扩展和声明合并方面有何区别?

    interface 可声明合并且方便扩展对象形状;type 可表达联合、交叉、元组、条件类型等,不能同名声明合并。二者多数对象类型场景可互用。

  7. 如何为异步请求建模成功、加载、失败三种状态,避免非法状态?

    用判别联合:{status:'loading'} | {status:'success',data:T} | {status:'error',error:Error};只有成功分支才有 data,避免多个独立布尔量形成非法组合。

  8. 为什么 Vite 能转换 TypeScript,却仍需要单独执行类型检查?

    Vite 开发时用转译器去掉 TS 类型以求速度,不执行完整语义类型检查;CI 仍需 tsc --noEmit 或 vue-tsc --noEmit。

18 / 25

HTML、CSS 与无障碍

8 题
  1. Flexbox 与 Grid 分别适合哪些布局?实现响应式卡片网格。

    Flex 适合单轴排布,Grid 适合二维行列;响应式卡片可用 grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr))。

  2. BFC 是什么?它如何影响外边距折叠和浮动?

    BFC 是独立的块格式化上下文,内部浮动参与高度、与外部浮动布局隔离,内部外边距与外部不折叠;可用 display:flow-root 创建。

  3. 如何避免字体、图片和异步内容造成布局偏移?

    图片声明宽高或 aspect-ratio,字体预加载并考虑替换指标,异步区域预留空间;用真实 CLS 数据找移动元素。

  4. 如何实现暗色模式,并兼顾系统偏好与用户手动选择?

    用 prefers-color-scheme 作默认值、用户设置作覆盖并持久化;保证对比度,更新 color-scheme 让原生控件匹配。

  5. 如何让弹窗具备正确的焦点管理、键盘操作和语义?

    优先原生 <dialog>;打开时聚焦合适元素,限制焦点在模态内容内,Escape 可关闭,关闭后回到触发按钮,并标注标题。

  6. 何时使用原生 HTML 元素,何时才需要 ARIA 属性?

    原生按钮、链接、输入自带键盘和语义,优先使用;只有原生语义无法表达时补 ARIA,ARIA 不会自动添加交互行为。

  7. 如何验证表单错误提示对键盘用户和屏幕阅读器可用?

    标签关联控件,错误文本用 aria-describedby 关联,提交后聚焦错误摘要或首个错误;仅颜色变化不足够,用键盘和屏幕阅读器实测。

  8. 如何实现宽屏到移动端的自适应布局,而不依赖固定设备宽度?

    用内容驱动的 Grid/Flex、相对单位与容器查询,让内容自然换行;窄视口、缩放到 200% 和长文案都需检查溢出。

19 / 25

网络、缓存与安全

10 题
  1. HTTP 强缓存与协商缓存如何配合?Cache-Control 和 ETag 分别解决什么问题?

    Cache-Control 指定可缓存性与新鲜期,命中强缓存可不发请求;过期后带 If-None-Match 与 ETag 协商,未变则 304。带哈希的静态资源适合长期缓存。

  2. CORS 为什么由浏览器执行?预检请求在什么情况下出现?

    CORS 是浏览器同源策略的一部分,服务器通过响应头授予跨源读取权限;非简单方法/头或某些内容类型会先发 OPTIONS 预检,服务器间请求不受浏览器 CORS 限制。

  3. 从 DNS、TCP/TLS 到 HTTP 响应,HTTPS 请求的关键步骤是什么?

    先解析 DNS、建立传输连接(TCP 或 QUIC)、TLS 验证证书并协商密钥,然后发送 HTTP 请求与接收响应;可复用连接并有缓存/CDN 分支。

  4. HTTP/2 与 HTTP/3 对前端资源加载有什么影响?

    HTTP/2 多路复用和头压缩减少请求开销,但单 TCP 丢包仍可能影响流;HTTP/3 基于 QUIC,可减少部分队头阻塞及连接迁移成本。仍应控制资源大小和优先级。

  5. SSE、WebSocket 与普通 HTTP 请求应如何选择?

    普通 HTTP 适合一次性请求;SSE 适合服务端单向事件流与自动重连;WebSocket 适合持续双向低延迟通信,需处理心跳与断线恢复。

  6. 如何实现 Token 刷新时的并发去重和失败请求重放?

    用单个 refreshPromise 让并发 401 共享刷新;刷新成功后重放可安全重试的请求并防无限循环,失败则清理登录态。请求体不可重放时需特殊处理。

  7. 如何防止 XSS?为何只设置 CSP 仍不够?

    默认对不可信文本做上下文编码,避免 innerHTML,对允许的 HTML 用可信清洗库;CSP 是纵深防御,不能替代正确编码和 DOM API 使用。

  8. Cookie 的 HttpOnly、Secure 和 SameSite 分别解决什么问题?

    HttpOnly 阻止 JS 读取 Cookie,Secure 要求 HTTPS 传输,SameSite 限制跨站请求携带;仍需考虑 CSRF、XSS 与会话过期。

  9. CSRF 的触发条件是什么?前后端如何配合防护?

    站外页面诱导浏览器携带受害者凭据发送有副作用请求,即可形成 CSRF;用 SameSite、CSRF token、Origin/Referer 检查及避免 GET 改数据共同防护。

  10. 如何设计上传大文件的分片、断点续传、校验和重试?

    客户端切块并记录上传 id、分片索引和校验值,服务端幂等接收、查缺补漏与最终合并校验;失败只重传缺失块,限制并发并支持取消。

20 / 25

React 与 React 19

11 题
  1. React 的 render 与 commit 阶段分别做什么?状态更新何时触发重新渲染?

    render 阶段计算下一棵 UI,可被中断/重试;commit 阶段把变化提交到 DOM 并运行相关 effect。状态更新安排重新渲染,同值更新可能被跳过。

  2. 列表 key 为什么必须稳定?错误的 key 会造成什么问题?

    key 标识同级列表项身份,重排时帮助保留正确组件状态;用索引作 key 在插入/删除/排序时可能让输入状态错位。

  3. useEffect 的依赖、清理和闭包过期问题如何排查?

    effect 应列全读取的响应式依赖,返回清理函数;闭包过期可通过正确依赖、函数式更新或 ref 解决,避免靠禁用 lint 掩盖问题。

  4. useMemo、useCallback 和 React.memo 在什么条件下才有收益?

    只有计算昂贵、引用稳定性确实影响子组件,且测量证明收益时才用;记忆化本身也有成本,先用 React Profiler 定位。

  5. useTransition 与 useDeferredValue 分别适合什么交互?

    useTransition 把特定状态更新标为非紧急并给 pending 状态;useDeferredValue 让某个值的消费延后,适合输入即时响应、结果稍后更新。

  6. 如何在 React 中处理表单提交的 pending、错误和乐观更新?

    提交时禁重复或标 pending,显示可恢复错误;乐观更新需在失败时回滚或重新同步,尤其注意并发提交的顺序。

  7. useActionState、useFormStatus 和 useOptimistic 各承担什么职责?

    useActionState 管理 action 结果与 pending,useFormStatus 读取所属表单提交状态,useOptimistic 在异步期间提供临时乐观视图。

  8. React 中何时使用 useState、Context 或外部状态库?

    局部状态用 useState;跨层共享少量稳定值可用 Context;复杂跨页面共享或高频更新再评估外部状态库,避免把所有状态集中。

  9. 如何定位大型 React 应用的无效渲染并量化优化效果?

    用 React Profiler 找提交次数和耗时,确定导致更新的 state/props,改动后对同一交互量化比较;开发模式数据不等于生产模式。

  10. 如何设计一个支持取消、竞态处理和错误状态的异步数据 Hook?

    Hook 在 effect 中创建 AbortController,请求开始设 loading,完成时只让当前版本写入 data/error,清理时 abort;给调用方 retry 与取消入口。

  11. React Compiler 的自动记忆化与手动 useMemo、useCallback 有什么关系?如何验证它是否生效?

    React Compiler 可自动记忆化组件和 Hook 中部分计算;手动 useMemo/useCallback 仍可精确控制特定值的稳定性,但不能把记忆化当作程序正确性的前提。检查构建配置与编译诊断,用 Profiler 对比提交次数和耗时。

21 / 25

Vue 3

8 题
  1. Vue 3 的 reactive 和 ref 如何跟踪依赖并触发更新?

    reactive 通过 Proxy 跟踪对象属性读写,ref 通过 .value 跟踪读写;effect 读取时收集依赖,写入触发调度更新。

  2. computed、watch 和 watchEffect 的使用场景及执行时机有何不同?

    computed 缓存派生值;watch 监控指定源并可取新旧值;watchEffect 自动追踪同步读取的依赖。默认 watcher 回调通常在父更新后、组件 DOM 更新前运行,需 DOM 更新后用 flush:'post'。

  3. Vue 组件之间如何选择 props、emit、provide/inject 和 Pinia?

    父子数据用 props/emit,深层依赖注入用 provide/inject,跨页面共享业务状态可用 Pinia;避免把局部临时状态全局化。

  4. v-model 在原生表单和自定义组件上分别如何工作?

    原生表单 v-model 映射值属性和输入事件;自定义组件默认用 modelValue prop 和 update:modelValue 事件,也可声明命名模型。

  5. Vue 的 nextTick 解决了什么问题?什么时候不该依赖它?

    nextTick 等待 Vue 批量 DOM 更新完成,适合更新数据后读取新 DOM;不应用它代替正确的数据流或异步业务完成通知。

  6. Vue 2 项目迁移 Vue 3 时,如何分阶段处理 API 与依赖兼容?

    先升级依赖并运行迁移构建,处理不兼容 API/插件;可逐模块迁移组件,覆盖关键交互测试,再切换应用入口。

  7. 为什么解构 reactive 对象可能丢失响应性?如何修复?

    直接解构普通变量会失去 Proxy 属性读取链;使用 toRefs/toRef 保留关联,或在消费处继续读取 state.key。

  8. Vue 列表渲染中 key 的作用是什么?如何避免组件状态错位?

    key 帮助 Vue 在列表变动时识别节点与组件身份;用稳定 id,不在重排列表中用索引作 key。

22 / 25

Next.js 与服务端渲染

8 题
  1. CSR、SSR、SSG 与按需重新生成页面分别适用于什么场景?

    CSR 适合强交互且 SEO 要求低的界面;SSR 适合请求时个性化内容;SSG 适合变化少的公开内容;按需重新生成适合可容忍短时间旧内容的公开页面。

  2. Server Component 与 Client Component 的边界如何划分?

    Server Component 负责服务端数据读取、保密逻辑和减少客户端 JS;需要 state、事件、浏览器 API 的边界用 Client Component,尽量让客户端边界靠近叶子。

  3. Hydration mismatch 有哪些常见原因?如何定位和修复?

    服务端与客户端首屏输出不一致会报错,常见于时间、随机数、浏览器状态或非法 HTML 嵌套;定位具体节点,保证首次输出一致,浏览器专有值放 effect 后处理。

  4. 哪些数据可以在服务端读取,哪些交互必须留在客户端?

    密钥、数据库读取只在服务端;交互、DOM、localStorage 在客户端。服务端返回给客户端的 props 必须可序列化且不含秘密。

  5. 如何用 Suspense 和流式渲染改善慢数据页面的体验?

    用 Suspense 为慢数据区块设 fallback,让外层内容先到达;流式响应逐步发送边界内容,仍需有错误边界和可用的加载状态。

  6. 如何设计公开页面与个性化页面的缓存策略,避免数据串用?

    公共数据可按明确标签/时效缓存;含用户身份的数据按请求隔离或不缓存,缓存键必须包含影响输出的身份/权限维度,防止串用。

  7. 如何让服务端渲染页面同时具备良好的 SEO 和可交互性?

    服务端输出可抓取的标题、正文与元数据,客户端对需要的区域进行 hydration;减少阻塞脚本并确保交互在加载前有可理解的状态。

  8. Next.js 的 Cache Components 与 use cache 如何划分静态、缓存和请求时数据?

    Cache Components 让可缓存区域显式使用 use cache 并设置生命周期/失效策略;依请求变化的区域保持动态,使用 Suspense 边界。具体行为随 Next.js 版本和配置变化,应以项目版本验证。

23 / 25

性能、工程化与测试

10 题
  1. LCP、INP、CLS 各衡量什么?如何用真实用户数据定位问题?

    LCP 衡量最大内容渲染,INP 衡量交互响应,CLS 衡量意外布局偏移;按设备/页面分组看真实用户分位数,再用实验室工具找根因。

  2. 首屏图片、字体与脚本分别如何优化,并如何验证收益?

    图片用合适尺寸/格式并优先加载 LCP 图,字体减少字重并控制交换,脚本按需拆分/延后;用同口径真实用户指标和网络瀑布图验证。

  3. 长列表和聊天记录应如何虚拟化?动态高度会带来什么问题?

    只渲染视口及缓冲区项目;动态高度需测量并维护位置索引,插入历史消息时保持滚动锚点,避免跳动。

  4. Vite 开发服务器与生产构建的工作方式有何区别?

    Vite 开发时按需通过原生 ESM 提供模块并快速 HMR;生产构建打包、优化、拆分和压缩资源。TypeScript 类型检查需另跑。

  5. Tree shaking 失效的常见原因是什么?如何检查打包体积?

    副作用标记错误、CommonJS、不支持静态分析的动态访问或整个库导入可影响 tree shaking;用构建分析报告和产物确认实际体积。

  6. 如何为组件设计单元测试、交互测试和端到端测试的边界?

    纯逻辑用单元测试,组件行为用用户交互测试,跨页面关键流程用 E2E;避免三层重复测试同一实现细节。

  7. 如何测试异步请求的加载、失败、取消和重复提交?

    用可控的请求 mock 验证 loading→success/error、取消后不更新、重复提交行为;断言用户可见状态与请求次数。

  8. Monorepo 中如何划分共享包、版本与依赖边界?

    共享包只放跨应用稳定能力,明确包之间依赖方向与发布版本;避免应用互相引用内部源码,CI 按变更范围构建测试。

  9. 如何制定前端错误监控、Source Map 和发布回滚方案?

    采集错误与发布版本、匿名会话线索;Source Map 私有上传并限权限;发布采用可回退产物/开关,白屏率异常时快速回滚。

  10. 如何量化一次性能优化,避免只看本地 Lighthouse 分数?

    明确目标指标与样本,固定页面、设备和网络条件比较实验室数据,同时看线上同口径 p75/p95 与错误率,避免只凭一次 Lighthouse 分数。

24 / 25

AI 应用前端与流式交互

8 题
  1. 如何用 fetch、ReadableStream 和 TextDecoder 逐块解析模型流式响应?

    fetch 后从 response.body.getReader() 循环读块,用 TextDecoder.decode(value,{stream:true}) 保留跨块字符;SSE 还需缓冲到完整事件边界再解析,不能假定一块就是一条消息。

  2. SSE 流断开、用户取消和网络重连时,界面状态如何恢复?

    维护连接状态与最后事件 id;断线按可重放协议重连并去重,用户取消用 AbortController 且不自动重连;服务端不支持重放时不能假称续传。

  3. 模型输出 Markdown 时,如何处理不完整片段和不可信内容?

    对不完整 Markdown 可先按文本或增量解析展示,完成后再稳定渲染;禁止直接注入模型生成 HTML,链接与代码展示也要做安全处理。

  4. 聊天消息持续增长时,如何控制渲染成本和自动滚动行为?

    长列表虚拟化或分段,流式文本按帧/批次更新;仅当用户停留底部时自动跟随,用户向上阅读则显示“跳到底部”按钮。

  5. 如何避免旧请求的流式内容覆盖用户最新发起的会话?

    每次会话/请求分配版本 id 并取消旧流;收到片段时先核对当前 id,再写入对应消息,切换会话时清理 reader。

  6. 工具调用或 Agent 运行较久时,如何展示进度、错误和可取消状态?

    明确“排队/运行/等待工具/完成/失败/已取消”状态,显示可理解的进度与重试入口;取消要传到底层并处理服务端仍在执行的情况。

  7. 如何审查 AI 生成代码的正确性、安全性与可维护性?

    先核对需求与数据边界,再审查输入校验、依赖、授权、错误处理与复杂度;运行测试和真实交互,对关键逻辑保留人工复核。

  8. 你如何在实际项目中使用 AI 工具,并验证它产出的代码?

    可以描述具体任务、给 AI 的上下文范围、所采用与拒绝的建议,以及测试/审查证据;面试重点是你如何验证,而非仅说“用 AI 提效”。

25 / 25

系统设计与项目深挖

8 题
  1. 如何设计一个高并发搜索建议框,并处理防抖、取消和结果乱序?

    输入防抖并设最小长度,取消旧请求或用版本号丢弃旧结果;缓存短期热门查询,限制服务端速率,完整处理键盘操作、加载和空态。

  2. 如何设计后台系统的路由、按钮和数据权限?前后端职责如何划分?

    前端路由/按钮权限仅控制体验,后端对每个资源和操作强制鉴权;权限模型可用角色加资源动作,并避免把敏感数据先发给无权用户。

  3. 如何设计大型前端应用的组件边界和状态管理方案?

    按业务领域划分组件与状态,局部状态留局部,跨域状态经明确接口共享;区分服务端缓存数据与 UI 临时状态,防止全局 store 成为耦合中心。

  4. 如何解释一次真实项目的性能瓶颈、改动、指标和取舍?

    用“场景与基线→证据→改动→前后指标→取舍”叙述真实案例,例如 p75 LCP 从多少降到多少;没有实测数据就如实说明。

  5. 如何设计稳定的文件上传体验,包括进度、失败恢复和重复提交?

    分片上传、进度计算、断点查询和幂等完成接口;网络失败可重试单片,重复提交用上传 id/幂等键防止重复文件,最终校验完整性。

  6. 如何设计实时聊天页面的消息顺序、断线重连和历史分页?

    消息用服务端序号或时间加稳定 id 排序,发送中临时 id 与回执对账;断线按游标补消息,历史分页向上插入并保持滚动位置。

  7. WebView 与原生页面通信时,如何设计 JS Bridge 的权限和错误处理?

    JS Bridge 采用白名单方法、版本化请求/响应 id、来源校验与权限控制;每次调用有超时和结构化错误,敏感能力必须由原生端复核授权。

  8. 当线上页面出现白屏时,你会如何定位、止损和复盘?

    先确认影响范围并回滚/切流止损,再查发布版本、静态资源加载、JS 错误、接口和 CDN;复现根因、补监控与回归测试,记录恢复时间和预防措施。