Skip to content

前端100道问答

第一部分:HTML/CSS 与 JS 基础 (1-15)

1. DOMContentLoaded 与 load 事件的区别

  • 通俗解释
    • DOMContentLoaded 就像是”房子骨架搭好了(HTML 解析完毕,DOM 树构建完成),延迟脚本也执行完了,但大件家具(图片、视频、iframe)还在运输路上”。此时 JS 可以安全操作 DOM 了。
    • load 是”房子盖好,家具搬完,连窗帘都挂好了”。页面上所有外部资源(图片、样式表、视频、iframe)都下载并渲染完成了。
  • 注意细节:CSS 本身不阻塞 DOM 解析,但如果 <link> 后面跟着 <script>,浏览器会等 CSS 加载完再执行脚本,从而间接延迟 DOMContentLoaded
  • 应用场景:大多数 JS 初始化逻辑应该放在 DOMContentLoaded 里(或使用 defer 脚本),因为等所有图片加载完再运行 JS,体验会很卡。

2. HTML5 语义化标签

  • 通俗解释:用对的标签做对的事。不要满屏都是 <div>
  • 常用语义标签
    • 页面结构<header>(页头)、<footer>(页脚)、<nav>(导航)、<main>(主内容,页面唯一)、<aside>(侧边栏)。
    • 内容分区<article>(独立完整的内容,如博客文章)、<section>(主题性分组,如章节)。
    • 文本语义<figure> + <figcaption>(图片/图表及其说明)、<time>(日期时间)、<mark>(高亮文本)、<details> + <summary>(可折叠内容)。
    • 表单增强<input type="email/date/range/color"><datalist>(输入建议列表)、<output>(计算结果)。
  • 为什么重要
    1. 给机器看(SEO):搜索引擎更容易读懂你的网页结构,排名更高。
    2. 给特殊人群看(无障碍):屏幕阅读器能直接跳到 <nav> 导航或 <main> 内容区,体验更好。
    3. 给开发者看(可维护性):看到 <article> 就知道是独立内容,比看到第 N 个 <div class="box"> 清晰得多。

3. BFC (块级格式化上下文)

  • 通俗解释:BFC 就像是一个完全隔离的独立房间。房间里的家具(元素)怎么乱摆,都不会影响到房间外面;外面的洪水也流不进房间里。
  • BFC 内部布局规则
    1. 内部的块元素会在垂直方向上一个接一个放置。
    2. 同一个 BFC 内相邻块元素的垂直 margin 会发生折叠(取较大值)。
    3. BFC 区域不会与浮动元素重叠。
    4. BFC 在计算高度时,会把内部浮动子元素的高度也算进去。
  • 如何触发(建这个房间)
    • display: flow-root最推荐,语义明确,专为创建 BFC 而生,无副作用)。
    • overflow: hidden | auto | scroll(常用,但可能裁剪溢出内容或出现多余滚动条)。
    • float: left | right(元素本身浮动时自动成为 BFC)。
    • display: flex | inline-flex | grid | inline-grid(Flex/Grid 容器)。
    • display: inline-block | table-cell | table-caption
    • position: absolute | fixed(脱离文档流)。
    • 根元素 <html> 本身就是一个 BFC。
  • 解决了什么问题
    1. 清除浮动(高度塌陷):子元素浮动了(float),父元素高度塌陷为 0。把父元素变成 BFC(如 display: flow-root),它就能包裹住浮动的子元素,撑起高度。
    2. 防止外边距折叠:同一个 BFC 内相邻块元素的上下 margin 会合并为一个。要阻止合并,把其中一个元素包裹在新的 BFC 容器里,两个不同 BFC 之间的 margin 不会折叠。
    3. 阻止元素被浮动覆盖:一个浮动元素会覆盖/重叠在相邻的普通块元素上。把那个普通块元素变成 BFC,它就不会与浮动元素重叠,而是紧贴浮动元素排列 — 这是实现两栏自适应布局的经典技巧。

4. 盒模型 (Box Model)

  • 通俗解释:网页里的每个元素都是一个盒子。盒子的大小怎么算?有两种流派:
    1. W3C 标准模型 (content-box):你设置 width: 100px,这仅仅是内容的宽度。如果你再加 10px 的 padding,盒子实际总宽度变成了 120px。坑点:布局容易撑爆。
    2. IE/怪异模型 (border-box):你设置 width: 100px,这 100px 包含了 内容 + padding + border。你加 padding,内容区会自动缩小,总宽度死死卡在 100px。
  • 最佳实践:所有项目都在 CSS 开头写上 * { box-sizing: border-box; },这才是符合人类直觉的。

5. Flexbox 布局

  • 核心概念:一维布局(主要是一行或一列)。想象成一根绳子串珠子。
  • 容器属性(父元素)
    • flex-direction:主轴方向。row(默认,水平)| column(垂直)| row-reverse | column-reverse
    • justify-content:主轴对齐。flex-start | center | flex-end | space-between(两端对齐,中间等分)| space-around(每项两侧等距)| space-evenly(完全等距)。
    • align-items:交叉轴对齐。stretch(默认,拉伸填满)| flex-start | center | flex-end | baseline(按文字基线对齐)。
    • flex-wrap:是否换行。nowrap(默认,挤在一行)| wrap(换行)。
    • align-content:多行时的交叉轴对齐(只在 wrap 时生效)。
    • gap:项目之间的间距,替代以前用 margin 模拟间距的做法。
  • 项目属性(子元素)
    • flex: 1flex-grow: 1; flex-shrink: 1; flex-basis: 0% 的简写。意思是"剩余空间我来分"。
    • flex-grow:有多余空间时,按比例分配。默认 0(不放大)。
    • flex-shrink:空间不够时,按比例缩小。默认 1(等比缩小)。设为 0 表示不缩小(常用于固定宽度侧边栏)。
    • flex-basis:分配多余空间前,项目占据的主轴空间。可以理解为"初始宽度"。
    • align-self:单独覆盖某个子元素的 align-items
    • order:改变排列顺序,默认 0,值越小越靠前。
  • 经典布局
    • 圣杯布局:左右固定宽度 + 中间自适应 → 左右 flex: 0 0 200px,中间 flex: 1
    • 底部固定flex-direction: column; min-height: 100vh,内容区 flex: 1,footer 自然贴底。

6. Grid 布局

  • 核心概念:二维布局(有行也有列),像 Excel 表格或棋盘。
  • 对比 Flex:Flex 适合局部组件(如导航栏、卡片内部),Grid 适合整个页面的宏观排版(如左侧菜单+右侧内容+底部)。两者可以嵌套使用。
  • 容器属性
    • grid-template-columns:定义列。repeat(3, 1fr) 三等分;200px 1fr 200px 左右固定中间自适应。
    • grid-template-rows:定义行。auto 1fr auto 头尾自适应中间撑满。
    • gap(或 row-gap / column-gap):行列间距。
    • grid-template-areas:用字符画出布局结构,非常直观:
      css
      grid-template-areas:
        "header header header"
        "sidebar main aside"
        "footer footer footer";
    • justify-items / align-items:单元格内内容的对齐方式。
    • justify-content / align-content:整个网格在容器中的对齐方式。
  • 项目属性
    • grid-column: 1 / 3:从第 1 条列线跨到第 3 条(占两列)。简写 span 2
    • grid-row: 1 / -1:从第一行跨到最后一行。
    • grid-area: header:配合 grid-template-areas 使用,声明自己属于哪个区域。
  • 响应式利器
    • repeat(auto-fill, minmax(250px, 1fr)):自动填充列,每列最小 250px,多余空间等分。不需要媒体查询就能实现响应式网格。
    • auto-fit vs auto-fillauto-fit 会把空列折叠掉让现有列撑满,auto-fill 保留空列。

7. CSS 选择器优先级 (权重)

  • 通俗解释:当多条规则通过不同方式选中同一个元素时,谁说了算?看谁的权重大。
  • 计算公式 (A, B, C, D)
    • A: style 行内样式 (1,0,0,0)
    • B: ID 选择器 (0,1,0,0)
    • C: 类 (.class)、伪类 (:hover)、属性 ([type="text"]) (0,0,1,0)
    • D: 标签 (div)、伪元素 (::before) (0,0,0,1)
    • !important:核武器,无视规则,直接生效(尽量少用)。
  • 注意事项
    • 权重不进位:11 个 class 的权重 (0,0,11,0) 仍然低于 1 个 ID (0,1,0,0)。
    • 相同权重时,后写的覆盖先写的(层叠规则)。
    • :not() 本身不算权重,但括号里的选择器算。:is():where() 中,:where() 的权重永远为 0(用于写可被轻松覆盖的默认样式)。
    • 通配符 *、组合符 (>, +, ~)、:where() 权重为 0。

8. 水平垂直居中方案

  • 方案一 (现代版):父元素 display: flex; justify-content: center; align-items: center;(最推荐)。
  • 方案二 (Grid版):父元素 display: grid; place-items: center;(代码最少)。
  • 方案三 (绝对定位版):子元素 absolutetop: 50%; left: 50%; transform: translate(-50%, -50%);(不用知道子元素宽高,兼容性好)。

9. 移动端 1px 边框问题

  • 问题来源:现在的手机是高清屏(Retina),你写 1px,在 2倍屏上实际上渲染了 2个物理像素,看起来很粗。
  • 解决方案
    • 不要直接写 border: 1px
    • 用伪元素 ::after 画一个边框。
    • 将这个伪元素放大到 200% 或 300%,然后用 transform: scale(0.5) 把它缩放回来。这样虽然逻辑占了 1px,但视觉上只有 0.5px 的粗细,在高清屏上刚刚好。

10. CSS 预处理器 (Sass/Less)

  • 解决了什么:原生 CSS 不能嵌套写,不能定义变量(虽然现在原生有了),很难复用。
  • 核心功能
    • 嵌套nav { a { color: red } },结构清晰。
    • 变量:Sass 用 $theme-color: blue,Less 用 @theme-color: blue,改一个地方全站换肤。
    • Mixins:像函数一样封装一段 CSS,比如一段清除浮动的代码,哪里需要哪里调。
    • 继承 (Extend)@extend .btn 让一个选择器继承另一个的所有样式。
    • 函数与运算darken($color, 10%)$width / 2 等。
  • Sass vs Less:Sass(SCSS 语法)功能更强大,社区更大,是目前主流。Less 语法更简单,但生态逐渐萎缩。
  • 现状:随着 CSS 原生支持嵌套、变量 (--var)、@layer 等特性,预处理器的必要性在降低,但 Mixins、函数等高级功能仍有价值。

11. PostCSS

  • 通俗解释:CSS 界的 Babel。它不是一种语言,而是一个工具平台,通过插件来转换 CSS。
  • 常用插件
    • Autoprefixer:你只管写 display: flex,它自动帮你加上浏览器前缀,让你不用操心兼容性。
    • postcss-preset-env:让你使用未来的 CSS 语法(如嵌套),自动降级为当前浏览器支持的语法。
  • 生态地位Tailwind CSS 本身就是一个 PostCSS 插件,说明 PostCSS 是现代 CSS 工具链的基础设施。

12. 响应式设计与媒体查询

  • 原理@media screen and (max-width: 768px) { ... }。意思是:如果屏幕宽度小于 768px(手机),就执行这里面的 CSS。
  • rem 与 em
    • em:相对于父元素字体大小。容易套娃,计算混乱。
    • rem:相对于根元素 (html) 字体大小。现在移动端适配的主流方案(配合 JS 动态设置 html 的 font-size)。
    • vw/vh:相对于视口宽高的百分比。100vw = 屏幕宽度。

13. 原型与原型链 (Prototype Chain)

  • 核心概念:JS 里没有传统的”类”(class 只是语法糖),对象通过原型链实现继承。每个对象都有一个内部属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf() 访问)。
  • 链条:当你访问对象 obj.name,JS 引擎先找 obj 自己有没有;没有就去 obj.__proto__(它的原型)找;还没有就找原型的原型...直到找到 Object.prototype,再往上就是 null(尽头)。
  • 关键关系
    • 构造函数.prototype === 实例的 __proto__
    • 构造函数.prototype.constructor === 构造函数本身。
    • 所有函数的 __proto__ === Function.prototype
    • Function.prototype.__proto__ === Object.prototype
    • Object.prototype.__proto__ === null(链的终点)。
  • 面试常考 — 类型判断
    • typeof:适合基础类型(string, number, boolean, undefined, symbol, bigint),但 typeof null === 'object'(历史 bug),typeof [] 也是 'object'。
    • instanceof:顺着原型链找,看能不能找到构造函数的 prototype。[] instanceof Array === true
    • Object.prototype.toString.call()最准确Object.prototype.toString.call([]) 返回 '[object Array]'

14. 作用域与闭包 (Closure)

  • 作用域:变量活着的地盘。
    • 全局作用域:整个脚本都能访问。
    • 函数作用域:函数内部声明的变量,外部访问不到。
    • 块级作用域(ES6):let/const{} 内有效。var 没有块级作用域(这是经典的 for 循环 var 陷阱的根源)。
    • 作用域链:函数嵌套时,内层函数可以访问外层函数的变量,一层层往外找,直到全局。这条链在函数定义时就确定了(词法作用域/静态作用域)。
  • 闭包
    • 定义:函数能够记住并访问它的词法作用域,即使函数在其词法作用域之外执行。
    • 人话:函数执行完了,本该销毁内部变量,但因为这些变量被返回出去的小函数”揪住”了,所以这些变量被迫留在了内存里,形成了一个专属的”小背包”。
    • 用途
      • 私有变量:模拟面向对象的私有属性,外部无法直接访问。
      • 函数工厂makeAdder(5) 返回一个专门加 5 的函数。
      • 防抖节流:timer 变量通过闭包保持在内存中。
      • 模块模式:IIFE + 闭包实现模块化(ES Module 之前的主流方案)。
    • 缺点:滥用会导致内存泄漏(变量一直不回收)。不再需要时应手动解除引用(赋值为 null)。

15. this 指向机制

  • 一句话口诀谁调用它,this 就指向谁(箭头函数除外)。
  • 分情况
    1. obj.fn():this 指向 obj
    2. fn()(普通调用):this 指向全局(window/global),严格模式下是 undefined
    3. new Fn():this 指向新创建出来的实例对象。
    4. 箭头函数:它没有自己的 this,它的 this 是写代码时定义位置的外层上下文(出生时就定死了)。
    5. call/apply/bind:强行改变 this 指向。

第二部分:ES6+ 新特性与异步编程 (16-30)

16. var, let, const 区别

  • var:老古董。
    • 函数作用域:在 iffor 里定义的 var,外面也能访问(坑)。
    • 变量提升 (Hoisting):可以在定义之前使用(值为 undefined),非常反直觉。
    • 可重复声明:写两次 var a 不会报错。
  • let:新时代的变量。
    • 块级作用域{} 也就是花括号里定义的,出了花括号就死。
    • 暂时性死区 (TDZ):在定义之前坚决不能用,用了就报错。
  • const:常量。
    • 规则同 let。
    • 不可重新赋值const a = 1; a = 2; 会报错。
    • 注意:如果是对象 const obj = {a:1},你修改 obj.a = 2 是允许的,因为对象的内存地址没变,变的是房子里的东西。

17. 箭头函数 (Arrow Function)

  • 写法const add = (a, b) => a + b; 简洁优美。
  • 关键区别
    1. 没有自己的 this:它直接继承外层作用域的 this。这解决了以前在回调函数里 this 乱跑的问题。
    2. 不能当构造函数:不能用 new 调用。
    3. 没有 arguments 对象:要拿所有参数,得用剩余参数 ...args

18. 解构赋值 (Destructuring)

  • 通俗解释:从数组或对象里“提取”数据的快捷方式。
  • 数组解构const [a, b] = [1, 2]; -> a=1, b=2。用来交换变量值特别方便 [a, b] = [b, a]
  • 对象解构const { name, age } = user;
  • 重命名const { name: userName } = user; 把提取出来的 name 改名叫 userName。

19. 扩展运算符 (...)

  • 展开 (Spread):把数组或对象“炸开”。
    • const arr2 = [...arr1, 4, 5]:复制数组并添加新元素。
    • const obj2 = { ...obj1, c: 3 }:浅拷贝对象并添加新属性。
  • 剩余 (Rest):把剩下的东西“打包”。
    • function fn(first, ...others) {}:传入 (1, 2, 3),first 是 1,others 是数组 [2, 3]

20. Map 与 Set

  • Set (集合):一堆不重复的值。
    • 最强用途:数组去重。[...new Set([1, 2, 2, 3])] 瞬间变成 [1, 2, 3]
    • 常用方法add()delete()has()size
    • WeakSet:只能存对象引用,弱引用,不可遍历。用于标记对象(如"这个 DOM 节点已处理过")。
  • Map (字典):超级键值对。
    • 对比 Object:Object 的 key 只能是字符串或 Symbol。Map 的 key 可以是任何东西(对象、函数、DOM节点)。
    • 有序:Map 保持插入顺序,Object 的 key 顺序在某些情况下不可靠(数字 key 会排前面)。
    • 性能:频繁增删键值对时,Map 比 Object 性能更好。
    • 常用方法set()get()has()delete()size
    • WeakMap:弱引用的 Map,key 只能是对象。如果 key(对象)在外面被销毁了,WeakMap 里的这条数据会自动消失,不占内存。不可遍历。
    • WeakMap 用途:存储 DOM 节点的私有数据、实现私有属性(Vue3 响应式系统用 WeakMap 存储对象与其响应式代理的映射)。

21. Promise 原理

  • 通俗解释:承诺。就像你去买奶茶,店员给你一张小票(Promise)。这时候奶茶还没好(Pending)。
    • Resolve (兑现):奶茶做好了,叫号,你拿到奶茶(Data)。状态变为 Fulfilled。
    • Reject (拒绝):做奶茶的机器坏了,店员告诉你做不了(Error)。状态变为 Rejected。
    • 不可逆:状态一旦从 Pending 变为 Fulfilled 或 Rejected,就不能再变了。
  • 链式调用.then() 返回一个新的 Promise,所以可以链式调用。这解决了以前”回调地狱”(Callback Hell)一层套一层的问题。
  • 静态方法
    • Promise.all([ ]):所有都成功才成功,一个失败就全失败。适合并发请求且全部需要。
    • Promise.allSettled([ ]):等所有都结束(不管成功失败),返回每个的状态和结果。适合”尽力而为”的场景。
    • Promise.race([ ]):谁先完成(不管成功失败)就返回谁。适合超时控制。
    • Promise.any([ ]):谁先成功就返回谁,全失败才失败。适合多源竞速取最快成功的。

22. Async / Await

  • 本质:Promise 的语法糖。让异步代码看起来像同步代码。底层是 Generator + 自动执行器。
  • 写法
    javascript
    async function getData() {
        try {
            const user = await fetchUser(); // 等待fetchUser完成
            const posts = await fetchPosts(user.id); // 拿到id再去查帖子
        } catch (err) {
            // 错误处理,替代 .catch()
            console.error(err);
        }
    }
  • 并发优化:多个不相互依赖的请求不要串行 await,应该并发执行:
    javascript
    // ❌ 串行:总耗时 = A + B
    const a = await fetchA();
    const b = await fetchB();
    // ✅ 并发:总耗时 = max(A, B)
    const [a, b] = await Promise.all([fetchA(), fetchB()]);
  • 注意async 函数永远返回 Promise。即使你 return 1,外面拿到的也是 Promise<1>
  • 顶层 await:ES2022 起,在 ES Module 的顶层可以直接写 await,不需要包在 async 函数里。

23. Event Loop (事件循环)

  • 核心机制:JS 是单线程的(只有一个厨师)。那怎么处理耗时任务(炖汤)?
    1. 同步代码 (切菜):直接在主线程(执行栈)做。
    2. 异步代码 (炖汤):丢给浏览器其他线程去看着(计时器、网络请求),主线程继续切别的菜。
    3. 回调 (汤好了):炖汤线程把回调函数放到任务队列里排队。
    4. 循环:主线程切完所有菜(执行栈空了),就会去检查任务队列,把排队的任务拿过来执行。
  • 宏任务与微任务
    • 微任务 (Microtask):VIP 通道。Promise 的 .then, process.nextTick
    • 宏任务 (Macrotask):普通通道。setTimeout, setInterval
    • 顺序:主线程做完 -> 清空所有微任务 -> 渲染 UI -> 取出一个宏任务 -> ...

24. 垃圾回收机制 (GC)

  • 标记清除 (Mark and Sweep):目前主流算法。
    • 垃圾回收器定期从”根”(全局对象、当前执行栈中的变量)出发,顺着引用链往下找。
    • 能找到的:打上”活着”的标记。
    • 找不到的(孤立的对象):视为垃圾,回收内存。
  • 引用计数:老算法。
    • 对象每被引用一次 +1,引用断开 -1。变成 0 就回收。
    • 致命缺陷:循环引用。A 引用 B,B 引用 A,虽然没人用它们,但计数器永远是 1,内存泄漏。
  • V8 引擎的优化策略
    • 分代回收:把堆内存分为新生代(存活时间短的对象)和老生代(存活时间长的对象)。
    • 新生代:使用 Scavenge 算法(复制算法)。空间分为 From 和 To 两半,存活对象从 From 复制到 To,然后交换。速度快但空间利用率 50%。
    • 老生代:使用标记清除 + 标记整理。标记整理会把存活对象移到一端,解决内存碎片问题。
    • 增量标记:把标记过程拆成小步,穿插在 JS 执行中,避免长时间暂停(Stop-The-World)。

25. 模块化标准

  • CommonJS (CJS):Node.js 用的。
    • require('fs')module.exports = ...
    • 特点:同步加载(适合服务器,因为文件在硬盘上读得快)。运行时加载(require 可以写在 if 里动态决定加载什么)。
    • 值拷贝:导出的是值的拷贝,模块内部变化不影响外部已导入的值。
  • ES Module (ESM):浏览器和现代前端标准。
    • importexport
    • 特点:静态分析(编译时就能确定依赖关系,这是 Tree Shaking 也就是”摇树优化”去除无用代码的基础)。
    • 值引用(动态绑定):导出的是值的引用,模块内部变化会反映到外部。
    • 异步加载import() 动态导入,返回 Promise,是代码分割(Code Splitting)的基础。
  • 对比总结
    特性CJSESM
    加载时机运行时编译时(静态)
    导出方式值拷贝值引用
    能否 Tree Shaking不能
    浏览器支持不支持原生支持

26. Proxy 与 Reflect

  • Proxy (代理):像一个拦截器或保镖。
    • 你想访问对象 obj.a,代理拦截下来:”等等,我要先记录日志”,或者”如果 a 不存在,我给你返回默认值”。
    • 可拦截的操作(traps)get(读取)、set(赋值)、has(in 操作符)、deleteProperty(delete)、apply(函数调用)、construct(new)等共 13 种。
    • Vue3 的核心:Vue3 用 Proxy 替代了 Vue2 的 Object.defineProperty 来实现响应式,因为 Proxy 可以直接监听数组变化和对象新增/删除属性,且是惰性的(只在访问时才递归代理嵌套对象)。
  • Reflect:与 Proxy 的 traps 一一对应的静态方法集。
    • 为什么需要:在 Proxy 的 handler 里,用 Reflect.get(target, key, receiver) 代替 target[key],能正确处理 this 指向和继承场景。
    • 规范化:把以前散落在 ObjectFunctiondelete 操作符上的反射操作统一到 Reflect 对象上。

27. 迭代器 (Iterator) 与 生成器 (Generator)

  • Iterator:一种协议/接口,只要对象实现了 Symbol.iterator 方法(返回一个含 next() 方法的对象),就能被 for...of 循环遍历。
    • 内置可迭代对象:Array、String、Map、Set、arguments、NodeList。普通对象 {} 默认不可迭代。
    • next() 返回 { value, done }。done 为 true 时迭代结束。
  • Generator:带 * 的函数,是创建迭代器的便捷方式。
    • function* gen() { yield 1; yield 2; yield 3; }
    • 特点:函数可以暂停(yield)和恢复(next)。每次调用 next() 执行到下一个 yield 处暂停。
    • 双向通信next(value) 可以向 Generator 内部传值,作为上一个 yield 表达式的返回值。
    • 与 async/await 的关系async/await 的底层实现原理就是 Generator + 自动执行器(如 co 库)。await 相当于 yield,只是自动帮你调用 next()
  • 实际应用:Redux-Saga 用 Generator 管理副作用;实现惰性求值(按需生成无限序列)。

28. 正则表达式

  • 通俗解释:处理字符串的神器。
  • 常用元字符
    • ^ 开头,$ 结尾。
    • \d 数字,\D 非数字。\w 字母数字下划线,\W 反之。\s 空白字符,\S 非空白。
    • . 匹配除换行符外的任意字符。
    • + 至少一个,? 0个或1个,* 任意个。{n} 恰好 n 个,{n,m} n 到 m 个。
  • 贪婪与非贪婪:默认是贪婪的(尽可能多匹配)。加个 ? 变成非贪婪(尽可能少匹配)。如 .*?
  • 分组与捕获
    • (abc) 捕获组,可以用 $1 反向引用。
    • (?:abc) 非捕获组,只分组不捕获。
    • 命名捕获(?<year>\d{4}),用 match.groups.year 获取。
  • 断言(零宽)
    • (?=a) 正向先行断言:后面跟着 a。
    • (?!a) 负向先行断言:后面不跟 a。
    • (?<=a) 正向后行断言:前面是 a。
    • (?<!a) 负向后行断言:前面不是 a。
  • 常用标志g(全局匹配)、i(忽略大小写)、m(多行模式)、s(dotAll,. 匹配换行符)。
  • 实战:手机号 /^1[3-9]\d{9}$/、邮箱 /^\w+@\w+\.\w+$/(简化版)、千分位 '12345678'.replace(/\B(?=(\d{3})+$)/g, ',')

29. 常用设计模式

  • 单例模式:保证全局只有一个实例(比如 Vuex/Pinia 的 Store,全局唯一的弹窗管理器)。
  • 发布订阅模式on (订阅), emit (发布)。Vue 的事件总线 (EventBus)、Node.js 的 EventEmitter 就是这个。与观察者模式的区别:有一个中间调度中心,发布者和订阅者互不认识。
  • 观察者模式:Vue 的响应式原理(Dep 和 Watcher)。数据变了,直接通知所有盯着它的观察者。与发布订阅的区别:没有中间人,目标和观察者直接耦合。
  • 工厂模式:根据条件创建不同的对象,隐藏创建逻辑。如 React.createElement 根据 type 创建不同元素。
  • 策略模式:定义一系列算法,把它们封装起来,可以互相替换。如表单校验:不同字段用不同的校验策略(邮箱策略、手机号策略),避免大量 if-else。
  • 装饰器模式:不修改原有对象,动态给它添加新功能。如 HOC(高阶组件)、TypeScript/ES 装饰器 @log
  • 代理模式:控制对对象的访问。如图片懒加载(先用占位图代理)、ES6 Proxy、缓存代理。

30. 函数式编程 (FP)

  • 纯函数:输入相同,输出永远相同;没有副作用(不修改外部变量,不发网络请求)。这样的函数非常容易测试和缓存(memoize)。
  • 不可变性 (Immutability):数据一旦创建就不修改,要改就创建新的副本。React 的 state 更新就要求不可变(setState 必须传新对象)。工具库:Immer(用 produce 以可变写法生成不可变数据)。
  • 柯里化 (Currying):把 add(1, 2) 变成 add(1)(2)。用于参数复用和函数组合。
  • 函数组合 (Compose / Pipe):把多个小函数串成一个大函数。compose(f, g, h)(x) = f(g(h(x)))pipe 是从左到右的版本。
  • 高阶函数:参数是函数,或者返回一个函数(如 map, filter, reduce)。
  • 实际应用:React 的函数组件 + Hooks 本质上就是函数式编程思想;Redux 的 Reducer 是纯函数;RxJS 是函数式响应式编程。

第三部分:框架原理与源码分析 (31-45)

31. Virtual DOM (虚拟 DOM)

  • 通俗解释:真实的 DOM 操作(操作网页上的元素)非常慢,像是在搬砖。虚拟 DOM 就是用轻量的 JS 对象(蓝图)来描述 DOM 结构。
  • 工作流程
    1. 状态改变。
    2. 生成新的虚拟 DOM 树(新蓝图)。
    3. Diff:对比新旧两张蓝图,找出不一样的地方(比如只是墙纸颜色变了)。
    4. Patch:只把变化的部分应用到真实 DOM 上(只刷墙,不拆房)。
  • 误区:虚拟 DOM 不一定比直接操作 DOM 快。它的价值在于跨平台(蓝图可以生成网页,也可以生成 App 界面)和保证了下限(不管你怎么写,性能都不会太差)。

32. Vue2 vs Vue3 核心差异

  • 响应式原理
    • Vue2Object.defineProperty。只能劫持对象的属性读取/写入。缺点是监听不到数组下标变化,也监听不到对象新增/删除属性(需要用 $set)。
    • Vue3Proxy。直接代理整个对象。上面提到的缺点全没了,性能也更好(惰性劫持,用到才追踪)。
  • API 风格
    • Vue2:Options API (data, methods, mounted 分开放)。代码多了逻辑会很分散,跳来跳去。
    • Vue3:Composition API (setup 函数 / <script setup> 语法糖)。把同一个功能的逻辑(比如”搜索功能”的状态、方法、生命周期)写在一起,便于抽取为 Composables 复用。
  • 其他重大变化
    • Teleport:把组件渲染到 DOM 的任意位置(弹窗不再被父容器 overflow 裁剪)。
    • Suspense:优雅处理异步组件的加载状态。
    • 多根节点(Fragment):模板不再强制单根元素。
    • 更好的 TypeScript 支持:源码用 TS 重写,类型推断更准确。

33. Vue 双向绑定原理

  • 核心模式数据劫持 + 发布订阅模式
  • 三个角色
    1. Observer (观察者):给数据装上监控摄像头(defineProperty/Proxy)。一变动就通知 Dep。
    2. Dep (依赖收集器):一个管家。谁用了这个数据,就把它记在本子上(收集依赖)。
    3. Watcher (订阅者):页面上的组件。当数据变了,Dep 通知 Watcher,Watcher 去更新视图。
  • v-model
    • Vue2value 属性 + input 事件的语法糖。
    • Vue3modelValue 属性 + update:modelValue 事件的语法糖。且支持多个 v-model 绑定,如 v-model:title="title"

34. Vue computed vs watch vs watchEffect

  • Computed (计算属性)
    • 带缓存:只要依赖的数据没变,多次访问直接返回缓存值,不重新计算。
    • 同步:里面不能做异步操作(如发请求)。
    • 场景:一个值由其他值得出(购物车总价 = 单价 * 数量)。
  • Watch (监听器)
    • 无缓存
    • 支持异步:数据变了,我可以等 1秒后再去搜索。
    • 场景:数据变了要做一些”副作用”(发请求、打印日志、操作 DOM)。
    • 配置项deep: true(深度监听对象内部变化)、immediate: true(创建时立即执行一次)、flush: 'post'(DOM 更新后执行)。
  • watchEffect (Vue3 新增)
    • 自动收集依赖:不需要指定监听谁,回调里用了什么响应式数据就自动监听什么。
    • 立即执行:创建时就执行一次(相当于 watch 的 immediate: true)。
    • 场景:依赖多个数据源的副作用,不想手动列出所有依赖。
    • 对比 watch:watch 更精确(明确知道什么变了),watchEffect 更简洁(自动追踪)。

35. Vue 生命周期的理解

  • Vue2 Options API
    • 创建阶段
      • beforeCreate:刚起步,data 和 methods 还没好。
      • created:数据好了,可以发请求拿初始数据,但 DOM 还没渲染。
    • 挂载阶段
      • beforeMount:模板编译完成,即将挂载到 DOM。
      • mounted:DOM 渲染完了。可以操作 DOM 元素(比如初始化图表)。
    • 更新阶段beforeUpdate -> updated
    • 销毁阶段beforeDestroy -> destroyed。在这里清除定时器、解绑事件,防止内存泄漏。
  • Vue3 Composition API 对应关系
    • beforeCreate / createdsetup() 本身(setup 在这两个钩子之间执行)。
    • beforeMountonBeforeMount
    • mountedonMounted
    • beforeUpdateonBeforeUpdate
    • updatedonUpdated
    • beforeUnmountonBeforeUnmount(替代 beforeDestroy)。
    • unmountedonUnmounted(替代 destroyed)。
  • 父子组件执行顺序
    • 挂载:父 beforeMount → 子 beforeMount → 子 mounted → 父 mounted。
    • 销毁:父 beforeUnmount → 子 beforeUnmount → 子 unmounted → 父 unmounted。
  • 请求数据放哪:一般放 created / setup(不依赖 DOM)或 mounted / onMounted(需要 DOM)。SSR 场景下只能放 created,因为服务端没有 mounted。

36. Vue NextTick 原理

  • 问题:你修改了数据 this.msg = 'Hello',然后立刻去获取 DOM 的内容,发现内容还是旧的。为什么?
  • 原因:Vue 更新 DOM 是异步的。它会把所有数据变更攒起来,放到一个队列里,等当前代码执行完了一次性更新(为了性能)。
  • NextTick:就是把你的回调函数放到这个队列的最后面。等到 DOM 更新完了,自然就轮到你的回调执行了。底层用的是 Promise 或 MutationObserver。

37. Keep-Alive 原理

  • 作用:缓存组件。比如从列表页点进详情页,再退回来,列表页不用重新刷新,滚动条位置也在。
  • 原理:组件切换时,不销毁组件实例,而是把它存到内存的一个对象里(cache)。下次再切回来,直接从内存里拿出来渲染,跳过 createdmounted 钩子,触发 activated 钩子。
  • LRU 算法:内存有限,缓存满了怎么办?最近最少使用算法。把最久没用过的那个组件踢出去。

38. React Fiber 架构

  • 背景:React 15 的更新是同步的,一旦开始 Diff,就必须一口气做完。如果组件树很大,这口气太长,主线程一直被占用,页面就会卡顿(掉帧)。
  • Fiber:把更新任务拆分成一个个小任务单元(每个 Fiber 节点就是一个工作单元)。
  • 时间切片 (Time Slicing):React 像个懂事的打工仔。做一会任务,看看浏览器主线程有没有急事(用户点击、动画)。如果有,就暂停手里的活,让给浏览器先处理;处理完回来继续做。实现了异步可中断渲染
  • React 18 并发特性
    • useTransition:标记某些状态更新为"非紧急",用户输入等紧急更新优先渲染,列表过滤等耗时更新不阻塞 UI。
    • useDeferredValue:让某个值"延迟更新",类似防抖但由 React 调度。
    • Suspense + Streaming SSR:服务端可以流式发送 HTML,边加载边渲染。

39. React Hooks 原理与限制

  • 为什么有 Hooks:类组件太重,逻辑复用难(HOC/Render Props 嵌套地狱)。
  • 核心原理:闭包 + 链表。
    • React 内部维护了一个链表来存每个 Hook 的状态。
  • 常用 Hooks
    • useState / useReducer:状态管理。
    • useEffect / useLayoutEffect:副作用(前者异步,后者同步在 DOM 更新后立即执行)。
    • useMemo / useCallback:性能优化(缓存值 / 缓存函数引用)。
    • useRef:持有可变引用,不触发重渲染。
    • useTransition / useDeferredValue(React 18+):并发特性,标记非紧急更新。
  • 两大铁律
    1. 只能在函数组件顶层调用:不能写在 if, for, function 内部。因为 React 是靠调用顺序来对应状态的。如果你套了个 if,下次渲染少调了一个 Hook,后面的链表全错位了,数据就乱了。
    2. 只能在 React 函数中调用(组件或自定义 Hook)。

40. React 合成事件 (SyntheticEvent)

  • 机制:你在 React 写的 onClick 并没有直接绑定到那个按钮 DOM 上,而是通过事件代理统一管理。
    • React 16 及以前:事件委托到 document 上。
    • React 17+:事件委托到应用根节点 (rootNode) 上。这样多个 React 应用共存时不会互相干扰。
  • 目的
    1. 跨浏览器兼容:React 帮你抹平了不同浏览器事件对象的差异。
    2. 性能:减少内存消耗,不用给成千上万个元素分别绑定事件。
  • 注意:React 合成事件中的 e.stopPropagation() 能阻止 React 事件树中的冒泡,但不会阻止原生事件监听器(通过 addEventListener 直接绑定的)的触发。

41. Diff 算法详解

  • 核心策略 (O(n) 复杂度):理论上对比两棵树的最优算法是 O(n³),但通过三个假设降到 O(n):
    1. 同层比较:只比同一层级,不跨层级比。跨层级移动节点视为删除+重建。
    2. Key 的作用
      • 如果没有 key:框架只能按顺序逐个对比,无法识别节点移动,效率低。
      • 如果有 key:框架发现”key=A 的这个元素只是从第一位跑到了第三位”,那就直接移动它,而不是删除重建。大大提升性能。
      • 不要用 index 做 key:列表有增删时 index 会变,导致错误复用,尤其是包含输入框等有状态的组件时会出 Bug。
    3. 类型不同直接换:如果是 <div> 变成了 <p>,那子节点都不看了,直接整个替换。
  • Vue 和 React 的 Diff 差异
    • Vue2:双端对比(头头、尾尾、头尾、尾头四种比较),效率高于 React 15 的单向遍历。
    • Vue3:在双端对比基础上,对中间乱序部分使用最长递增子序列 (LIS) 算法,最小化 DOM 移动操作。
    • React:单向遍历(从左到右),通过 key 建立映射。React 的 Fiber 架构使 Diff 可中断。

42. 状态管理 (Redux / Vuex / Pinia / Zustand)

  • 核心思想单一数据源。把所有组件共享的状态抽离出来,放在一个全局的大仓库(Store)里。
  • Redux
    • Flux 架构:单向数据流。
    • 流程:View -> Dispatch(Action) -> Reducer(纯函数,计算新状态) -> Store -> View。
    • 特点:模板代码多,但非常规范,易于调试(Time Travel)。现代项目推荐搭配 Redux Toolkit (RTK) 大幅简化写法。
  • Vuex / Pinia
    • Vuex:Mutation (同步改状态), Action (异步)。Vue3 项目已不推荐。
    • Pinia:Vue 官方推荐的状态管理库。去掉了 Mutation,只有 Action,TypeScript 支持好,更轻量,支持 SSR。
  • Zustand(React 生态热门)
    • 极简 API,无 Provider 包裹,无模板代码。
    • const useStore = create((set) => ({ count: 0, inc: () => set(s => ({ count: s.count + 1 })) }))
    • 适合中小型项目,学习成本极低。类似选择还有 Jotai(原子化状态)和 Valtio(代理模式)。

43. React Context API

  • 作用:避免”属性钻取” (Prop Drilling)。爷爷传给爸爸,爸爸传给孙子...太累。Context 像是搭建了一个”传送门”,爷爷可以直接传数据给孙子,中间组件不用经手。
  • 用法
    1. const ThemeContext = createContext(defaultValue) 创建 Context。
    2. <ThemeContext.Provider value={theme}> 在上层提供数据。
    3. const theme = useContext(ThemeContext) 在下层消费数据。
  • 场景:全局主题(黑夜模式)、多语言(i18n)、当前登录用户信息。
  • 缺点与优化
    • Context 里的值一变,所有消费它的组件都会强制重新渲染。如果用来做高频更新的状态管理,性能会炸。
    • 优化手段:拆分 Context(把频繁变化的和不变的分开)、用 useMemo 包裹 value、配合 React.memo 减少不必要渲染。
    • 不适合:复杂的全局状态管理(这时候该用 Redux/Zustand)。适合低频变化的全局配置。

44. SSR (服务端渲染) 原理

  • SPA (单页应用) 痛点:首屏慢(要等 JS 下载执行完才显示内容)、SEO 差(爬虫只看到一个空 div)。
  • SSR (Server-Side Rendering)
    1. 用户请求页面。
    2. Node.js 服务器运行 React/Vue 代码,生成完整的 HTML 字符串。
    3. 服务器把 HTML 发给浏览器。用户立刻看到内容(首屏快)。
    4. 注水 (Hydration):浏览器下载 JS,接管页面,把静态的 HTML 变成可交互的应用。
  • SSG (Static Site Generation):构建时就生成好 HTML,部署为纯静态文件。适合内容不常变的页面(博客、文档站)。速度最快,但不适合动态内容。
  • ISR (Incremental Static Regeneration):Next.js 的方案。静态页面 + 按需重新生成。兼顾 SSG 的速度和 SSR 的动态性。
  • Streaming SSR:React 18 支持流式渲染,服务器边生成 HTML 边发送,配合 Suspense 实现渐进式加载,用户更早看到内容。
  • 框架:Next.js (React), Nuxt.js (Vue), Astro (多框架)。
  • 权衡:SSR 增加了服务器成本和部署复杂度。不是所有项目都需要 SSR,纯后台管理系统用 SPA 就够了。

45. 微前端 (Micro-Frontends)

  • 解决了什么:巨石应用(Monolith)。几百个人维护一个项目,编译一次半小时,上线风险巨大。
  • 核心思想:把大应用拆成一个个独立的小应用(子应用)。
    • 技术栈无关:子应用可以用 Vue,可以用 React,甚至 jQuery。
    • 独立部署:A 团队发版不影响 B 团队。
  • 实现方案
    • iframe:最简单,隔离最完美,但体验差(弹窗遮罩问题、通信难、路由状态丢失)。
    • qiankun (基于 single-spa):社区广泛使用。通过 JS 沙箱隔离全局变量,通过样式隔离解决 CSS 冲突。加载 HTML Entry。
    • Module Federation (Webpack 5 / Rspack):模块级共享方案。不同应用可以在运行时共享模块,无需打包到一起,适合渐进式拆分。
    • Wujie / micro-app:新一代方案。Wujie 基于 Web Components + iframe 沙箱,兼顾隔离性和体验;micro-app 基于 Web Components,接入成本低。

第四部分:工程化与构建工具 (46-60)

46. Webpack 核心流程

  • 通俗解释:Webpack 就像一个打包工厂。
  • 流程
    1. 初始化:读取配置文件 (webpack.config.js)。
    2. 编译 (Compile):从入口文件 (entry) 开始,递归寻找所有依赖的模块。
    3. 转换:遇到不认识的文件(scss, vue, ts),调用对应的 Loader 翻译成 JS。
    4. 构建依赖图:搞清楚谁依赖谁。
    5. 输出 (Emit):根据依赖图,把模块组装成一个或多个 Chunk(代码块),写入文件系统 (dist 目录)。
  • 关键概念
    • Loader:翻译官。css-loader, babel-loader
    • Plugin:插件。在打包的特定时机干坏事(压缩代码、拷贝文件、生成 HTML)。

47. Loader 与 Plugin 的区别

  • Loader (加载器)
    • 作用:文件转换。Webpack 原生只懂 JS 和 JSON,Loader 让它能看懂 CSS、图片、TS。
    • 运行时机:在读取文件内容时。
    • 例子sass-loader 把 Sass 变成 CSS。
  • Plugin (插件)
    • 作用:功能扩展。介入打包的整个生命周期,做更复杂的事。
    • 运行时机:整个构建过程的任何钩子 (Hooks) 处。
    • 例子HtmlWebpackPlugin 生成 index.html;CleanWebpackPlugin 打包前清空 dist 目录。

48. Vite 原理

  • 核心优势。为什么快?
  • 开发环境 (Dev)
    • 不打包 (No-Bundle)。利用现代浏览器原生支持 ES Modules (<script type="module">) 的特性。
    • 当你请求一个页面,浏览器遇到 import,再向服务器去请求对应的文件。Vite 只需要按需编译这个文件返回给你。相比 Webpack 把整个项目打包好才能启动,Vite 是秒开
    • 依赖预构建:首次启动时用 esbuild(Go 编写,极快)将第三方依赖(如 lodash)预编译为 ESM 格式并缓存,后续不再重复处理。
  • 生产环境 (Build)
    • 使用 Rollup 进行打包(Vite 6+ 正在引入基于 Rust 的 Rolldown 替代)。因为原生 ESM 在生产环境(尤其是有几千个模块时)网络请求太多,性能不行,所以还是得打包。
  • 生态:已成为 Vue、React、Svelte 等主流框架的默认脚手架工具。

49. Babel 原理

  • 作用:JS 编译器。把 ES6+ 的新语法(const, arrow function, optional chaining)转译成目标环境能运行的语法,也处理 JSX、TypeScript 等非标准语法。
  • 三步走
    1. 解析 (Parse):把代码变成 AST (抽象语法树)。就像把句子拆解成”主谓宾”结构。
    2. 转换 (Transform):遍历 AST,增删改查。比如看到”箭头函数”节点,把它改成”普通函数”节点。这是 Babel 插件工作的地方。
    3. 生成 (Generate):把改好的 AST 重新变回代码字符串。
  • 现代替代:esbuild(Go 编写)和 SWC(Rust 编写)在编译速度上比 Babel 快 10-100 倍,越来越多项目(如 Next.js、Vite)已切换到 SWC。

50. Tree Shaking (摇树优化)

  • 通俗解释:拿起这棵树摇一摇,把枯萎的叶子(没用到的代码)摇下来,不打包进最终文件。
  • 前提:必须使用 ES Module (import/export)。因为 ESM 是静态分析的,打包工具在编译时就能确切知道哪个函数没被引用。CommonJS (require) 是动态的,摇不动。
  • Side Effects (副作用):如果一个文件里只有 console.log('hello'),虽然没被 import,但它有副作用,不能摇掉。可以在 package.json 里配置 "sideEffects": false 告诉工具放心摇。

51. HMR (热更新) 原理

  • 体验:你改了 CSS 颜色,浏览器不用刷新页面,按钮颜色直接变了,而且输入框里填的内容还在。
  • 流程
    1. Webpack 监听到文件变化,重新编译该模块。
    2. 服务端(WDS)通过 WebSocket 推送更新消息(hash值)给浏览器。
    3. 浏览器运行时代码收到消息,请求新的模块代码。
    4. 替换:尝试用新模块替换旧模块。如果失败(比如没写 accept 逻辑),则回退到整页刷新。

52. 浏览器缓存策略

  • 强缓存(浏览器自作主张):
    • Expires:绝对时间(如 2030 年到期)。受本地时间影响,已淘汰。
    • Cache-Controlmax-age=3600(一小时内别烦我,直接读内存/硬盘缓存)。优先级最高
  • 协商缓存(浏览器问服务器):
    • 强缓存失效了,浏览器带上标识问服务器:“这图过期没?”
    • Last-Modified / If-Modified-Since:按修改时间。精度只到秒。
    • Etag / If-None-Match:按文件指纹(Hash)。只要内容没变,Etag 就不变。最准确
    • 结果:如果没变,服务器回 304 Not Modified(没变,继续用旧的吧),省带宽。

53. DNS 解析过程

  • 通俗解释:把域名 www.google.com 翻译成 IP 地址 142.250.1.1
  • 查找顺序
    1. 浏览器缓存(之前访问过吗?)。
    2. 系统 hosts 文件
    3. 路由器缓存
    4. ISP DNS 服务器(电信/移动的服务器)。
    5. 根域名服务器 -> 顶级域名服务器 (.com) -> 权威域名服务器 (google.com)(递归/迭代查询)。

54. CDN (内容分发网络)

  • 通俗解释:京东的快递仓库。如果仓库只在北京,海南用户买东西要等很久。CDN 就是在全国各地建仓库(节点)。
  • 原理:用户访问图片时,DNS 会根据用户的 IP,把请求解析到离用户最近的那个 CDN 节点 IP。
  • 好处:速度快、减轻源站压力。

55. TCP 三次握手与四次挥手

  • 三次握手 (建立连接)
    1. Client: “我想连你,听到吗?” (SYN)
    2. Server: “听到了,我也想连你,你听到吗?” (SYN + ACK)
    3. Client: “听到了,连接建立。” (ACK)
    • 为什么三次? 为了防止已失效的连接请求突然又传到了服务端,造成资源浪费。
  • 四次挥手 (断开连接)
    1. Client: “我说完了,要挂了。” (FIN)
    2. Server: “知道了,等我把剩下的话说完。” (ACK)
    3. Server: “我说完了,挂吧。” (FIN)
    4. Client: “好,挂了。” (ACK) -> 等待 2MSL 时间后真正关闭。

56. HTTP/1.1 vs HTTP/2 vs HTTP/3

  • HTTP/1.1
    • 队头阻塞:一个连接一次只能处理一个请求。前面的图没传完,后面的 CSS 就得等。
    • 文本传输
  • HTTP/2
    • 多路复用:一个 TCP 连接可以并发处理无数个请求。解决了队头阻塞(应用层)。
    • 二进制分帧:传输更高效。
    • 头部压缩 (HPACK):减少重复 Header 传输。
    • 服务端推送:允许服务器主动推送资源。但实践中收效有限,Chrome 已于 2022 年移除对此特性的支持,103 Early Hints 成为替代方案。
  • HTTP/3
    • 基于 UDP (QUIC) 协议。
    • 解决了 TCP 丢包导致的队头阻塞问题。连接建立更快(0-RTT)。

57. HTTPS 握手流程

  • HTTPS = HTTP + SSL/TLS (安全层)。
  • 流程
    1. Client Hello:支持的加密算法。
    2. Server Hello:选定算法,发来数字证书(含公钥)。
    3. 验证证书:Client 找操作系统确认证书是不是真的(防中间人攻击)。
    4. 生成密钥:Client 生成一个随机数(对称密钥),用公钥加密发给 Server。
    5. 握手完成:Server 用私钥解密拿到随机数。之后双方就用这个随机数进行对称加密通信(速度快)。

58. CSRF 攻击与防御

  • CSRF (跨站请求伪造):借刀杀人。
    • 场景:你登录了银行网站 A(Cookie 存着)。然后你手贱点开了钓鱼网站 B。B 网站里有一个隐藏的图片 <img src="http://bank.com/transfer?to=hacker&money=1000">
    • 后果:浏览器会自动带上银行 A 的 Cookie 发送请求,银行以为是你本人操作的,钱没了。
  • 防御
    • SameSite Cookie:设置 Cookie 只能在同站发送。
    • CSRF Token:提交表单时必须带一个随机 Token,钓鱼网站拿不到这个 Token。
    • 验证 Referer:检查请求是从哪来的。

59. XSS 攻击与防御

  • XSS (跨站脚本攻击):代码注入。
  • 三种类型
    • 存储型 (Stored):恶意脚本存入数据库(如评论区),所有访问该页面的用户都会执行。危害最大
    • 反射型 (Reflected):恶意脚本在 URL 参数中,服务器将其"反射"回页面。需要诱导用户点击特定链接。
    • DOM 型:纯前端漏洞。JS 直接把不可信数据插入 DOM(如 innerHTML = userInput),不经过服务器。
  • 防御
    • 转义输出:把 < 变成 &lt;> 变成 &gt;。永远不要相信用户的输入。对不同上下文(HTML、JS、URL、CSS)使用对应的转义规则。
    • CSP (内容安全策略):告诉浏览器只准加载哪里的脚本,禁止内联脚本。Content-Security-Policy: script-src 'self'
    • HttpOnly Cookie:禁止 JS 读取 Cookie,即使 XSS 成功也偷不走 Cookie。
    • 避免 innerHTML:用 textContent 代替,或使用框架的自动转义(React 的 JSX、Vue 的模板默认转义)。
    • 输入校验:白名单校验,只允许预期格式的输入。

60. 前端性能优化指标 (Core Web Vitals)

  • Google 定义的网页体检报告(2024 年 3 月起更新):
    • LCP (最大内容渲染时间):网页里最大的那张图或那段字多久显示出来?(衡量加载速度,越快越好,<2.5s)。
    • INP (Interaction to Next Paint):用户的所有交互(点击、键入、触控)中,从操作到页面视觉响应的最慢延迟。(衡量整体交互响应性,越短越好,<200ms)。已于 2024 年 3 月正式替代 FID。
    • CLS (累积布局偏移):看网页的时候,图片有没有突然加载出来把文字顶下去了?(衡量视觉稳定性,越稳越好,<0.1)。

第五部分:浏览器原理、性能优化与 TypeScript (61-75)

61. 浏览器渲染原理 (关键路径)

  • 通俗解释:浏览器拿到 HTML 代码后,怎么把它画到屏幕上?
  • 五步走
    1. 解析 HTML:生成 DOM 树(骨架)。
    2. 解析 CSS:生成 CSSOM 树(皮肤样式)。
    3. 合成 (Render Tree):把 DOM 和 CSSOM 结合。注意:display: none 的节点不会出现在渲染树中,但 visibility: hidden 会。
    4. 回流/重排 (Layout/Reflow):计算每个节点在屏幕上的确切位置和大小(算坐标)。
    5. 重绘 (Repaint):填充像素(画颜色、阴影)。
    6. 合成 (Composite):如果有多个图层(比如用了 transformz-index),GPU 会把它们像 PS 图层一样合并成一张图。

62. 重排 (Reflow) 与 重绘 (Repaint)

  • 区别
    • 重排:布局变了(改宽、高、位置、字体大小、删元素)。非常消耗性能,因为牵一发而动全身,周围的元素都要重新算位置。
    • 重绘:样子变了但位置没变(改颜色、背景)。性能消耗较小。
  • 结论重排一定导致重绘,重绘不一定导致重排
  • 优化
    • 不要一条条改样式,用 class 一次性改。
    • 操作 DOM 时,先把元素 display: none(这时操作不触发重排),改完再放回去,只触发两次重排。
    • 使用 transform 代替 top/left 做动画(transform 会开启 GPU 加速,跳过重排和重绘,直接合成)。

63. 本地存储方案对比

  • Cookie
    • 老旧。只有 4KB。每次请求都会自动带给服务器(浪费带宽)。主要用于存 Session ID。
  • LocalStorage
    • 持久存储。5MB 左右。除非你手动删,否则一直都在。不参与服务器通信。
  • SessionStorage
    • 会话存储。5MB 左右。页面关了就没了。适合存表单临时数据。
  • IndexedDB
    • 数据库。容量大(几百MB)。异步操作。适合存大量结构化数据。

64. 跨域 (CORS) 解决方案

  • 同源策略:浏览器为了安全,禁止 协议、域名、端口 任一不同的网页互相访问数据。
  • CORS (跨域资源共享)最正统的方案。
    • 后端设置:后端在响应头加 Access-Control-Allow-Origin: https://example.com(指定域名比 * 更安全)。
    • 简单请求:GET/POST/HEAD + 常见 Content-Type,浏览器直接发送,带上 Origin 头。
    • 预检请求 (Preflight):如果是复杂请求(如 PUT/DELETE、自定义 Header、Content-Type: application/json),浏览器会先发一个 OPTIONS 请求问服务器:”我能发这个吗?”服务器通过 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 等头部回应。预检结果可通过 Access-Control-Max-Age 缓存。
    • 携带 Cookie:需要前端设置 credentials: 'include',后端设置 Access-Control-Allow-Credentials: true,且 Allow-Origin 不能为 *
  • Proxy (代理)开发环境常用。
    • 前端 localhost:5173 -> Vite/Webpack 代理服务器 (同源) -> 目标服务器。服务器之间没有同源策略。
  • JSONP:古老方案。利用 <script> 标签不受同源策略限制。只能发 GET 请求,有安全风险,已基本淘汰。
  • 其他方案window.postMessage(跨窗口通信)、Nginx 反向代理(生产环境)。

65. 防抖 (Debounce) 与 节流 (Throttle)

  • 防抖 (Debounce)“最后一个人说了算”
    • 场景:搜索框输入。你一直打字我就不搜,等你停下来 500ms 不动了,我再去搜。
    • 代码思路:清除旧定时器,设置新定时器。
  • 节流 (Throttle)“按规定频率执行”
    • 场景:滚动条监听、按钮防狂按。不管你滑得多快,我每隔 100ms 只执行一次。
    • 代码思路:如果有定时器在跑,就直接 return,直到定时器结束。

66. 事件委托 (Event Delegation)

  • 原理:利用 事件冒泡。不给每个子元素(li)绑事件,而是绑在父元素(ul)上。
  • 好处
    1. 省内存:绑 1 个事件 vs 绑 1000 个事件。
    2. 动态支持:后来通过 JS 新增的 li 元素,不用重新绑定,天然就有事件。
  • 实现ul.addEventListener('click', (e) => { if (e.target.tagName === 'LI') { ... } })

67. TypeScript 核心优势

  • 静态类型:代码还没跑,写的时候就能发现错误(比如拼写错误、参数传错了)。
  • 智能提示:IDE 知道在这个对象里有什么属性,点一下全出来,开发效率极高。
  • 可维护性:对于大型项目,TS 就是文档。看一眼接口定义 (interface) 就知道数据长啥样。
  • 重构安全:改了一个类型定义,所有用到它的地方立刻报错,不会遗漏。
  • 常用高级类型
    • 泛型 (Generics)function identity<T>(arg: T): T。让函数/类型可以适配多种类型,保持类型安全。
    • 联合类型string | number,值可以是其中之一。
    • 交叉类型A & B,同时满足 A 和 B。
    • 字面量类型type Direction = 'up' | 'down' | 'left' | 'right'
    • 内置工具类型Partial<T>(所有属性可选)、Required<T>(所有属性必填)、Pick<T, K>(选取部分属性)、Omit<T, K>(排除部分属性)、Record<K, V>(键值对映射)、ReturnType<T>(获取函数返回类型)。

68. TypeScript: Interface vs Type

  • Interface (接口):主要用来定义对象的形状。
    • 特性:可以合并(同名 interface 会自动合并属性,称为"声明合并")。支持 extends 继承。
    • 适合:定义公共 API 契约、第三方库的类型扩展(利用声明合并)。
  • Type (类型别名):更强大,可以定义基本类型、联合类型、交叉类型、元组等。
    • type Status = 'success' | 'fail';(联合类型,Interface 做不到)
    • type Point = { x: number } & { y: number };(交叉类型)
    • type Pair = [string, number];(元组)
    • type Callback = (data: string) => void;(函数类型)
    • 不能声明合并(同名 type 会报错)。
  • 选谁
    • 写库或插件用 Interface(为了让别人能通过声明合并扩展)。
    • 写业务逻辑一般用 Type 更灵活(联合类型、映射类型等 Interface 做不了)。
    • 两者在定义对象形状时几乎等价,团队统一即可。

69. TypeScript: Any vs Unknown vs Never

  • any放弃治疗。告诉编译器:”别管我,我爱咋用咋用”。失去了 TS 的意义。尽量用 unknown 替代。
  • unknown安全的 any。表示”我不知道这是啥”。但是!在你使用它之前(比如调用它的方法),必须先进行类型判断(类型收窄),否则报错。
    • 收窄方式:typeofinstanceof、自定义类型守卫 (is)、断言 (as)。
  • never不可能存在的类型
    • 函数永远不会正常返回(抛异常或死循环):function fail(): never { throw new Error() }
    • 穷尽检查:在 switch 的 default 分支赋值给 never 类型变量,如果漏了某个 case,TS 会报错。这是保证联合类型被完全处理的技巧。

70. 移动端适配方案

  • Viewport<meta name="viewport" content="width=device-width, initial-scale=1.0">。必须加,否则手机会把网页当成电脑版缩放。
  • rem:相对于根元素 (html) 的 font-size
    • 通常配合 JS 或 CSS 媒体查询,根据屏幕宽度动态修改 htmlfont-size
  • vw/vh:1vw = 屏幕宽度的 1%。目前最主流的方案。
  • 1px 边框问题:高清屏(Retina)下,CSS 的 1px 看起来很粗(实际占了 2 或 3 个物理像素)。
    • 解决:用伪元素 ::after 设置 1px 边框,然后 transform: scale(0.5)

71. 图片懒加载 (Lazy Load)

  • 原理:图片的 src 先不填真地址,填一个占位图。把真地址存在 data-src 里。
  • 检测:监听滚动,当图片即将出现在视口(Viewport)时,把 data-src 赋值给 src
  • APIIntersectionObserver。比传统的监听 scroll 事件性能好得多,浏览器原生帮你检测元素是否可见。

72. Web Worker

  • 痛点:JS 是单线程的,计算量太大(比如图像处理、加密解密、大数据排序)会卡死主线程,页面就动不了了。
  • Web Worker:允许你在后台开启一个新的线程运行 JS。
  • 限制:Worker 线程不能操作 DOM,不能访问 windowdocument。它只能做纯计算,通过 postMessage 和主线程通信(结构化克隆算法传递数据)。
  • 类型
    • Dedicated Worker:专属于创建它的页面,最常用。
    • Shared Worker:可以被多个页面/标签页共享。
    • Service Worker:独立于页面,用于离线缓存和推送通知(见第 91 题)。
  • Transferable Objects:对于大型 ArrayBuffer,可以用 postMessage(data, [data]) 转移所有权而非拷贝,零拷贝传输,性能极高。
  • 实际场景:大文件 hash 计算(分片上传)、Canvas 离屏渲染(OffscreenCanvas)、复杂数据处理。

73. 显式类型转换 vs 隐式类型转换

  • 显式转换:开发者主动调用转换方法。
    • Number('123') → 123,Number('abc') → NaN。
    • String(123) → '123'。
    • Boolean(0) → false。假值只有 6 个:false0''nullundefinedNaN。其他全是真值(包括 []{}'0')。
    • parseInt('12px') → 12(从左往右解析,遇到非数字停止)。
  • 隐式转换 (坑)
    • + 运算符:有字符串就变拼接。1 + '2' = '12'
    • -*/ 运算符:全转数字。'6' - 1 = 5
    • [] + [] = ""(两个数组都转为空字符串再拼接)。
    • [] + {} = "[object Object]"
    • {} + [] = 0{} 被解析为空代码块,实际执行的是 +[])。
  • == vs ===
    • == 会尝试转换类型再比较(1 == '1' 为 true,null == undefined 为 true)。
    • === 严格比较,类型不同直接 false。永远推荐用 ===
    • 特殊NaN !== NaN(NaN 不等于任何值包括自身),用 Number.isNaN() 判断。

74. 常见的内存泄漏场景

  • 未清理的定时器:组件销毁了,setInterval 还在跑。解决:在 beforeUnmount / useEffect 的 cleanup 中 clearInterval
  • 未移除的事件监听addEventListener 了但没有 removeEventListener。解决:组件卸载时移除,或使用 AbortController 统一管理。
  • 全局变量:不小心把变量挂到了 window 上(如函数内未用 let/const 声明的变量)。
  • 闭包:虽然好用,但如果持有了不再需要的大对象引用,GC 无法回收。
  • DOM 引用:JS 里存了一个 DOM 节点的引用(如存在变量或 Map 中),后来这个节点从页面删了,但 JS 里还指着它,内存就释放不掉。
  • 未取消的网络请求:组件销毁后请求才返回,回调中操作已卸载的组件。解决:使用 AbortController 取消 fetch。
  • console.log:开发时打印的大对象会被 DevTools 持有引用,生产环境应移除。
  • 排查工具:Chrome DevTools → Memory 面板 → Heap Snapshot(堆快照对比)、Allocation Timeline(分配时间线)。

75. 进程 (Process) 与 线程 (Thread)

  • 比喻
    • 进程:一个工厂(拥有独立的资源、内存空间)。比如 Chrome 的每一个 Tab 页通常是一个独立的进程。
    • 线程:工厂里的工人(共享工厂的资源)。一个进程可以有多个线程。
  • 浏览器是多进程的:主进程、渲染进程、GPU 进程、插件进程。
  • JS 是单线程的:渲染进程里,JS 引擎线程GUI 渲染线程是互斥的。JS 执行时,页面渲染就会停(所以 JS 写了死循环页面就卡死)。

在面试中,能说出原理是“银”,能手写出代码才是“金”。请务必在一个编辑器里亲自敲一遍这些逻辑。

第六部分:手写代码与算法基础 (76-90)

76. 手写防抖 (Debounce)

  • 核心逻辑
    1. 定义一个变量 timer 保存定时器 ID。
    2. 返回一个函数。
    3. 每次调用时,先 clearTimeout(timer) 清除之前的定时器。
    4. 重新 setTimeout,在延迟时间后执行真正的函数。
  • 用途:搜索框输入、窗口调整大小。

77. 手写深拷贝 (Deep Clone)

  • 乞丐版JSON.parse(JSON.stringify(obj))。缺点:会丢失函数、undefinedSymbol,无法处理循环引用、Date 变字符串、RegExp 丢失。
  • 现代版structuredClone(obj)(ES2022 原生 API)。支持循环引用、Date、RegExp、Map、Set、ArrayBuffer 等。但不支持函数、DOM 节点、Symbol。日常开发首选
  • 面试手写版逻辑
    1. 判断类型,如果是基本类型(null、非对象)直接返回。
    2. 处理特殊对象:Date → new Date(obj),RegExp → new RegExp(obj)
    3. 如果是对象/数组,创建一个新的 {}[]
    4. 循环引用:使用 WeakMap 做一个缓存字典。每次拷贝前先看字典里有没有这个对象,有就直接返回,没有就存进去再递归。
    5. 递归遍历属性(用 Reflect.ownKeys() 可以拿到 Symbol key),赋值给新对象。
  • 第三方库:lodash 的 _.cloneDeep() 是最完善的实现,处理了几乎所有边界情况。

78. 手写 Promise.all

  • 核心逻辑
    1. 接收一个 Promise 数组。
    2. 返回一个新的 Promise。
    3. 内部维护一个计数器 count 和结果数组 results
    4. 遍历数组,执行每个 Promise。
    5. 每成功一个,把结果存入 results (注意索引要对应),count++
    6. count === 数组长度 时,resolve(results)
    7. 只要有一个失败,立刻 reject(error)

79. 手写发布订阅模式 (Event Emitter)

  • 结构:一个类,里面有一个对象 events = {} 存事件。
  • 方法
    1. on(name, fn): 订阅。this.events[name].push(fn)
    2. emit(name, ...args): 发布。找到 this.events[name] 数组,遍历执行里面的函数。
    3. off(name, fn): 取消。filter 掉那个函数。
    4. once(name, fn): 也就是包装一层函数,执行完立刻调用 off

80. 数组扁平化 (Flatten)

  • 需求[1, [2, [3, 4]]] -> [1, 2, 3, 4]
  • 方案一arr.flat(Infinity) (ES6)。
  • 方案二 (递归):遍历数组,如果是数组就递归调用,不是就 push。
  • 方案三 (Reduce)arr.reduce((prev, cur) => prev.concat(Array.isArray(cur) ? flatten(cur) : cur), [])

81. 数组去重

  • 方案一 (最快)[...new Set(arr)]
  • 方案二 (indexOf):遍历,如果你在结果数组里找不到 (indexOf === -1),就 push 进去。
  • 方案三 (filter)arr.filter((item, index) => arr.indexOf(item) === index)。保留第一次出现的,过滤掉后面重复的。

82. 手写 call / apply / bind

  • 核心原理:如何改变 this 指向?
  • Call 实现
    1. 把函数挂到目标对象上:context.fn = this
    2. 执行这个函数:context.fn(...args)
    3. 删除这个属性:delete context.fn
  • Bind 实现:返回一个新的函数,这个新函数内部去调用 apply。注意处理 new 调用的情况。

83. 手写 instanceof 原理

  • 作用:判断 A instanceof B,即 A 是否是 B 的实例。
  • 逻辑:沿着 A 的原型链 (__proto__) 往上找。
  • 循环:如果 A.__proto__ === B.prototype,返回 true。如果找到了顶端 null 还没找到,返回 false。

84. 手写 new 操作符

  • new 干了四件事
    1. 创建一个新对象 obj
    2. obj 的原型指向构造函数的原型 (obj.__proto__ = Constructor.prototype)。
    3. 执行构造函数,并把 this 绑定到 obj 上。
    4. 关键:如果构造函数显式返回了一个对象,就返回那个对象;否则返回 obj

85. 函数柯里化 (Currying)

  • 概念:把 add(1, 2, 3) 变成 add(1)(2)(3)
  • 逻辑
    1. 判断当前传入参数的个数 (args.length)。
    2. 如果参数够了 (>= 原函数需要的参数),直接执行原函数。
    3. 如果不够,返回一个新的函数,继续收集参数,直到参数凑齐。

86. 解析 URL 参数

  • 需求?name=jack&age=18 -> {name: 'jack', age: '18'}
  • 方案
    1. window.location.search 拿到字符串。
    2. 去掉 ?,用 & 分割成数组。
    3. 遍历数组,用 = 分割 key 和 value。
    4. 神器Object.fromEntries(new URLSearchParams('?name=jack&age=18')) 一行代码搞定。

87. 快速排序 (Quick Sort)

  • 算法思路 (分治法)
    1. 基准:找一个中间数(Pivot)。
    2. 分区:比它小的放左边数组,比它大的放右边数组。
    3. 递归:对左边和右边重复这个过程。
    4. 合并[...quickSort(left), pivot, ...quickSort(right)]
  • 复杂度:平均 O(n log n)。

88. 列表转树形结构 (List to Tree)

  • 场景:后端给你一个扁平数组(包含 id, parentId),你要转成嵌套的树给菜单组件用。
  • 逻辑
    1. 用一个 Map (或对象) 存储所有节点,id 为 key。
    2. 遍历数组,找到每个节点的 parent。
    3. 如果 parent 存在,就把当前节点 push 到 parent 的 children 数组里。
    4. 如果 parent 不存在(是根节点),放入结果数组。
  • 优势:利用对象引用,时间复杂度 O(n),比双重循环 O(n^2) 快得多。

89. LRU 缓存算法 (最近最少使用)

  • 需求:设计一个缓存,容量有限。满了之后,删除最久没用过的那个。
  • 数据结构:JS 的 Map。因为 Map 的 Key 是有序的(按插入顺序)。
  • Get 逻辑:如果有,先删除,再重新 set 进去(这样它就跑到了链表末尾,变成“最近使用”)。
  • Put 逻辑:如果满了,删除 map.keys().next().value (头部的就是最久没用的),然后 set 新的。

90. 图片懒加载 (IntersectionObserver 代码)

  • 传统做法:监听 scroll,计算 scrollTop + windowHeight > imgOffsetTop。太麻烦且卡顿。
  • 现代做法
    javascript
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) { // 进入视口
          const img = entry.target;
          img.src = img.dataset.src; // 替换真实地址
          observer.unobserve(img);   // 停止观察
        }
      });
    });
    document.querySelectorAll('img').forEach(img => observer.observe(img));

🎉 第七部分:现代前端进阶 (91-100)

91. Service Worker 与 PWA

  • Service Worker:运行在浏览器后台的独立线程,充当网页和服务器之间的代理。
    • 生命周期:安装 (install) -> 激活 (activate) -> 监听 (fetch/push/sync)。
    • 核心能力:拦截网络请求、管理缓存、实现离线访问。
  • PWA (Progressive Web App):利用 Service Worker + Web Manifest,让网页拥有类似原生 App 的体验。
    • 可安装:添加到主屏幕,有自己的图标和启动画面。
    • 离线可用:Service Worker 缓存关键资源,没网也能用。
    • 消息推送:通过 Push API 实现推送通知。
  • 缓存策略
    • Cache First:先找缓存,没有再请求网络(适合静态资源)。
    • Network First:先请求网络,失败再用缓存(适合 API 数据)。
    • Stale While Revalidate:先返回缓存,同时后台更新缓存(兼顾速度和新鲜度)。

92. WebSocket 与 Server-Sent Events (SSE)

  • WebSocket
    • 全双工通信:客户端和服务器可以随时互发消息(聊天室、实时协作)。
    • 握手:先走 HTTP 升级协议 (Upgrade: websocket),之后切换为 WebSocket 协议。
    • 场景:即时通讯、多人协作编辑、实时游戏。
  • SSE (Server-Sent Events)
    • 单向通信:服务器 -> 客户端的实时推送。
    • 基于 HTTP:不需要特殊协议,天然支持断线重连和事件 ID。
    • 场景:股票行情、新闻推送、AI 大模型流式输出(ChatGPT 就用的 SSE)。
  • 对比:需要双向通信选 WebSocket;只需服务器推送选 SSE(更简单、自动重连)。

93. 前端测试策略

  • 测试金字塔
    • 单元测试 (Unit):测试最小功能单元(工具函数、Hooks)。速度快、数量多。工具:Vitest、Jest。
    • 组件测试 (Component):测试组件渲染和交互。工具:Testing Library(Vue/React 通用)。
    • E2E 测试 (End-to-End):模拟真实用户操作整个流程。速度慢、数量少但价值高。工具:Playwright、Cypress。
  • 核心原则
    • 测试行为而非实现:不要测 "state 变成了 X",而是测 "用户点击后页面显示了 Y"。
    • Arrange-Act-Assert 模式:准备数据 -> 执行操作 -> 断言结果。
  • 覆盖率:不要盲目追求 100%。核心业务逻辑、边界情况、回归 Bug 是重点。

94. Monorepo 与包管理

  • Monorepo:把多个相关项目放在同一个 Git 仓库中管理。
    • 优势:代码共享方便、原子化提交(改了公共库和使用方在一个 commit)、统一工具链。
    • 劣势:仓库体积大、权限管理粗粒度、CI 需要优化。
  • 工具链
    • pnpm workspace:包管理器层面的 Monorepo 支持。pnpm 通过硬链接 + 符号链接实现磁盘空间节约和严格依赖隔离(不会出现幽灵依赖)。
    • Turborepo:构建编排工具。智能缓存(本地 + 远程)、并行执行、只构建受影响的包。
    • Nx:功能更全面的 Monorepo 框架,内置依赖图分析和增量构建。

95. CSS 新特性

  • Container Queries(容器查询)
    • 比媒体查询更精细。媒体查询只能检测视口宽度,容器查询可以检测父容器宽度。
    • @container (min-width: 400px) { .card { flex-direction: row; } }
    • 场景:同一个卡片组件,在侧边栏里纵向排列,在主内容区横向排列。
  • :has() 选择器(父选择器)
    • CSS 终于能"向上看"了。div:has(> img) 选中"包含 img 子元素的 div"。
    • 场景:表单校验 — input:has(~ .error) 让有错误提示的输入框变红。
  • CSS 嵌套(原生)
    • 不再需要 Sass/Less 就能写嵌套。.card { h2 { color: red; } &:hover { opacity: 0.8; } }
  • Subgrid:子网格继承父 Grid 的轨道定义,对齐更精确。

96. React Server Components (RSC)

  • 核心思想:组件在服务器上渲染,不发送组件 JS 代码到客户端,只发送渲染结果。
  • 与 SSR 的区别
    • SSR:组件在服务器渲染成 HTML,然后客户端下载 JS 进行 Hydration(注水),组件代码仍然会到达客户端
    • RSC:Server Component 的代码永远不到达客户端。可以直接访问数据库、读文件系统,零客户端 JS 开销。
  • 分类
    • Server Component(默认):不能用 useStateuseEffect 等客户端 Hook。
    • Client Component(标记 'use client'):普通的交互式组件。
  • 框架:Next.js App Router 是 RSC 的主要载体。

97. Web Accessibility (无障碍访问)

  • 为什么重要:全球约 15% 人口有某种形式的残障。无障碍不仅是道德要求,很多国家和地区已有法律强制要求。
  • WCAG 标准:Web Content Accessibility Guidelines,分 A、AA、AAA 三个级别。
  • 实践要点
    • 语义化 HTML:使用正确的标签(<button> 而非 <div onclick>),屏幕阅读器才能识别。
    • ARIA 属性aria-label(为无文字元素提供标签)、aria-hidden(对辅助技术隐藏装饰元素)、role(声明元素角色)。
    • 键盘导航:所有交互都必须能通过键盘完成(Tab 切换焦点、Enter/Space 触发)。
    • 颜色对比度:文字与背景的对比度至少 4.5:1(AA 标准)。不能仅用颜色传达信息。
    • alt 属性:所有有意义的图片必须有描述性的 alt 文字。

98. 前端错误监控

  • 错误类型
    • JS 运行时错误window.onerrorwindow.addEventListener('error') 捕获。
    • Promise 未处理的拒绝window.addEventListener('unhandledrejection') 捕获。
    • 资源加载错误:图片、脚本加载失败,通过 error 事件(捕获阶段)监听。
    • 接口错误:封装 fetch / axios 拦截器统一上报。
  • 上报方式
    • navigator.sendBeacon(url, data):页面即将关闭时也能可靠发送(不阻塞卸载)。
    • new Image().src = url:简单但有 URL 长度限制。
  • Source Map:生产环境代码压缩后报错行号没意义。上传 Source Map 到监控平台(Sentry 等),可还原出源码位置。
  • 主流工具:Sentry、阿里 ARMS、腾讯 Aegis。

99. 大文件上传方案

  • 核心思路分片上传
    1. 前端使用 file.slice(start, end) 将大文件切成小块(如每块 5MB)。
    2. 并发上传各分片(控制并发数,如最多 3 个)。
    3. 后端收到所有分片后进行合并
  • 断点续传
    • 每个分片用文件内容的 hash(如 SparkMD5)标识。
    • 上传前先问后端"哪些分片已经传过了?",已传过的跳过。
    • 网络中断后重新打开页面,只需传剩余分片。
  • 秒传
    • 上传前先计算整个文件的 hash,发给后端查询。
    • 如果后端已有相同 hash 的文件,直接返回成功,无需实际传输。
  • Web Worker:hash 计算量大,放到 Worker 线程避免卡主线程。

100. 前端发展趋势

  • AI 驱动开发:AI 辅助编码(Copilot、Claude Code)、AI 组件生成、自然语言转 UI 正在重塑开发流程。
  • Rust 工具链:SWC(编译)、Rolldown(打包)、Biome(Lint + Format)、Turbopack 等 Rust 编写的工具正在替代 JS 工具链,构建速度提升数十倍。
  • 边缘计算 (Edge Runtime):Cloudflare Workers、Vercel Edge Functions。代码运行在离用户最近的边缘节点,冷启动极快。
  • Islands Architecture(孤岛架构):页面大部分是静态 HTML,只有交互部分(孤岛)加载 JS。代表框架:Astro。
  • WebAssembly (Wasm):将 C/C++/Rust 编译为浏览器可执行的二进制格式。适合计算密集型场景(图像处理、视频编辑、3D 渲染)。Figma 就大量使用 Wasm。
  • 类型安全全栈:tRPC、Zod 等工具实现前后端类型共享,从 API 到数据库的端到端类型安全。

全系列完结

你已经浏览完了前端面试最核心的 100 个知识点

如何复习:

  1. 查漏补缺:看着标题,如果你能自己讲出个大概,就过;如果卡住了,去搜详细解析。
  2. 深度优先:对于框架原理(Vue/React)和工程化(Webpack/Vite),不仅要背结论,最好能看过源码浅析。
  3. 手写练习:第 76-90 题,请务必在电脑上敲一遍,肌肉记忆很重要。
  4. 关注趋势:第 91-100 题的内容在近年面试中出现频率越来越高,尤其是 RSC、Monorepo、错误监控等。

祝你面试顺利,拿到心仪的 Offer!

基于 VitePress 构建