微步在线前端开发工程师笔试+面试准备

微步在线前端开发工程师通过牛客进行笔试的备考入口,整理公开回忆题、牛客题型风格、高频考点和准备建议。

#tech / dev / frontend #type / howto #status / growing #resource / interview #source / company / weibu

[!info] related notes

微步在线前端开发工程师笔试+面试准备

微步在线前端岗的公开“完整原卷”不多,但牛客回忆题已经足够看出风格:React 和浏览器原理占比不低,JS/CSS 基础必须稳,算法不会缺席。

这篇怎么用

  1. 先看自己能不能口头回答文中的高频题。
  2. 不会的题,顺着后面的相关笔记补知识点。
  3. 真到牛客笔试前,只看“复现版题单”和“最后速背清单”。

目前公开信息的稳定判断

比较明确的情况有两点:

  • 微步在线前端岗的牛客公开题库很少,公开页更容易看到的是面经和回忆题,而不是整套原卷。
  • 从岗位要求和回忆题看,前端岗会稳定覆盖:数据结构与算法、网络基础、HTML/CSS/JavaScript、HTTP、浏览器原理、React。

因此最稳的准备方式不是等完整原卷,而是把公开回忆题吃透,再按 JD 主线把关联知识补齐。

题型画像

从公开回忆看,微步前端笔试/技术面比较像这几类题的组合:

  • CSS 布局和样式覆盖
  • JS 基础与输出题
  • Promise / 事件循环 / 异步错误处理
  • React Hooks / JSX / key / Fiber
  • 浏览器缓存、关键渲染路径、HTTP
  • 一到两道常规算法题

如果按牛客常见形式推断,比较像“选择/简答/代码题混合”,不是纯算法场。

已公开的高价值回忆题

1. 2024 微步前端实习回忆

这一组非常贴近前端笔试:

2. React 原理和工程化向回忆题

这组题更偏一面或深挖:

3. 前端一面 / 二面回忆

这组更偏面试现场:

高频考点清单

CSS / 布局

JS / 语言机制

异步 / Promise

React

浏览器 / 网络 / 工程化

算法

10 分钟刷题:简易模板渲染引擎

这题考字符串替换、嵌套属性访问和容错处理。最稳的写法是 replace 配合回调函数,再把 {{user.name}}. 逐层取值,取不到就返回空字符串。

题目:实现 renderTemplate(template, data),解析模板中的 {{key}} 并替换为 data 中对应的值。

要求:支持嵌套属性、属性不存在时渲染为空字符串、允许大括号内部有空格。

function renderTemplate(template, data) {
  const regex = /\{\{\s*(.+?)\s*\}\}/g;

  return template.replace(regex, (match, key) => {
    const paths = key.trim().split('.');
    let value = data;

    for (const path of paths) {
      if (value === undefined || value === null) {
        return '';
      }
      value = value[path];
    }

    return value === undefined || value === null ? '' : String(value);
  });
}

10 分钟刷题:高亮关键词

这题考 DOM 遍历和文本节点处理。不要直接替换 innerHTML,而是只处理 TextNode,再用正则分割重建节点。

题目:实现 highlight(word),只高亮 jsContent 节点内的文本,不能跨标签。

要求:支持前缀递减匹配、同一内容只高亮一次、不能跨标签高亮、高亮格式用 <em>

function highlight(word) {
  if (!word) return;

  const root = document.getElementById('jsContent');
  if (!root) return;

  const prefixes = [];
  for (let i = word.length; i > 0; i--) {
    prefixes.push(word.slice(0, i).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
  }

  const regex = new RegExp(`(${prefixes.join('|')})`, 'g');

  function processNode(node) {
    const children = Array.from(node.childNodes);

    for (const child of children) {
      if (child.nodeType === 3) {
        const text = child.nodeValue;
        if (!text) continue;

        const parts = text.split(regex);
        if (parts.length <= 1) continue;

        for (let i = 0; i < parts.length; i++) {
          if (!parts[i]) continue;

          if (i % 2 !== 0) {
            const em = document.createElement('em');
            em.textContent = parts[i];
            node.insertBefore(em, child);
          } else {
            node.insertBefore(document.createTextNode(parts[i]), child);
          }
        }

        node.removeChild(child);
      } else if (child.nodeType === 1) {
        processNode(child);
      }
    }
  }

  processNode(root);
}

牛客复现版题单

下面这组题可以直接按”微步笔试模拟卷”去刷,每题后附相关原子笔记链接,点击即可跳转复习:

复现题参考答案速记

垂直居中

速记答案:

最通用的是 Flex:父容器 display: flex; justify-content: center; align-items: center;。已知宽高时也可以绝对定位加 transform: translate(-50%, -50%)。单行文字垂直居中还能用 line-height 等于容器高度,但场景比较窄。

左固定右自适应两栏布局

速记答案:

最稳的是 Flex,左边固定宽度,右边 flex: 1。也可以左边 float、右边形成 BFC,或者右边用 calc(100% - 200px)。面试里优先说 Flex,因为表达最直接。

CSS 选择器权重

速记答案:

行内样式最高,然后是 id,再是 class / 属性 / 伪类,最后是标签和伪元素。比较权重时是按位比较,不是简单十进制相加。!important 会强行提高优先级,但不应该滥用。

组件库样式为什么覆盖不掉

速记答案:

一般先看四件事:选择器优先级够不够、样式加载顺序对不对、组件库有没有内联样式、样式是不是被 CSS Modules 或作用域机制隔离了。更稳的做法是优先走组件库提供的主题、变量和扩展 class,而不是一味堆选择器。

JS 基本类型和数组判断

速记答案:

基本类型有 numberstringbooleanundefinednullsymbolbigint。数组判断最稳的是 Array.isArray()。更通用的类型判断可以用 Object.prototype.toString.call(x)

var / let / const 和变量提升

速记答案:

var 存在函数作用域和变量提升,未赋值前访问是 undefinedletconst 是块级作用域,存在暂时性死区,声明前访问会报错;const 不能重新赋值,但如果值是对象,内部属性仍可改。函数声明也会提升,而且通常先于 var 生效。

Promise 的状态和链式调用

速记答案:

Promise 只有 pendingfulfilledrejected 三种状态,一旦从 pending 变成后两者之一就不会再变。链式调用本质上是 then 返回一个新的 Promise,前一个 then 的返回值会传给下一个。

把 ajax 封装成 Promise

速记答案:

核心就是在 Promise 构造函数里发请求,请求成功时 resolve,失败时 reject。重点不是 API 细节,而是把回调式异步改成状态驱动的结果对象。

function ajaxPromise(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.responseText);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => reject(new Error('network error'));
    xhr.send();
  });
}

事件循环

速记答案:

先执行同步代码;当前宏任务执行完后,先清空微任务队列;再进入下一个宏任务。Promise 的 thencatchfinally 回调属于微任务,setTimeoutsetInterval 属于宏任务。

for + setTimeout 为什么输出一样,怎么修

速记答案:

如果用 var,循环结束后所有回调共享同一个变量,所以最后拿到的是同一个值。修法有三种:改成 let、用立即执行函数形成闭包、给 setTimeout 额外传参。

React 的 key

速记答案:

key 是列表节点的稳定身份标识。框架 diff 时靠它判断哪些节点是新增、删除、复用。没有 key 或乱用 index,在插入、删除、排序场景下容易出现状态错位和不必要重渲染。

JSX 为什么会变成 React.createElement

速记答案:

JSX 不是浏览器原生语法,它只是语法糖。编译阶段会被 Babel 一类工具转成 React.createElement(...) 或新的 JSX Runtime 调用,本质上仍然是在创建 React Element 对象。

Fiber 解决了什么问题

速记答案:

它解决的是旧版 React 更新过程不可中断、长任务容易阻塞主线程的问题。Fiber 把更新拆成更细的工作单元,可以暂停、恢复、按优先级调度,让页面在复杂更新时仍然更容易保持响应。

浏览器缓存里的 304 / memory cache / disk cache

速记答案:

304 是协商缓存命中,资源没变,服务器告诉你继续用本地副本。memory cache 是内存缓存,速度最快,通常当前页面生命周期里更常见。disk cache 是磁盘缓存,持久性更强,但读取比内存慢。

关键渲染路径

速记答案:

浏览器拿到 HTML 后会解析 DOM,解析 CSS 生成 CSSOM,再合成 Render Tree,之后做 Layout 和 Paint。性能优化通常围绕减少阻塞资源、缩短关键路径、降低重排重绘成本来做。

数组洗牌

速记答案:

用 Fisher-Yates 洗牌。核心是从后往前遍历,每次在 [0, i] 随机选一个位置交换,这样才能保证等概率。

function shuffle(arr) {
  const res = arr.slice();
  for (let i = res.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [res[i], res[j]] = [res[j], res[i]];
  }
  return res;
}

两数之和

速记答案:

用哈希表记录已经遍历过的值和下标。每次先看目标差值在不在表里,在就返回,不在就把当前值存进去。时间复杂度 O(n)

两个栈实现队列

速记答案:

一个栈负责入队,一个栈负责出队。出队时如果输出栈为空,就把输入栈元素全部倒过去,再弹出栈顶。这样均摊复杂度是 O(1)

全排列

速记答案:

典型回溯题。维护路径和已使用标记,每层选一个还没用过的数加入路径,递归到底后收集答案,再回溯撤销选择。

斐波那契

速记答案:

最容易超时的是纯递归。面试更稳的是迭代版或 DP,用两个变量滚动保存前两项,时间 O(n),空间 O(1)

这些题至少要答到什么程度

CSS / 布局

  • 垂直居中:至少能讲 Flex、绝对定位+transform、行高或 Grid
  • 两栏布局:至少能写 Flex、BFC 或 calc 方案
  • 样式覆盖:至少知道优先级、选择器层级、样式注入顺序、CSS Modules / scoped 的边界

JS / 异步

  • 类型判断:至少会 typeofinstanceofArray.isArray()Object.prototype.toString.call
  • 变量提升:至少知道 var 声明提升、let/const 暂时性死区、函数声明提升
  • 事件循环:至少能准确说清同步、微任务、宏任务顺序
  • Promise:至少能讲状态只能从 pending 变成 fulfilled/rejected,且状态一旦确定不可逆

React

  • Hooks:至少能讲 useStateuseEffectuseRef 的边界
  • key:至少能讲“稳定标识节点身份,不建议乱用 index”
  • Fiber:至少知道它是为了让渲染任务可中断、可分片调度,而不是一次性阻塞主线程

浏览器 / 工程化

  • 缓存:至少知道强缓存、协商缓存,以及 DevTools 里常见的 memory cache / disk cache
  • 关键渲染路径:至少能从 HTML 解析、CSSOM、Render Tree、Layout、Paint 主线讲下来
  • webpack / babel:至少知道 webpack 是打包构建工具,babel 负责语法转换,AST 是代码的语法树表示

典型问答模板

组件库样式为什么覆盖不掉

推荐答法:

先看是不是优先级不够,再看样式注入顺序,最后看组件库本身有没有内联样式、CSS Modules 或更高优先级规则。覆盖组件库样式不能只想着加更长选择器,还要判断是否会造成维护成本失控。更稳的做法是优先使用它提供的主题、变量或 class 扩展能力,实在不行再做局部覆盖。

Promise 和事件循环为什么总一起问

推荐答法:

因为 Promise 回调会进微任务队列,很多输出题本质上考的是你知不知道同步代码先执行,当前宏任务结束后清空微任务,再执行下一个宏任务。只会背 Promise API 但讲不清事件循环,通常一做输出题就会乱。

Fiber 相比旧 Stack 架构解决了什么

推荐答法:

旧的 Stack Reconciler 更像一次性递归执行,任务一旦开始就不容易打断,复杂页面更新时可能长时间占用主线程。Fiber 的关键改进是把更新过程拆成更细的小任务,让调度更灵活,可以中断、恢复和分优先级处理,从而改善大更新场景下的响应性。

为什么 try/catch 抓不到某些异步错误

推荐答法:

因为 try/catch 只能捕获当前同步调用栈里的异常。像 setTimeout 回调、点击事件回调这类异步任务,执行时已经不在原来的调用栈里了,所以外层 try/catch 抓不到。Promise 场景一般要配合 .catch()async/await + try/catch 在对应异步边界里处理。

手写题优先级

第一优先

  • 两数之和
  • 两个栈实现队列
  • 数组洗牌

第二优先

  • 全排列
  • 斐波那契
  • Promise 封装题

第三优先

  • 手写 Promise 链式调用

这一题更偏一面深挖,不一定是笔试必出,但很能区分你对异步模型到底理解到哪一层。

对应知识点链接

CSS

JS 基础

异步 / Promise

React

浏览器 / 网络 / 工程化

算法

备考顺序建议

  1. 先刷 JS 基础:类型判断、提升、thisnew、闭包
  2. 再刷异步:Promise、事件循环、输出题、异步异常
  3. 再刷 React:Hooks、key、JSX、Fiber
  4. 再刷浏览器和工程化:缓存、渲染路径、webpack/babel/AST
  5. 最后补算法:两数之和、队列栈、全排列、斐波那契

最后速背清单

  • CSS:垂直居中、两栏布局、选择器权重、样式覆盖
  • JS:类型判断、提升、this、闭包、new
  • 异步:Promise、事件循环、宏任务微任务、异步错误处理
  • React:Hooks、key、JSX、Fiber
  • 浏览器:缓存、关键渲染路径、HTTP
  • 算法:两数之和、数组洗牌、两个栈实现队列、全排列、斐波那契

实战提醒

  • 微步前端不是只问“会不会 React API”,而是会顺着 React 继续问到调度、渲染和工程链路。
  • 牛客题库公开量不高,所以你要重点吃透“回忆题 + JD 高频点”的交集,而不是等完整原题。
  • 如果时间不够,优先保证 JS 异步、React、浏览器缓存和两三道基础算法题能稳定答出来。

一面模拟问答

这一段原来是独立的一面模拟问答,现在合并到主笔记里,方便统一维护。

自我介绍

我是 XX,主要做前端开发,平时接触比较多的是 JavaScript、React 或 Vue,以及常见的前端工程化实践。最近我在重点补浏览器、网络、异步模型和 React 原理,因为我希望找的是既要求基础扎实,也要求工程落地能力的前端岗位。做项目时我比较关注需求拆解、问题定位和交付质量,所以这次也主要在投对前端基础和工程能力要求更高的团队。

介绍一个你最满意的项目

我最满意的项目是 X。这个项目的目标是 Y,我主要负责 A、B 两个模块。这里面最有挑战的问题是 Z,因为它同时影响用户体验和研发效率。我当时先确认问题边界,再比较不同方案的成本和收益,最终选了一个更稳的方案落地。结果上,这个项目把某个关键指标优化到了 N,或者把某类线上问题明显压下来了。

这个项目里最难的问题是什么

最难的不一定是代码量最大,而是那种会同时影响稳定性、体验和协作效率的问题。对我来说最难的是 X,因为它不是单点 bug,而是涉及到 A、B、C 几个环节。我当时先把问题拆开,定位是前端状态管理、接口时序还是服务端返回问题,再逐步收敛方案。

你具体做了什么

我主要负责需求拆解、核心页面实现、接口联调,以及上线后的问题排查和小范围性能优化。真正由我主导的部分是 A 和 B,因为这两个模块既直接影响用户主流程,也更容易暴露稳定性问题。

如果现在让你重做一次,你会怎么优化

如果重做一次,我会优先优化两类东西。第一类是结构性问题,比如状态边界、组件拆分、接口层抽象;第二类是观测能力,比如埋点、日志、错误监控。这样后面无论是定位问题还是继续迭代,成本都会更低。

varletconst 的区别

关键区别在作用域和提升表现。var 是函数作用域,会提升,声明前访问得到 undefinedletconst 是块级作用域,存在暂时性死区,声明前访问会报错;const 不能重新赋值,但如果是对象,内部属性仍可改。

this 是怎么确定的

this 不是定义时决定,而是调用位置决定。普通函数默认绑定看运行环境;对象方法调用时指向调用它的对象;call/apply/bind 可以显式指定;箭头函数没有自己的 this,会捕获外层词法作用域的 this

事件循环怎么理解

先执行同步代码,当前宏任务结束后清空微任务队列,再进入下一个宏任务。Promise 的回调是微任务,setTimeout 是宏任务。很多输出题本质上考的就是你能不能分清这条执行顺序。

为什么 try/catch 抓不到 setTimeout 里的错误

因为外层 try/catch 只能捕获当前同步调用栈里的异常。setTimeout 回调真正执行时已经是后面的宏任务了,不在原来的调用栈里,所以抓不到。异步场景要在对应异步边界里处理错误,比如 Promise 的 .catch()async/await + try/catch

Promise 为什么适合链式调用

因为 then 会返回一个新的 Promise,前一个步骤的结果可以自然传给下一个步骤。这样可以把原本层层嵌套的回调改成线性的异步流程,代码更容易维护,也更容易统一处理错误。

React 中 key 有什么用

key 是列表节点的稳定身份标识。React diff 时会用它判断哪些节点需要复用、哪些节点是新增或删除。如果没有 key 或乱用 index,在插入、删除、排序场景下容易出现状态错位和额外渲染。

常用 Hooks 你怎么理解

useState 用来管理组件内部状态,useEffect 用来处理副作用,比如订阅、请求、同步外部系统,useRef 用来保存不触发重渲染的可变值,或者拿 DOM 引用。面试里最好别只背 API,而是讲清它们各自的边界。

JSX 为什么能运行

JSX 本身不是浏览器认识的语法,它会先被编译工具转换成 React.createElement(...) 或新的 JSX Runtime 调用,最终本质上还是在创建 React Element 对象。

Fiber 相比旧架构解决了什么问题

旧版更新过程更像一次性递归,任务开始后不容易打断,复杂更新时容易长时间占用主线程。Fiber 把任务拆细后,更新可以暂停、恢复、按优先级调度,所以在复杂场景下页面更容易保持响应。

浏览器缓存怎么答

先区分强缓存和协商缓存,细节可看 浏览器缓存分类与 304 / memory cache / disk cache。强缓存命中时直接用本地副本,不发请求;协商缓存会发请求让服务端确认资源有没有变化,常见结果是 304。DevTools 里还经常能看到 memory cachedisk cache,它们更多是在说明缓存副本存放的位置。

关键渲染路径是什么

浏览器拿到 HTML 后会解析 DOM,解析 CSS 得到 CSSOM,再合成 Render Tree,之后做 Layout 和 Paint。性能优化常见做法就是减少阻塞资源、缩短关键路径和降低重排重绘开销。

webpack、babel、AST 分别是什么

webpack 主要负责模块打包和构建流程组织,babel 主要负责语法转换,AST 是代码的抽象语法树表示。很多代码转换、静态分析和工程化能力,本质上都是围绕 AST 做处理。

两数之和怎么答

我会优先用哈希表。遍历数组时先看目标差值是否已经出现过,如果出现过就直接返回下标,否则把当前值和下标存进去。这样时间复杂度是 O(n)

两个栈实现队列怎么答

一个栈负责入队,一个栈负责出队。出队时如果输出栈为空,就把输入栈全部倒过去,再弹栈顶。这样每个元素最多搬运一次,均摊复杂度是 O(1)

数组洗牌怎么答

正确洗牌要用 Fisher-Yates。核心是从后往前遍历,每次在 [0, i] 范围里随机选一个元素交换,这样才能保证每种排列概率一致。

反问面试官

  • 这个岗位在前端侧更偏业务交互、平台建设,还是偏 React 工程和性能优化
  • 团队对前端同学的期待更偏快速交付,还是对基础原理和工程质量也会有明确要求
  • 这个岗位进去后最先接触的业务和技术难点通常是什么
创建于 2026/3/25 更新于 2026/5/27