ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

小红书前端笔试题复盘:从JavaScript机制到工程化实战

小红书前端笔试题复盘:从JavaScript机制到工程化实战 这份试卷我后来反复看了好几遍也拿给身边正在准备秋招的朋友们做过。说实话它不算刁钻但覆盖面很广JavaScript基础、浏览器原理、框架认知、工程化都有涉及有些题表面在考API实际在考你对运行机制的理解。如果你正打算投小红书的前端岗位或者想摸底一下自己在校招水平线上处于什么位置这份卷子的参考价值是很高的。这篇复盘我不会只贴答案而是把每道题背后的考察意图、常见的错误思路、以及我当时是怎么一步步推理的过程都写出来尽量让每个结论都有据可循。1. 整体风格这份卷子想筛选什么样的人看一套笔试题先别急着做题要站在出题人的角度想一个问题这家公司希望招到什么样的前端从这个角度切入整张卷子的逻辑会清晰很多。小红书的前端技术栈以React为主页面偏重交互体验和内容展示对动画、性能、渲染效率的要求都不低。所以这套笔试题明显不是那种刷完题库就能过的类型它更关注三件事基础是否扎实JS核心概念、异步机制、作用域、闭包这类底层知识占比很大说明他们默认你必须有扎实的语言功底。有没有真实项目经验部分题目不会直接问怎么做而是给你一个场景让你判断哪种方案合理。这种题靠背概念是答不好的必须真的在项目里踩过坑才能给出有说服力的答案。代码风格和工程意识手写题不只看你写不写得出来还看你的变量命名、边界处理、注释习惯这些细节在批卷时其实很加分。我估了一下整份卷子如果正常发挥难度属于中等偏上。不算难但想拿高分需要复习得比较系统靠临时抱佛脚或者只刷 Vue 相关题的话会有些吃亏。2. 代码输出题JavaScript 运行机制的照妖镜这类题几乎是所有前端笔试题的标配但每个公司考的角度不太一样。小红书这份卷子里的代码输出题给我的感觉是它们不考冷门语法而是考你平时写代码时最容易想当然的地方。2.1 典型题一var、let 与闭包的组合拳题目大概是这样的for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }这道题已经被写烂了但我发现每次校招都还有不少人掉坑里。输出结果是 5 个 5而不是 0、1、2、3、4原因是var声明的i是函数作用域循环结束时i已经变成了 5而setTimeout的回调在未来某个时刻才执行此时读取的i自然就是 5。但小红书这道题没有止步于此它接着问了一个跟进题如果换成let输出会是什么为什么for (let i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }答案是 0、1、2、3、4。关键点在于let在每次迭代时会创建一个新的绑定也就是说每一轮循环的i都是独立的一份副本闭包捕获的是当轮的值而不是共享同一个变量。这个区别本质上不是let 更聪明而是 JS 引擎在底层为let做了类似每轮新建作用域的处理。我当时做题时习惯把这个过程画出来每轮循环相当于一个独立的小房间房间里放着一把写着当前i值的椅子setTimeout的回调只认自己房间里的那把椅子。var版本则是一间大房子所有人共享一把椅子最后椅子上的数字被改成了 5所以大家看到的都是 5。2.2 典型题二事件循环与 Promise 的执行顺序这道题我印象很深因为它的答案具有迷惑性很多人第一眼会想当然。console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); Promise.resolve() .then(function() { console.log(promise1); }) .then(function() { console.log(promise2); }); console.log(script end);正确输出顺序是script start script end promise1 promise2 setTimeout这里牵扯到宏任务、微任务的优先级。简单说JS 引擎在处理完当前宏任务后会先把微任务队列清空再去取下一个宏任务。Promise.then注册的是微任务setTimeout注册的是宏任务即使setTimeout延迟设为 0也要排在微任务后面。但我在复盘时想到一个更值得聊的问题微任务里如果又产生了微任务呢比如Promise.resolve() .then(function() { console.log(promise1); Promise.resolve() .then(function() { console.log(promise3); }); }) .then(function() { console.log(promise2); });这个输出的顺序是 promise1、promise3、promise2。原因是第一个.then回调执行完后会返回一个新的 Promise而在这个回调内部又注册了一个新的微任务它会被放到当前微任务队列的末尾但依然排在下一个宏任务之前。理解这个点之后再看复杂的异步流程就不会乱了。提示这类题建议大家平时用node --trace-events或者浏览器 Performance 面板观察任务队列直观看到宏任务和微任务的调度过程比自己空想要清楚得多。3. 手写题从 API 调用到原理实现手写题在一套前端笔试题里通常占 30% 左右的分数也是最容易拉开差距的部分。小红书这份卷子的手写题没有故意刁难人没有让你从零实现一个 React而是挑了几个工程中常用的方法让你写出一个能在生产环境用的版本。3.1 手写防抖函数比你想象的更讲究细节题目要求实现一个防抖函数并说明它适用的场景。基础写法很多人都会闭包加定时器function debounce(fn, delay) { let timer null; return function(...args) { const context this; if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); }, delay); }; }但很多人写到这个程度就停了。我当时的做法是继续追问自己一个问题如果用户希望第一次点击立即执行呢这就引出了immediate参数。function debounce(fn, delay, immediate false) { let timer null; let invoked false; return function(...args) { const context this; if (immediate !invoked) { fn.apply(context, args); invoked true; } if (timer) clearTimeout(timer); timer setTimeout(() { invoked false; timer null; }, delay); }; }这个版本的处理逻辑是首次触发时立即调用一次然后开始计时在delay时间内再次触发不会执行计时结束后重置状态下一次触发会再次立即执行。这个场景在搜索框输入建议时非常常用——用户第一次输入时先展示热门数据后面的输入才走防抖逻辑。我在批注里会特别强调一个坑手写防抖时要记得处理this指向。如果直接用箭头函数写回调里面的this会是定义时的上下文而不是调用时的上下文很容易在 React 组件里出 bug。用function加apply的方式是最稳妥的。3.2 手写深拷贝一层一层剥开复杂数据类型这道题有个常见的误区很多人一上来就写JSON.parse(JSON.stringify(obj))但这道题在批改时会明确扣分因为undefined、函数、Symbol 会被吃掉Date会被转成字符串RegExp、Map、Set会变成空对象循环引用会直接报错NaN会变成null我当时的实现分了好几步保证覆盖大多数情况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.source, target.flags); if (map.has(target)) { return map.get(target); } const cloneTarget Array.isArray(target) ? [] : {}; map.set(target, cloneTarget); Reflect.ownKeys(target).forEach(key { cloneTarget[key] deepClone(target[key], map); }); return cloneTarget; }这里有两个容易被忽略的点。第一是WeakMap的引入它专门用来解决循环引用问题而且WeakMap的键是弱引用不会造成内存泄漏比普通Map更合适。第二是Reflect.ownKeys可以取到包括 Symbol 在内的所有键比Object.keys更全面。笔试的时候我不会写这么完整的版本但会把关键点列出来基本类型直接返回、引用类型递归、循环引用要用数据结构记录、特殊对象要单独处理。这样即使代码不够完整也能让批卷人看到你的边界意识。4. 框架与工程化题目背后是业务现场的浓缩小红书的前端业务有一个特点C 端页面多内容分发逻辑复杂列表页和详情页的渲染性能会直接影响用户留存。所以框架和工程化的题目不是泛泛地问虚拟 DOM 是什么而是把许多真实场景中冒出来的问题直接搬到了卷子上。4.1 关于虚拟 DOM 的应用层考察题目描述大致是这样一个长列表页面每次数据更新都会造成明显卡顿你如何分析和优化并说明你在优化过程中对虚拟 DOM 和 Diff 算法的理解。这道题没有一个标准答案它是开放式的核心在于你有没有真正处理过类似的渲染性能问题。我当时的分析思路分成几条线第一先判断卡顿发生在哪个阶段。如果网络请求回来后数据量大可能是 JS 主线程执行时间过长如果是页面滚动卡顿可能是频繁触发重排重绘如果每次交互都明显延迟可能是 Diff 过程过重或者组件更新范围太大。第二针对更新范围过大的问题React 的key是否正确设置是非常关键的前提。key的作用是让 Diff 算法可以复用已有节点而不是把整个列表推倒重建。我之前在项目里遇到过一个问题因为列表项用了数组下标作为key结果删除中间某一项后后面的每一项状态全部错乱。所以我在回答里专门写了一句key不是写给开发者看的是写给 Diff 算法看的它的选择必须保证节点的唯一性和稳定性。第三如果 Diff 本身没问题可以考虑从渲染机制上做优化。React 的memo能阻止不必要的子组件重渲染useMemo和useCallback可以缓存计算结果和函数引用。但这些 API 不是无脑使用过度 memo 反而会导致比较开销大于重新渲染的开销这种时候要用性能分析工具先量化一下再决定要不要加。4.2 构建与部署一场关于静态资源的省钱题另一道工程化题目让我印象很深它问一个页面首次加载时静态资源加载过慢首页白屏时间长你会从哪些方面优化这道题涵盖的面很广如果只答CDN、缓存、压缩三个词得分会很低。我当时的回答分成了四个层次一是资源体积层面。代码分割、按需加载、压缩混淆这个大家都能想到但还有一个细节容易被忽略——路由级别的懒加载。如果首屏只用到了首页的组件就不应该一次性把整站 JS 都打包下来。二是请求链路层面。CDN 的边缘节点选择、HTTP 缓存策略里的Cache-Control和ETag配合使用、雪崩和穿透场景下的处理方案这些都属于链路优化。三是渲染层面。SSR 或预渲染、骨架屏、关键 CSS 内联这些手段能极大缩短用户感知到的白屏时间。四是网络协议层面。HTTP/2 多路复用、资源预加载preload和预连接preconnect这些是更进阶的优化点提出来会让人觉得你不只是在背八股文而是真的了解现代 Web 性能优化体系。我记得当时还加了一个很实际的建议在项目里接入性能监控平台把真实用户的首屏时间、白屏时间、资源加载耗时都量化出来。没有数据支撑的优化都是盲目的这个意识在面试里很加分。5. 选做题与开放题拉开差距的关键战场这套试卷最后有一两道开放性问题具体形式记不太清了但类型我记得很牢一类是设计类给一个场景让你设计方案另一类是观点类让你谈谈对某个前端趋势的看法。这类题没有标准答案但恰恰是最能体现功底的。5.1 设计方案题先定目标再谈架构我当时遇到的设计题核心是如何设计一个支持多端展示的内容卡片组件要求同时满足 Web 端和移动端的布局需求并且能方便地扩展新卡片类型。这道题如果直接从我要用一个大的 JSON 配置驱动渲染开始答方向没错但会被认为思考太浅。我是这样拆解的第一步明确边界。卡片组件管理的核心是结构与样式解耦。结构上一张卡片由封面区、标题区、摘要区、交互区四块组成样式上不同端的差异密度、字号、间距应通过设计令牌来控制而不是在每个卡片里写死。基于这个思路再进一步拆分技术方案卡片类型通过注册机制维护一个映射表新增类型时只需注册对应的渲染器而不需要改动卡片容器代码。第二步数据层设计。考虑服务端下发的数据字段可能不统一需要做一层适配器将不同源的数据标准化避免组件内部写大量兼容逻辑。第三步做性能预判。长列表滚动场景下卡片组件需要配合虚拟滚动使用图片懒加载要按真实滚动位置触发而不是简单地用 loadinglazy 糊弄过去。设计题最忌讳的是一锅端。把一个方案铺得很满看起来面面俱到实际没有重点。正确的做法是先定义系统边界再突出核心难点最后给出可以落地的细节。5.2 观点题技术选型背后的思考深度观点题常见的是你如何看待 Vite 和 Webpack 的优劣你如何看待微前端你如何看待 Server Components这类问题。这种题有个高分秘诀不要说某个工具更好而是要说在什么条件下某个工具为什么更合适。比如 Vite 和 Webpack 这种对比我答题时的逻辑是Vite 的开发体验好靠的是原生 ESM 和按需编译不用像 Webpack 那样先全量打包再启动 dev server所以大型项目冷启动很快。但 Vite 在生产构建时底层用的还是 Rollup在代码分割和长缓存策略上生态成熟度和稳定性还比不上 Webpack 深耕多年的插件体系。所以结论不是用 Vite 更好而是如果你的项目是全新的、团队对 Vue/React 的新工具链接受度高Vite 是更好的选择如果你在面对复杂的既有工程和大量老插件依赖Webpack 的稳妥性更有价值。这种回答方式能透露出一个信息你对技术有判断力不会盲目追新清楚技术在业务里的适用边界。对校招生来说这比堆砌一堆新名词更打动人。6. 复习路线从这套卷子倒推备战清单如果你准备参加下一次校招我建议不要只盯着笔试题本身而是从这套卷子倒推出一份复习路线。我把大部分过来人的共同经验整理在这里可以直接对照查漏补缺。第一板块是 JavaScript 核心。重点复习执行上下文、作用域链、闭包、原型链、Event Loop、Promise、async/await、模块机制。这一块没有捷径需要把每个概念用代码验证过一遍而不是停留在我知道的层面。比如你可以自己写代码测试一下 Promise 构造函数里的代码是同步执行还是异步执行实测之后对很多调度问题的理解会彻底打通。第二板块是浏览器与网络。渲染流程、回流重绘、缓存机制、HTTP 请求头、跨域方案、Web 安全XSS、CSRF都需要了解。我的建议是打开 DevTools 的 Performance 和 Network 面板找几个真实页面录制分析一下比背图有效果得多。第三板块是框架与工程化。不要只停留在会写组件的阶段至少要把 React 的 Diff 策略、状态更新机制、Hooks 的设计动机、代码分割、懒加载这些主题过一遍。工程化方面熟悉 Webpack 核心概念loader、plugin、tapable、Vite 的构建链路、CI/CD 的基本概念就够应付笔试了。第四板块是手写代码。常见的手写题包括防抖节流、深拷贝、Promise 系列、数组去重与扁平化、发布订阅、LazyMan 等。我建议准备一个自己的代码仓库把这些实现分门别类整理好并附上注释和测试用例。这个过程能帮你把手写题从背答案变成真的理解。我还建议大家做一件事把做错的题整理一个错题本按主题归类。等考前一周只看错题本就好不再刷新题。这个习惯在校招期间帮我省了很多时间推荐给你。这套卷子最值得琢磨的不是某一题的解法而是它揭示了一条完整的能力链路从语言底层机制到浏览器运行环境再到框架与工程化决策每一环都需要扎实的积累。如果你能按照这个链路补齐自己的短板那这份笔试题的价值就远远超过了一场考试本身。
返回列表