资讯动态

小红书前端笔试复盘:JavaScript、React与手写代码全解析

发布时间:2026/8/29 23:51:04 来源:尧图企业网站定制
1. 笔试整体复盘拿到卷子先别急着敲代码这批笔试整体给我的印象是题目不偏、不怪但覆盖面极广且非常考验基础功底。不像有些公司喜欢出冷门框架源码题小红书的这套卷子更偏向“你平时写代码时到底有没有想过为什么”。先说结论如果你正在准备前端岗笔试死记硬背八股文只能帮你拿下一半分数另一半拼的是你能否把原理讲清楚、能否在限定时间内写出可运行的代码。整张卷子大致分三块选择题约40分钟、编程题约60分钟、问答题约20分钟。时间分配上我建议选择题控制在30分钟以内给编程题留足时间。编程题不是“会做就行”而是“要能跑通”很多人挂在边界条件和输入输出处理上非常可惜。另外提醒一句笔试环境是网页版IDE没有自动补全不能切出去查资料。如果你平时重度依赖编辑器提示建议考前一周就切换到无补全模式刷题否则真正上考场会非常不适。考完复盘下来我认为这套题的核心脉络可以归纳为四个方向JavaScript基础功底、框架原理理解、工程化常识、手写代码能力。下文我按这几个方向逐块拆解把每类题背后的考察意图和应对策略说清楚。2. JavaScript基础你真的懂this和事件循环吗2.1 this指向别背规则要会推演选择题里必有一道this指向的题今年考的是经典组合const obj { name: xiaohongshu, getName: function() { return this.name; } }; const getName obj.getName; console.log(getName());答案显然是undefined而不是报错。这道题本身不难难的是同卷的另一道变体const obj { name: xhs, getName: function() { return () this.name; } }; const fn obj.getName(); console.log(fn());这里考察的是箭头函数没有自己的this会继承外层函数的this。因为fn()是通过obj.getName()返回的箭头函数外层getName的this指向obj所以输出xhs。我的建议是不要死记“谁调用指向谁”这种口诀因为箭头函数、bind、new这些场景口诀根本覆盖不了。你要建立的是“this是函数调用时确定的和定义位置无关箭头函数除外”这个底层认知然后逐层分析调用方式。2.2 事件循环微任务和宏任务的执行顺序事件循环几乎是前端笔试必考题今年也不例外。题目大概是这样的输出顺序题console.log(A); setTimeout(() console.log(B), 0); Promise.resolve().then(() console.log(C)); console.log(D); // 输出顺序答案A、D、C、B。理由很简单同步代码先执行完然后清空微任务队列Promise.then最后执行宏任务setTimeout。但今年加了点花样考了async/await的微任务时机async function test() { console.log(A); await 1; console.log(B); } test(); console.log(C);输出是A、C、B。很多人以为await 1后面的代码会立即执行实际上await右侧表达式求值后虽然值不是Promise也会把后续代码包装成微任务。所以B要等当前同步任务执行完C输出后才打印。这类题想拿分建议把“事件循环”画成流水线的图同步代码 → 微任务全部清空 → 宏任务取一个 → 微任务再清空 → 下一个宏任务。每次笔试前默写一遍这张图输出的顺序题基本不会丢分。2.3 闭包与内存不只是概念题闭包这次没有考定义而是考了一段实际代码for (var i 0; i 5; i) { setTimeout(() console.log(i), 100); }输出5个5这个大多数人都知道。但接下来问你怎么改才能输出0到4。我写的是for (let i 0; i 5; i) { setTimeout(() console.log(i), 100); }let的块级作用域为每次循环创建了新的绑定这个解法最简洁。但面试官如果想看你会不会用闭包可以补充for (var i 0; i 5; i) { (function(i) { setTimeout(() console.log(i), 100); })(i); }笔试建议优先写let方案简洁不易错。但我个人喜欢在答题区补充一句“也可以用IIFE包裹实现闭包隔离”展示你对底层机制的理解这个加分项很值得写。3. 框架与工程化React和Vue的核心差异3.1 Hooks闭包陷阱为什么拿到的是旧值小红书的技术栈偏React所以React Hooks题一定跑不了。今年考了一个很经典的“闭包陷阱”function Counter() { const [count, setCount] useState(0); useEffect(() { const timer setInterval(() { setCount(count 1); }, 1000); return () clearInterval(timer); }, []); return div{count}/div; }问这个Counter组件每秒会加几答案是加到1就停住了。因为useEffect的空依赖数组只在挂载时执行一次闭包捕获的count永远是最初的0所以每次setCount(0 1)界面永远显示1。正确写法是setCount(prev prev 1)用函数式更新来获取最新状态。这类题考察的不只是API记忆而是你是否理解“函数组件每一次渲染都有独立的闭包环境”这个核心概念。笔试中如果遇到类似题尽量把“为什么”写在答案里不要只写正确代码。3.2 Virtual DOM它到底解决了什么问题这道题是问答题题目大意是“为什么需要Virtual DOM直接操作真实DOM不行吗”很多人的回答是“因为直接操作DOM慢Virtual DOM快”。这个答案只能说对了一半。Virtual DOM真正的价值在于把“操作DOM”转换为“操作JavaScript对象”让视图更新逻辑可以跨平台浏览器、Native、小程序。提供了一种声明式编程的载体你只管描述UI应该长什么样框架负责把变更应用到真实DOM。通过Diff算法最小化真实DOM操作次数虽然Diff本身也有开销但在复杂交互场景下批量化的更新策略比手动操作DOM更容易控制性能。你答题时如果能从“声明式UI”和“跨平台能力”两个角度切入比如Vue的模板编译器和React的render函数最终都生成Virtual DOM再由不同平台的渲染器消费这道题就能拿到一个很高的分数。3.3 组件通信与状态管理选择题里考了“React中父组件给子组件传值子组件想修改这个值应该怎么做”。这是React经典问答正确方式是父组件传入回调函数子组件调用回调由父组件修改状态。直接修改props在React严格模式下会报错因为props是只读的。Vue这边问的是v-model和自定义组件的写法ChildComponent v-modelsearchText / !-- 等价于 -- ChildComponent :modelValuesearchText update:modelValuesearchText $event /Vue 3.4之后推荐使用defineModel简化实现但笔试中考察的还是对modelValue和update:modelValue这两个约定的理解。状态管理题问的是Redux数据流dispatch(action) → reducer 生成 new state → 通知订阅者更新。注意强调reducer必须是纯函数不能在reducer里做异步操作或修改原state。4. 手写代码与算法限时内写出可运行代码4.1 手写防抖节流基础但仍有人翻车编程题第一道是手写防抖函数要求支持leading参数是否立即执行第一次。我给出的实现function debounce(fn, wait, leading false) { let timer null; let hasInvoked false; return function(...args) { const context this; if (leading !hasInvoked) { fn.apply(context, args); hasInvoked true; } clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); hasInvoked false; }, wait); }; }注意两点一是this要原样透传二是要处理leading模式下第一次执行后wait时间段内不能再触发立即执行。这里用一个hasInvoked标志位控制比单纯判断timer是否为空更严谨。节流写起来大差不差核心是控制“固定时间窗口内最多执行一次”可以基于时间戳也可以基于定时器我习惯用时间戳定时器的组合写法既能保证首次立即执行也能保证最后一次触发不丢失。4.2 实现一个简单的深拷贝这道题要求不能使用JSON.parse(JSON.stringify(obj))要处理数组、对象、Date、RegExp以及循环引用。我给出的代码function deepClone(target, map new WeakMap()) { if (target null || typeof target ! object) { return target; } if (map.has(target)) { return map.get(target); } if (target instanceof Date) { return new Date(target); } if (target instanceof RegExp) { return new RegExp(target.source, target.flags); } if (Array.isArray(target)) { const result []; map.set(target, result); target.forEach(item result.push(deepClone(item, map))); return result; } const result {}; map.set(target, result); Object.keys(target).forEach(key { result[key] deepClone(target[key], map); }); return result; }WeakMap一定是关键点它用原对象作为key如果当前对象已经克隆过直接返回之前克隆的结果解决循环引用。注意不能用普通Map做这个缓存因为Map的key是强引用会导致原对象无法被垃圾回收WeakMap则没有这个问题这也是考察点之一。4.3 编程题数组扁平化与去重编程题还考了“将多维数组扁平化并去重然后按从小到大排序”。这题本身不难但要写出高效且健壮的版本也不容易function flattenUniqueSort(arr) { return [...new Set(arr.flat(Infinity))].sort((a, b) a - b); }arr.flat(Infinity)处理任意层级的嵌套Set去重最后排序时注意回调函数不能省否则按字典序排。当然如果你想展示基本功可以手动实现一个不使用flat的版本用栈或递归展开去重时用Map记录这样即使笔试环境不支持ES2019也能跑。我推荐两种都写先标榜一行流再补充一个兼容性更好的版本这样阅卷人能看到你“既懂现代语法又能处理兼容问题”。4.4 LRU缓存这道题区分度很高今年编程题里区分度最高的应该是实现一个LRU缓存。要求get和put的时间复杂度都是O(1)。class LRUCache { constructor(capacity) { this.capacity capacity; this.map new Map(); } get(key) { if (!this.map.has(key)) return -1; const value this.map.get(key); this.map.delete(key); this.map.set(key, value); return value; } put(key, value) { if (this.map.has(key)) { this.map.delete(key); } else if (this.map.size this.capacity) { const oldestKey this.map.keys().next().value; this.map.delete(oldestKey); } this.map.set(key, value); } }这里利用Map底层的哈希表实现O(1)查找同时Map迭代顺序就是插入顺序所以keys().next().value拿到的就是最久未使用的key。用Map实现LRU是JavaScript中最优雅的方案比手写双向链表简洁得多。如果你在这个基础上还能说出“为什么用Map而不是对象”——因为对象键顺序在某些场景下没有保证且删除键值对不够直接阅卷人会更认可你的深度。5. 笔试踩坑实录这些错我真的不希望你再犯5.1 细节错误最冤的丢分点很多同学不是不会做而是挂在细节上。我整理了一下自己之前笔试和今年观察到的典型问题变量提升题console.log(a); var a 1;输出undefined不是报错也不是1。很多人混淆了“变量提升”和“暂时性死区”的适用场景。类型转换题[] 0是true因为[]会先转成空字符串再转数字0。这类题要记得“对象转原始值先走valueOf再走toString”的规则。判断数组typeof []是object不是array。判断数组首选Array.isArray()不能用constructor.name因为跨iframe场景下会出问题。这些小点单个都不难但组合在一张卷子里很容易连环错。我建议在笔试前一天快速过一遍这些高频易错点当作考前热身。5.2 编程题的输入输出本地跑通不代表提交能过笔试环境最坑的一点是输入输出格式必须严格匹配。JavaScript在牛客和赛码这类平台上是这样读输入的const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const lines []; rl.on(line, line { lines.push(line); }).on(close, () { const [n, m] lines[0].split( ).map(Number); // 处理逻辑 });很多同学用的编辑器里跑的是Node环境但笔试平台可能是纯浏览器环境不支持require。建议考前先在目标平台做一道简单题确认可用的API集合。还有输出不要多打空格、不要console.log调试信息这些都会干扰判题。5.3 问答题字数不是越多越好要踩点问答题如果问“浏览器从输入URL到页面展示发生了什么”不要从头到尾洋洋洒洒写800字。阅卷人更希望看到你分层清晰、点面结合的答案。我常用这个结构网络层DNS解析 → TCP连接 → TLS握手 → HTTP请求与响应。解析层HTML解析成DOM树CSS解析成CSSOM合并成Render Tree。渲染层布局Layout→ 绘制Paint→ 合成Composite。JS执行解析过程中遇到script会阻塞解析所以脚本通常放在body底部或加defer/async。每层用一两句话解释清楚再补充一个实际优化点比如DOMContentLoaded和load事件的差异静态资源用CDN加缓存整体控制在300到500字逻辑清晰踩点完整。5.4 备一张“快速自查表”笔试交卷前我每次都会花5分钟按这个清单自查一遍变量命名是否表意清晰编程题会不会因为可读性差被扣印象分边界条件是否覆盖空数组、超大数字、重复元素、循环引用复杂关键操作有没有写注释代码里有没有多余的空行和调试输出问答题是不是每条只写了一个短句有没有用“是什么→为什么→怎么解决”逻辑补齐这张表看起来简单但实战中特别管用。我见过太多人代码对了但因为输出格式不对或者忘了处理capacity 1的边界情况而丢分。6. 后续怎么做从笔试反推能力短板笔试只是第一道关卡真正值得花时间思考的是这张卷子暴露了你的哪些短板如果this指向和事件循环这类基础题错了说明你对JavaScript的执行模型还没有建立体系化认知。我建议不要零散刷题而是系统性地把“调用栈→事件循环→微任务/宏任务→闭包作用域”这条线完整梳理一遍用一段时间建立自己的知识脉络。如果Hooks闭包陷阱答错了说明你对React函数组件渲染模型的理解还停留在“照着写”的阶段。建议去读react源码中Hooks相关的dispatcher实现或者动手写一个小型useState模拟器理解为什么state存储在Fiber节点上而不是存在某个全局变量里。如果LRU这类中等偏上的手写题卡住了说明数据结构和算法基本功还需要锻炼。不需要盲目刷难题把“数组、链表、哈希表、栈、队列、树”这几类基础结构在JavaScript中的实现写熟比背100道题更有价值。小红书的笔试风格整体上“不偏不怪”这对认真准备过基础的人来说是利好。但反过来说正因为题不难区分度就体现在细节上同样的代码谁能处理边界条件、谁能解释清楚原理、谁能在时间压力下保持代码整洁谁就能进入下一轮。

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

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

免费获取报价