← 返回知识库
前端工程前端总纲HTMLCSSJavaScriptReact工程化网络

前端面试总纲

通用前端考点详答:HTML/CSS → JavaScript → 框架 → 工程化 → 网络 → 计算机基础 → 进阶专题,覆盖第一轮系统复习。

本文定位:这是通用前端面试总纲,按「HTML/CSS → JavaScript → 框架 → 工程化 → 网络 → 计算机基础 → 进阶专题」逐项给出详细答案。它不是目录索引(文件名 index.md 是历史遗留,容易误解)。

与同目录其他文档的分工(避免重复背诵):

  • 本文 = 总纲 / 覆盖面最广,适合第一轮系统复习;
  • 前端面试真题130题完整答案.md = 真题演练(按原题表顺序,含 12 张图解),适合第二轮;
  • Vue3.md / Threejs.md / WebAssembly.md / 浏览器渲染优化指南 = 专项深挖,本文对应章节只给结论,细节请看专项文档。

第一阶段:核心基础知识答案

1. HTML & CSS 答案

1.1 语义化

  • 答案: 语义化是指用恰当含义的HTML标签(如 <header><nav><article><section><aside><footer>)来构建页面结构,而不是通篇使用 <div><span>
  • 为什么重要?
    1. 对开发者友好: 使代码结构清晰,易于阅读、维护和团队协作。
    2. 对SEO友好: 搜索引擎爬虫依赖标签来判断网页内容和权重,有助于提升搜索排名。
    3. 对无障碍友好: 屏幕阅读器等辅助技术可以根据语义标签更好地理解并朗读页面,帮助视障用户访问。

1.2 盒模型

  • 答案: CSS盒模型描述了元素在布局中占据的空间,由内到外包括:content(内容)padding(内边距)border(边框)margin(外边距)
  • 标准盒模型 vs IE盒模型:
    • 标准盒模型: width = content 的宽度。元素实际占宽 = width + padding + border + margin
    • IE盒模型 (怪异盒模型): width = content + padding + border 的宽度。元素实际占宽 = width + margin
  • box-sizing 的应用:
    • box-sizing: content-box; (默认) 使用标准盒模型。
    • box-sizing: border-box; 使用IE盒模型。在实际开发中更常用,因为它让设置的宽度直接就是元素可见宽度的总和,方便布局,尤其在处理百分比宽度和内边距时。

1.3 BFC (块级格式化上下文)

  • 答案: BFC 是 Web 页面的一块独立渲染区域,内部元素的布局不会影响外部元素。可以把它想象成一个隔离的容器。
  • 如何触发? 以下任一条件均可触发:
    • 根元素 (<html>)
    • 浮动元素 (float 不为 none)
    • 绝对定位元素 (positionabsolutefixed)
    • displayinline-blocktable-celltable-captionflexinline-flexgridinline-grid
    • overflow 不为 visible 的元素 (如 overflow: hidden/auto/scroll)
  • 解决了什么问题?
    1. 清除浮动: 计算BFC高度时,浮动元素也参与计算,可以解决父元素高度塌陷问题。
    2. 防止外边距重叠: 同一个BFC内的两个相邻块级元素,上下 margin 会重叠。但分属不同BFC的元素,margin 不会重叠。
    3. 阻止元素被浮动元素覆盖: 一个开启了BFC的元素不会与浮动元素重叠,可用于实现自适应两栏布局。

1.4 重绘与回流 (Repaint & Reflow)

  • 答案:
    • 回流 (Reflow): 当页面布局和几何信息改变(如窗口大小改变、增删DOM、修改元素宽高/位置)时,浏览器需要重新计算元素的位置和大小,这个过程叫回流。
    • 重绘 (Repaint): 当元素外观发生改变(如改变颜色、背景色、可见性 visibility),但不影响布局时,浏览器会重新绘制该元素,这个过程叫重绘。
  • 如何避免?
    • 避免逐条修改样式,最好一次性修改 class 或使用 cssText
    • 将需要多次重排的元素的 display 设为 none,操作完再显示。
    • 使用文档片段 documentFragment 批量操作DOM。
    • 对于复杂动画,使用 position: absolute/fixed 使其脱离文档流,减少对后续元素的影响。
    • 避免使用 table 布局,一个小改动可能引起整个表格回流。

1.5 移动端适配

  • 答案: 主要方案有 remvw/vh
    • rem 方案: 相对 html 根元素的字体大小。通过JS动态计算视口宽度并设置根元素 font-size,所有使用 rem 单位的元素都会等比缩放。常用方案:手淘的 flexible.js 库(原理是设置 data-dprfont-size)。
    • vw/vh 方案: vw (viewport width) 视口宽度的 1/100,vh 同理。直接使用 vw 作为单位,无需JS。例如:设计稿宽度750px,一个元素宽150px,则可写为 width: (150/750)*100vw = 20vw
    • 1px 问题: 在高分辨率(DPR=2/3)屏幕上,CSS 的 1px 会被渲染成 2~3 个物理像素,看起来偏粗。注意:不能直接对整个元素 transform: scale(0.5)——那会把内容和文字一起缩小。正确做法是给伪元素画边框再缩放它
      .hairline { position: relative; }
      .hairline::after {
        content: ''; position: absolute; left: 0; top: 0;
        width: 200%; height: 200%;
        border: 1px solid #ddd;
        transform: scale(0.5);          /* 只缩这条伪元素边框 */
        transform-origin: 0 0;
        pointer-events: none;
      }
      @media (-webkit-min-device-pixel-ratio: 3) { /* DPR=3 时 scale(0.333) */ }
      
      其他方案:box-shadow: 0 0 0 0.5px #ddd 模拟、border-image、或用 viewport 缩放(initial-scale 配合 rem)。

2. JavaScript 答案

2.1 闭包

  • 答案: 闭包是指有权访问另一个函数作用域中变量的函数。它通常是一个函数内部嵌套了另一个函数,并将该内部函数返回。
  • 原理: 利用JavaScript的词法作用域(静态作用域)机制,内部函数引用了外部函数的变量,即使外部函数执行完毕,其变量对象也不会被销毁,因为被内部函数引用着。
  • 应用场景:
    1. 创建私有变量: 模拟私有方法。
    2. 函数柯里化: 将多参数函数转化为单参数函数链。
    3. 在循环中保存变量状态(如 var 导致的经典问题,常用立即执行函数或 let 解决)。
  • 缺点: 使用不当可能导致内存泄漏(因为变量无法被垃圾回收)。解决: 在退出函数前,将不再使用的局部变量置为 null

2.2 原型与原型链

  • 答案: 每个函数都有一个 prototype 属性,它指向一个对象,这个对象就是通过该构造函数创建的实例的原型。每个对象都有一个 __proto__ 属性,它指向创建该对象的构造函数的 prototype。当访问一个对象的属性时,如果对象本身没有,就会沿着 __proto__ 链向上查找,直到找到或链的末端(Object.prototype.__proto__null),这就是原型链
  • 关系: 实例.__proto__ === 构造函数.prototype构造函数.prototype.constructor === 构造函数本身
  • 继承实现:
    • ES5 (组合继承): 使用 Parent.call(this) 继承属性,使用 Child.prototype = new Parent() 继承方法。缺点: 调用了两次父构造函数。
    • ES5 (寄生组合继承): 使用 Parent.call(this) 继承属性,使用 Child.prototype = Object.create(Parent.prototype)Child.prototype.constructor = Child 继承方法。这是最理想的ES5继承方式。
    • ES6: class Child extends Parent,本质上是寄生组合继承的语法糖。

2.3 异步编程与事件循环 (Event Loop)

  • 答案: JS是单线程语言,为了协调事件、用户交互、脚本、渲染、网络请求等任务,有了事件循环机制。
  • 宏任务与微任务:
    • 宏任务 (MacroTask): script(整体代码)、setTimeoutsetIntervalsetImmediate (Node.js)、I/O、MessageChannel、UI 事件回调。
      • ⚠️ 「UI 渲染」不是宏任务——渲染是浏览器在"微任务清空后、下一个宏任务前"择机执行的独立步骤,不在任务队列里。同理 requestAnimationFrame 也不属于宏/微任务,它挂在渲染步骤之前。这两个是最常见的错答点。
    • 微任务 (MicroTask): Promise.then/catch/finallyMutationObserverqueueMicrotaskprocess.nextTick (Node.js)。
  • 执行流程:
    1. 执行一个宏任务(最开始执行 script 整体代码)。
    2. 执行过程中遇到微任务,将其放入微任务队列。
    3. 当前宏任务执行完毕后,清空整个微任务队列(按入队顺序执行)。
    4. 微任务队列清空后,浏览器可能会进行UI渲染(如果有需要)。
    5. 从宏任务队列中取出下一个宏任务执行,循环往复。
  • 浏览器与Node环境区别: Node.js 之前版本有 process.nextTick(优先级高于微任务)和微任务队列,并且有 setImmediate。新版本Node与浏览器趋于一致,但仍有微小差异(如 setTimeoutsetImmediate 执行顺序不确定)。

2.4 手写代码 (核心)

  • 防抖 (Debounce)
    function debounce(func, wait, immediate) {
        let timeout;
        return function(...args) {
            const context = this;
            const later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            const callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    }
    
  • 节流 (Throttle)
    function throttle(func, wait, options = { leading: true, trailing: true }) {
        let timeout, context, args, result;
        let previous = 0;
    
        const later = function() {
            previous = options.leading === false ? 0 : Date.now();
            timeout = null;
            func.apply(context, args);
            if (!timeout) context = args = null;
        };
    
        const throttled = function(..._args) {
            const now = Date.now();
            if (!previous && options.leading === false) previous = now;
            const remaining = wait - (now - previous);
            context = this;
            args = _args;
            if (remaining <= 0 || remaining > wait) {
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = null;
                }
                previous = now;
                func.apply(context, args);
                if (!timeout) context = args = null;
            } else if (!timeout && options.trailing !== false) {
                timeout = setTimeout(later, remaining);
            }
        };
        return throttled;
    }
    
  • 深拷贝
    function deepClone(obj, hash = new WeakMap()) {
        if (obj === null || typeof obj !== 'object') return obj;
        if (obj instanceof Date) return new Date(obj);
        if (obj instanceof RegExp) return new RegExp(obj);
        if (hash.has(obj)) return hash.get(obj); // 解决循环引用
    
        const cloneObj = new obj.constructor();
        hash.set(obj, cloneObj);
    
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                cloneObj[key] = deepClone(obj[key], hash);
            }
        }
        return cloneObj;
    }
    
  • new 操作符原理
    function myNew(constructor, ...args) {
        // 1. 创建一个空对象
        const obj = {};
        // 2. 将空对象的原型指向构造函数的 prototype
        Object.setPrototypeOf(obj, constructor.prototype);
        // 3. 将构造函数的 this 指向这个新对象,并执行构造函数
        const result = constructor.apply(obj, args);
        // 4. 如果构造函数返回了一个对象,则返回那个对象;否则返回新创建的对象
        return result instanceof Object ? result : obj;
    }
    
  • Promise.all 实现
    function promiseAll(promises) {
        return new Promise((resolve, reject) => {
            if (!Array.isArray(promises)) return reject(new TypeError('Arguments must be an array'));
            const results = [];
            let count = 0;
            if (promises.length === 0) return resolve(results);
    
            promises.forEach((promise, index) => {
                Promise.resolve(promise).then(value => {
                    results[index] = value;
                    count++;
                    if (count === promises.length) resolve(results);
                }).catch(reject);
            });
        });
    }
    

3. 主流框架 (React) 答案

3.1 Virtual DOM 与 Diff 算法

  • Virtual DOM: 是一个JS对象,是对真实DOM的轻量级抽象表示。
  • 为什么快? 直接操作真实DOM代价高(引起回流重绘)。VDOM 在JS层面操作,通过Diff算法找出最小变更,然后一次性批量更新真实DOM,减少了浏览器回流重绘次数。
  • Diff 算法原理 (React):
    1. 同级比较: 只会对同一层级的节点进行比较,不会跨层级比较,复杂度为 O(n)。
    2. 不同类型节点: 如果节点类型不同(如 divp),直接删除旧子树,重建新子树。
    3. 相同类型节点: 递归比较属性,然后继续比较子节点。
    4. 列表比较 (key的作用): 通过 key 来识别节点是否可以复用、移动或删除。key 应该是稳定的、唯一的,最好不要用数组索引,因为索引会随元素删除/插入而变化,导致性能问题或状态错乱。

3.2 Hooks (核心)

  • 为什么要有 Hooks?
    • 逻辑复用难: 之前使用 HOC 和 Render Props 会导致组件嵌套地狱,代码难以理解。
    • 复杂组件难拆分: 生命周期函数里常常混入不相关的逻辑(如数据获取和事件监听)。
    • Class 的困扰: this 指向问题,bind 麻烦,不利于编译优化(如压缩、tree-shaking)。
  • useStatesetState 区别:
    • setState 在类组件中会合并更新(浅合并),而 useStateset 函数是替换更新。
    • setState 有回调函数获取最新状态,useState 需要使用 useEffect
    • 在异步事件中,React 18 之前,setState 在合成事件和生命周期中是异步批处理的,useState 也是;React 18 之后,自动批处理(Automatic Batching)让两者在更多场景下(如 Promise、setTimeout)也能批量更新。
  • useEffectuseLayoutEffect 区别:
    • useEffect 在浏览器绘制完成后异步执行,不会阻塞页面渲染。
    • useLayoutEffect 在 DOM 变更后、浏览器绘制之前同步执行。会阻塞视觉更新,用于读取/修改DOM并立即生效的情况(如测量DOM元素宽高),应尽量少用,避免性能问题。
  • Hooks 原理: React 内部为每个组件维护一个链表来存储 Hooks。useStateuseEffect 等 Hook 的调用顺序决定了它们在链表中的位置,因此Hooks 不能在条件语句、循环或嵌套函数中调用,必须保证每次渲染时顺序一致。

3.3 性能优化

  • React.memo (用于函数组件):props 进行浅比较,如果 props 没变,则跳过该组件及其子组件的重新渲染。
  • useMemo 缓存计算结果,只有依赖项变化才重新计算。
  • useCallback 缓存函数引用,只有依赖项变化才返回新函数。
  • 代码分割: 使用 React.lazySuspense 配合动态 import() 按需加载组件,减少首屏包体积。
  • shouldComponentUpdate / PureComponent 类组件中避免不必要的更新。

4. 工程化与进阶答案

4.1 Webpack

  • Loader 和 Plugin 的区别:
    • Loader: 本质是一个转换器,用于对模块的源代码进行转换。因为 Webpack 原生只认识 JS 和 JSON,Loader 负责处理其他类型文件(如 .css.vue、图片)并将其转换为有效模块。
    • Plugin: 本质是一个扩展器,监听 Webpack 在整个生命周期中广播的事件,通过钩子函数来执行更广泛的任务(如打包优化、资源管理、环境变量注入)。
  • 热更新 (HMR) 原理:
    1. 文件监听: Webpack 监听文件变化,重新编译打包,并将代码保存到内存中。
    2. 通知: webpack-dev-server 通过 WebSocket 与浏览器建立长连接,通知浏览器新模块的 hash
    3. 请求更新: 浏览器端的 HMR runtime 根据 hash 通过 fetch / XHR 向服务器拉取 **hot-update.json(更新清单)**与对应的 hot-update.js(增量模块代码),再执行回调完成替换。
      • ⚠️ 纠偏:早期 webpack-hot-middleware 用的是 JSONP,现代的 webpack-dev-server 已改为 fetch/XHR 拉取 chunk(带 webpackHotUpdate 回调)。答 JSONP 会被认为停留在老版本。
    4. 模块替换: HMR runtime 执行模块替换,并通知应用代码执行相应的回调(如 module.hot.accept),实现页面无刷新更新。
  • 构建流程:
    1. 初始化参数: 从配置文件和 Shell 语句中读取并合并参数,得到最终参数。
    2. 开始编译: 用参数初始化 Compiler 对象,加载所有插件,执行 run 方法。
    3. 确定入口: 根据配置中的 entry 找出所有入口文件。
    4. 编译模块: 从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,递归处理直到所有入口依赖的文件都经过了处理。
    5. 完成模块编译: 得到每个模块被翻译后的最终内容以及它们之间的依赖关系图。
    6. 输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表。
    7. 输出完成: 根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

4.2 TypeScript

  • interfacetype 的区别:
    • 相同点: 都可以用来描述对象或函数类型。
    • 不同点:
      • interface 可以重复声明(声明合并),type 不能重复声明。
      • interface 可以通过 extends 继承,type 可以通过交叉类型 & 扩展。
      • type 可以定义联合类型 (|)、元组、基本类型别名等,更灵活。
      • 官方推荐优先使用 interface,无法满足需求时用 type
  • 泛型: 是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。例如:function identity<T>(arg: T): T { return arg; }
  • 工具类型实现原理:
    • Partial<T>:T 中所有属性变为可选。{ [P in keyof T]?: T[P] }
    • Pick<T, K>:T 中挑选一组属性 K 构建新类型。{ [P in K]: T[P] }
    • Readonly<T>:T 中所有属性变为只读。{ readonly [P in keyof T]: T[P] }
    • Record<K, T>: 构造一个类型,其键名为 K,键值为 T{ [P in K]: T }

5. 浏览器与网络答案

5.1 从输入URL到页面显示完整过程

  • 答案: 这是一个经典的高频面试题,考察知识广度。
    1. URL 解析: 检查输入合法性,判断是搜索关键字还是网址。
    2. DNS 解析: 将域名解析为 IP 地址。查找顺序:浏览器缓存 -> 操作系统 Hosts 文件 -> 本地 DNS 服务器 -> 根/顶级/权威 DNS 服务器。
    3. 建立 TCP 连接: 与目标服务器进行三次握手,建立可靠的连接。
    4. 发送 HTTP/HTTPS 请求: 浏览器向服务器发送请求报文(请求行、请求头、请求体)。如果是 HTTPS,还有 TLS 握手过程。
    5. 服务器处理请求并返回响应: 服务器处理请求并返回响应报文(状态码、响应头、响应体)。
    6. 浏览器接收响应并解析渲染:
      • 判断状态码,处理重定向等。
      • 如果响应内容是 HTML,开始解析渲染过程。
    7. 断开 TCP 连接: 通过四次挥手断开连接(如果是短连接)。现代浏览器通常使用长连接 Connection: keep-alive
    8. 浏览器渲染页面: 解析 HTML 构建 DOM 树,解析 CSS 构建 CSSOM 树,合并成 Render 树,计算布局(Layout/Reflow),绘制(Paint),合成(Composite)。

5.2 浏览器缓存机制

  • 答案: 缓存分为强缓存和协商缓存。
    • 强缓存: 直接从本地缓存读取,不与服务器通信。相关字段:Expires (HTTP/1.0,绝对时间,受本地时间影响) 和 Cache-Control (HTTP/1.1,相对时间,优先级更高,常用 max-age=3600)。
    • 协商缓存: 向服务器发送请求,由服务器判断缓存是否可用。如果可用,返回 304 状态码,让浏览器使用本地缓存。相关字段:Last-Modified (最后修改时间) 和 If-Modified-SinceETag (文件唯一标识,基于内容) 和 If-None-MatchETagLast-Modified 更精确。

5.3 XSS 和 CSRF

  • XSS (跨站脚本攻击): 攻击者将恶意脚本注入到网页中,当其他用户浏览该网页时执行恶意脚本。
    • 防御: 输入过滤,对输出进行转义(HTML 实体编码),使用 CSP (内容安全策略),对 Cookie 设置 HttpOnly
  • CSRF (跨站请求伪造): 攻击者诱导用户访问恶意网站,利用用户在其他网站的登录状态,以用户身份发起恶意请求。
    • 防御: 使用 Token 验证(请求中携带服务器生成的随机 token),验证 Referer,对敏感操作使用二次验证(如验证码)。

6. 计算机基础答案

6.1 数据结构与算法

  • 常见数据结构:
    • 数组: 连续内存空间,随机访问快 O(1),插入删除慢 O(n)。
    • 链表: 非连续内存,插入删除快 O(1),查找慢 O(n)。常见题:反转链表、判断是否有环。
    • 栈: 后进先出 (LIFO)。应用:函数调用栈、括号匹配。
    • 队列: 先进先出 (FIFO)。应用:任务队列。
    • 树: 二叉树、二叉搜索树(左小右大)、平衡二叉树。
    • 哈希表: 键值对存储,通过哈希函数计算存储位置,查找、插入、删除平均 O(1)。应用:对象、Map。
  • 常见算法思想:
    • 递归: 函数调用自身。必须有终止条件。
    • 分治: 将大问题分解为若干个小问题,分别解决后合并。如:归并排序、快速排序。
    • 动态规划: 将复杂问题分解为简单子问题,并保存子问题结果避免重复计算。如:斐波那契数列、背包问题。
    • 回溯: 一种试错思想,尝试分步解决,当发现当前选择不满足条件时,回退到上一步重新选择。如:八皇后、全排列。

当然,面试准备是一个持续迭代的过程,除了上述的核心技术点,还有一些重要的补充内容,可以帮助你在面试中脱颖而出。以下是对之前答案的补充,涵盖了进阶技术、软技能、以及一些更深入的知识点。


补充内容:进阶与扩展

7. 性能优化(进阶篇)

除了常规的代码分割、懒加载,还可以深入理解以下内容:

7.1 性能指标

  • 核心 Web Vitals:
    • LCP (Largest Contentful Paint): 最大内容绘制,衡量加载性能(应≤2.5s)。
    • INP (Interaction to Next Paint): 交互到下一次绘制,衡量页面整个生命周期中的交互响应性(良好阈值通常为 ≤200ms,按第 75 百分位评估)。INP 已于 2024-03-12 取代 FID 成为 Core Web Vital。
    • CLS (Cumulative Layout Shift): 累积布局偏移,衡量视觉稳定性(应≤0.1)。
  • 其他指标: FCP (First Contentful Paint)、TTI (Time to Interactive)、TBT (Total Blocking Time)。

7.2 性能监控

  • RUM (Real User Monitoring): 使用 Performance API 收集真实用户数据,上报到监控平台(如 Sentry、自研)。
  • 性能面板分析: 熟练使用 Chrome DevTools 的 Performance、Lighthouse、Network 面板进行瓶颈分析。

7.3 关键渲染路径优化

  • 减少关键资源数量: 内联关键 CSS,延迟加载非关键 CSS/JS。
  • 减少关键资源大小: 压缩、Tree Shaking、代码分割。
  • 减少关键往返次数: 使用 HTTP/2 多路复用,合并文件。

7.4 渲染层优化

  • 避免强制同步布局: 在修改 DOM 后立即读取布局属性会导致强制回流。
  • 使用 will-change 提前告知浏览器哪些元素会变化,以便优化(但不要过度使用)。
  • CSS 动画优于 JS 动画: 使用 transform 和 opacity 可以触发合成,避免布局和绘制。

8. 微前端

如果你在简历中提到了微前端,或者面试的是中高级岗位,可能会被问到。

  • 定义: 将前端应用拆分成多个独立开发、独立部署的子应用,组合成一个整体。
  • 核心价值:
    • 技术栈无关: 不同团队可以使用不同框架。
    • 独立开发部署: 降低耦合,提升迭代速度。
    • 增量升级: 老项目可以逐步重构,而不是重写。
  • 主流方案:
    • 基座模式 (single-spa, qiankun): 通过路由加载子应用,JS 沙箱、样式隔离。
    • 自组织模式 (Webpack Module Federation): 模块联邦,实现运行时共享模块。
    • 微前端组合层 (iframe): 简单但体验差,不推荐。
  • 核心问题:
    • 样式隔离: CSS Module、CSS-in-JS、Shadow DOM、约定式命名(如 BEM)。
    • JS 沙箱: 快照沙箱、Proxy 沙箱(qiankun 实现原理)。
    • 应用通信: 全局事件总线、URL 传参、共享状态。

9. Node.js 相关知识

前端工程师往往也需要掌握一定的 Node.js 知识,尤其是在工程化、BFF(Backend For Frontend)层。

  • 核心模块: fspathhttpevents
  • 事件循环: Node.js 的事件循环与浏览器的区别(阶段划分:timers、pending callbacks、idle prepare、poll、check、close)。
  • Stream: 可读流、可写流、双工流、转换流。背压处理,流式数据处理优势。
  • 进程与集群: child_processcluster 模块,利用多核 CPU。
  • 常用框架: Express、Koa、Egg.js、NestJS。了解中间件机制(洋葱模型)。
  • BFF 层职责: 接口聚合、数据裁剪、SSR(服务端渲染)、鉴权。

10. 测试

了解测试策略,至少会写单元测试。

  • 测试金字塔: 单元测试 > 集成测试 > E2E 测试。
  • 单元测试工具: Jest、Mocha、Jasmine + 断言库(Chai)。
  • React 测试工具: React Testing Library、Enzyme。
  • E2E 测试: Cypress、Playwright、Puppeteer。
  • 测试要点:
    • 测试前准备(Arrange)、执行(Act)、断言(Assert)。
    • 测试覆盖率(行覆盖率、分支覆盖率)。
    • Mock 外部依赖(网络请求、模块)。

11. 前端架构设计

对于高级岗位,可能会考察架构设计能力。

  • 设计原则: 高内聚低耦合、开闭原则、单一职责。
  • 分层架构: 展示层、业务逻辑层、数据访问层(如 Model-View-ViewModel)。
  • 组件设计: 原子设计(原子、分子、组织、模板、页面),组件分类(基础组件、业务组件、页面组件)。
  • 状态管理选型: Redux、MobX、Zustand、Recoil 的优劣对比和适用场景。
  • 权限设计: 路由权限、按钮权限、数据权限的实现方式。
  • 错误处理: 全局错误边界(Error Boundaries)、请求重试、降级方案。

12. 软技能与行为面试

很多技术不错的候选人会因为软技能不足而败北。

12.1 常见行为问题(STAR 法则回答)

  • “请分享一个你遇到过的技术难题,以及你是如何解决的?”
    • 情境: 项目背景,时间压力等。
    • 任务: 你需要解决的具体问题。
    • 行动: 你采取的具体步骤,如调研方案、设计、代码实现、测试、复盘。
    • 结果: 最终效果(用数据说话,如性能提升、Bug减少、团队采纳)。
  • “请分享一个你与团队成员意见不合的例子。”
    • 展示你的沟通技巧、同理心、如何达成共识。
  • “你如何看待加班?”
    • 表明你对工作的责任心,同时强调效率。
  • “你为什么想来我们公司?”
    • 表达对公司业务、技术、文化的认同,结合自身发展规划。

12.2 其他软技能

  • 沟通能力: 能够清晰表达技术方案,与产品、后端、UI 有效协作。
  • 学习能力: 展示你如何追踪新技术,如何快速学习新框架/工具。
  • 领导力/影响力: 指导新人、推动技术改进、知识分享。

13. 前沿技术趋势

虽然不必精通,但了解趋势能展现你的技术视野。

  • Serverless: 无需管理服务器,按需执行函数。前端可以结合 Faas 实现后端逻辑。
  • WebAssembly (WASM): 将 C/C++/Rust 等编译到浏览器运行,适合高性能计算(如图像处理、游戏)。
  • PWA (Progressive Web App): 可离线、可安装、可推送,提升 Web 体验。
  • GraphQL: 一种查询语言,由客户端指定所需字段,避免 over-fetching 和 under-fetching。
  • Rust 在前端工具链的应用: SWC(替代 Babel)、Turbopack(Webpack 继任者)等,追求极致的编译速度。

14. 一些经典的手写代码补充

除了之前提到的,面试中还可能遇到:

  • 实现一个简单的 Event Bus (发布-订阅模式)
    class EventBus {
        constructor() {
            this.events = {};
        }
        on(event, listener) {
            (this.events[event] || (this.events[event] = [])).push(listener);
        }
        emit(event, ...args) {
            (this.events[event] || []).forEach(listener => listener(...args));
        }
        off(event, listener) {
            if (!this.events[event]) return;
            this.events[event] = this.events[event].filter(l => l !== listener);
        }
        once(event, listener) {
            const wrapper = (...args) => {
                listener(...args);
                this.off(event, wrapper);
            };
            this.on(event, wrapper);
        }
    }
    
  • 实现一个 compose 函数(函数组合)
    function compose(...funcs) {
        if (funcs.length === 0) return arg => arg;
        if (funcs.length === 1) return funcs[0];
        return funcs.reduce((a, b) => (...args) => a(b(...args)));
    }
    
  • 实现一个简单的 Promise(符合 Promises/A+ 规范的部分核心)(这是一个大挑战,但了解 then 链式调用即可)
  • 实现一个简单的 Redux(createStore)
    function createStore(reducer, preloadedState, enhancer) {
        if (enhancer) return enhancer(createStore)(reducer, preloadedState);
        let state = preloadedState;
        let listeners = [];
        function getState() { return state; }
        function dispatch(action) {
            state = reducer(state, action);
            listeners.forEach(listener => listener());
            return action;
        }
        function subscribe(listener) {
            listeners.push(listener);
            return () => {
                listeners = listeners.filter(l => l !== listener);
            };
        }
        dispatch({ type: '@@redux/INIT' });
        return { getState, dispatch, subscribe };
    }
    

15. 项目经验展示技巧

  • 挑选 1-2 个最能体现你能力的项目,在面试中主动引导话题。
  • 从业务价值出发: 项目解决了什么业务痛点?带来了什么商业价值?
  • 技术难点: 详细描述一个技术难点,展示你的深度思考。
  • 架构设计: 如果你参与了架构设计,画出架构图(口头描述或简单草图),说明选型理由。
  • 数据支撑: 用数据量化成果(性能提升、接入率、错误率下降)。