前端知识体系梳理
一、函数式编程
1. 核心特性:不可变
- 数据不可更改,React 的 state、reducer 都要求遵循该原则;简化操作需要借助 immer 这类工具
- 为什么用:正因为数据不可变,所以需要复制产生新的对象;复杂对象复制更新较为繁琐,因此使用工具库简化
- 优势:可预测、可测试、可维护、具备确定性;React 内部是浅比较(引用比较),如果直接修改会导致渲染异常
2. 纯函数
- 定义:确定的输入,产生确定的输出,并无副作用(没有修改外部变量这类与当前函数功能无关的操作)
- 注意事项:不要随便引入全局变量,不要引入可变数据,尽可能保障确定性
- 价值:解决可测试性、可维护性,保障工程中的确定性
- 相关应用:React 纯函数、reducer 需要掌握
3. 相关工具库
- lodash(需掌握)
- immer
- date-fns
4. 单向数据流
- 数据流向单一可控:props 只读,只能由父传子并改变,除非通过回调传入改变函数
5. 组合思想
- 一般组合优于继承
- 单一职责、低耦合
- 继承:耦合度高,父类的改变可能会影响其余所有子类
6. 高阶函数
- 本质是装饰器的思想
- 定义:传入函数或者返回一个函数
- 应用场景:
- 链式调用
- 偏函数
- 柯里化
- compose 中间件的思路
- 装饰器这类
7. 声明式写法
二、输入 URL 发生了什么
1. URL 解析
- 目的:路径补全、检测、缓存,便于后续解析域名
2. 域名解析(DNS)
- 递归查询
- 迭代查询
3. 网络传输分层
- 应用层:开始 HTTP 传输,首先建立连接
- 传输层:通过 TCP 建立连接,通过三次握手
- 网络层:IP 协议,寻址,找唯一的 MAC 地址
- 数据链路层
- 物理层
4. 浏览器渲染
- DOM 树构建
- CSS 树构建
- Render 渲染树构建
- 布局(回流):计算位置等信息
- 绘制:绘制可视图层
- 合成层:发送以上信息到 GPU 合成层,进行屏幕显示
三、跨标签页通信
1. 解决的问题
解决同域、非同域标签页的通信问题
2. 技术选型
- localStorage
- 仅同域名
- 容量有限制,只能存字符串
- BroadcastChannel
- 仅同源
- 当前标签页可被广播
- 支持对象等多种格式
- 用法和 postMessage 类似:postMessage 发送,message 监听
- 容量更大,取决于浏览器内存占用
- postMessage
- 支持跨域,项目中使用 iframe 时,可根据简洁、高效、跨域需求选择
- 注意数据序列化与性能问题
- SharedWorker
- 可以被多个标签页共享的线程
- 对比 Web Worker 只能在当前标签页使用
- 可用于计算优化
四、Web Worker
1. 为什么用 Web Worker
- 属于多线程 API
- 浏览器允许开辟一个线程单独运行,不阻塞主线程
2. 目的
- 解决主线程阻塞问题
- 提升性能
3. 应用场景
- 计算密集型:如大量的数据计算、图形处理;业务存在很多二次数据处理、特定算法处理,加快首屏渲染速度
- 文件上传、导出场景:针对金融业务表格数据居多的场景,前端自己导出表格会占用较多内存和时间
- 订阅大量实时行情:首屏加载需要处理大量实时数据
4. 弊端
- 无法访问全局变量、DOM
五、浏览器存储
1. Cookie
- 请求会自动携带,包含服务器一些少量数据
- 用于用户状态相关内容
- 相关属性:
- SameSite:防范跨站请求伪造(XSRF),禁止跨站携带;可配合 XSRF 双重 token 进行验证
- Strict:严格模式
- maxAge:过期时间
- domain:限制/允许其他子域名访问
- path:限制同域名的路径访问
- HttpOnly:防范 XSS
- Secure:仅 HTTPS 下携带
2. localStorage
- 容量:5-10MB
- 生命周期:永久,实践中会加入 TTL 过期控制
- 支持同域共享,可通过 storage 事件监听变化
- 适用场景:少量接口缓存(配备 TTL)以及用户的配置信息,提升用户体验
3. IndexedDB(异步)
- 属于数据库级存储,异步,容量大,支持多类型内容存储
- 适用场景:需要存储文档、图片等大容量内容时可用
4. sessionStorage
- 容量:5-10MB
- 生命周期:浏览器标签页关闭即消失
- 仅同域可用
5. Token 与 Session 区别
- session 存服务器,cookie 存浏览器
- session 依赖 cookie,存在 CSRF 风险;token 放请求头中,更安全
- token 支持跨域,session 仅同域
6. 相关问题
- CSRF 为何 cookie 请求会被携带?不是不同域吗?
- CSRF 如何防范
六、跨域
1. 本质
浏览器的安全限制,保障安全,禁止不同源脚本交互;同源要求协议、端口、域名完全一致
2. 解决策略
- JSONP
- 简单
- 只支持 GET 请求,难以处理复杂数据,存在安全攻击风险(callback 注入)
- CORS
- OPTIONS 预检请求:复杂请求(PUT、DELETE、携带复杂请求头)会先发预检
- 请求头:
- Origin:标识请求源
- Access-Control-Request-Method / Headers:告知服务端请求方法和请求头
- 响应头:服务端返回 Access-Control-Allow-Origin / Headers 等
- 浏览器判断是否允许跨域
- 携带 cookie:通过 credentials(凭证)字段设置
- sameOrigin:同域才携带
- omit:跨域不携带
- include:强制全部携带
七、前端安全
1. XSS(跨站脚本攻击)
- 本质:向浏览器注入恶意脚本并执行
- 分类:
- 反射式:注入脚本到路径,被访问后窃取信息发送到服务器
- 存储式:恶意脚本存储到服务器中
- DOM 型:把危险脚本通过 JS 注入到 innerHTML 中执行
- 防范:
- 输入、输出转义:通过 dompurify,或直接转义
- 转成 Markdown:AI 项目中为了兜底,先通过 markdown-it 进行了转义
- 设置 Cookie 的 HttpOnly 属性,禁止 JS 读取
- CSP 防护:设置请求头,控制加载资源,避免内联、非法资源加载
2. XSRF/CSRF(跨站请求伪造)
- 原理:用户已登录状态下,点击攻击页面,向目标网站发起伪造请求
- 本质:利用 cookie 在目标域请求中会自动携带的特性
- 防范:
- 双重 token 验证:利用跨域限制,攻击者无法获取具体 cookie 内容
- SameSite:仅同站点才能携带 cookie
- 验证码
3. 其他攻击
- 点击劫持:通过 X-Frame-Options(deny、allow 等)禁止 iframe 嵌入加载,CSP 设置白名单链接
- DDoS 攻击
- 中间人攻击等
八、前端工程化
1. 工程化是什么
一套软件工程流程,包含开发、测试、优化、部署、监控、CI/CD 等体系化工程
- 解决的问题:降低开发成本,提升工程效率,以及扩展性、稳定性等
2. 模块化
- 核心思想:分层解耦
- CommonJS 与 ES Modules 区别:
- CommonJS:同步、动态加载模块,导出的是值拷贝;通过 module.exports 导出,require 导入
- ES Modules:静态加载(编译阶段确定依赖关系),支持 Tree-Shaking,导出的是引用;通过 export / export default 导出,import 导入
- Node.js 使用 ES Modules:扩展名为 .mjs,或在 package.json 指定 type 字段为 module
- 循环依赖区别(低频考点)
3. Webpack
- 是什么:静态模块打包构建工具
- 解决的问题:解决浏览器兼容问题,以及性能优化问题
- 核心概念:
- entry:入口
- output:输出
- loader:文件转译
- css 相关:style-loader、css-loader
- js 相关:babel-loader
- thread-loader:开启多线程
- plugin:插件,处理整个构建流程
- module:单文件模块
- chunk:多个 module 构成,按照包更新频率进行分割
- bundle:最终文件产物
- 构建流程:
- 初始化阶段:读取配置,初始化 compiler 对象(管理配置、插件、生命周期);注册 plugin(plugin 通过 apply 函数传入 compiler 对象实现)
- 编译阶段:根据入口文件分析依赖图;遇到模块单文件使用 loader 进行转化,生成 AST;配合代码分割等优化;最终生成 chunk
- 输出阶段:根据输出配置,生成 bundle 文件
- 相关问题:
- Tree-Shaking 原理、适用场景
- 编译期间怎么提速
- 多线程优化属于哪个阶段
- loader 与 plugin 区别:
- loader:函数,针对单文件内容进行转换(如 markdown 转 html、babel-loader、ts-loader、css-loader)
- plugin:解决整个文件系统级别的问题,loader 只能处理单文件;通过 tapable 监听生命周期钩子执行
4. Vite
- 冷启动:相比于 webpack,直接利用原生浏览器 ES Module 解析能力,绕过编译打包,按需编译访问的文件
- 预构建:提前预构建 node_modules,并行处理;通过 esbuild(Go 语言编写的打包工具)实现多线程打包,合并多个文件及入口
- 预构建目的:1. CommonJS 转 ES 格式;2. 合并请求,减少请求数
5. 包管理工具
- npm:兼容性好,但会扁平化依赖,存在重复下载、幽灵依赖问题(包提升到根目录)
- yarn:支持并行下载、缓存机制
- pnpm:树形依赖结构,无幽灵依赖、无重复包;通过符号链接/硬链接减少重复包加载
6. Monorepo
- 多包管理方案:解决依赖治理、代码复用、团队协作问题;统一一个仓库,统一管理依赖和发布
7. Turbopack:了解即可
8. CI/CD
- 持续集成和部署:代码提交 → 自动构建 → 自动测试 → 部署上线
- Docker:保障环境一致性和隔离性,了解即可
9. 测试
- 共用函数:Jest
- 组件级别:React Testing Library
- E2E:端到端测试,覆盖完整流程
九、JavaScript 基础核心
1. 数据类型
- 共 8 种
- 基础类型:Symbol、String、Boolean、BigInt、null、undefined、Number
- 引用类型:Object
- 基础类型与引用类型区别
- 如何避免被修改:浅拷贝、深拷贝
- 浅拷贝:只拷贝第一层,嵌套对象只复制地址,无法完全隔离
- 实现方式:Object.assign、解构运算符、数组专用(slice、concat)
- 深拷贝:递归处理,生成完全独立的副本
- JSON.stringify 方案:无法处理循环引用、函数、异常值
- 浅拷贝:只拷贝第一层,嵌套对象只复制地址,无法完全隔离
- 工程运用:React 中 immer 底层基于浅比较、纯函数思想,返回新的对象,简化不可变数据操作
- 浅比较:处于性能平衡考虑,避免大对象深度对比
- 栈与堆:
- 基础数据类型存栈,引用类型存堆
- 栈是值的拷贝,堆是值的引用
- 基础类型不可变,引用类型可变
- 栈存放基础数据类型原因:栈结构简单,空间有限,提升查询效率;堆存引用类型,结构复杂,内存支持动态变换,适配复杂类型
2. 类型判断
- typeof:可判断基础数据类型,引用类型无法精细判断;typeof null 为 object(JS 早期设计缺陷:null 用全0字符表达,object 也用0标识,导致误判)
- instanceof:无法精准判断所有引用类型,无法判断基础类型;通过原型链机制判断
- Object.prototype.toString.call:最精确
- Array.isArray:专门判断数组
3. 浮点数精度问题
- 0.1 + 0.2 !== 0.3
- 原因:IEEE 754 标准,计算机会进行二进制转换,小数点存在精度截断
- 结构:1 位符号位、11 位指数位、52 位有效数字位
- 最大安全整数:2^53 - 1;-1 是为了精度不溢出,53 位包含一个前导 1
- BigInt:超出 2^53 - 1 的数值,可安全表示
4. Symbol
- 用途:模拟私有变量,解决命名冲突,工程协作中常用
- Symbol.iterator:迭代器,用于部署可迭代对象
5. 事件循环
- 是什么:解决异步任务调度的事件管理机制
- 背景:针对 JS 单线程特性,解决异步任务造成的阻塞现象
- 构成:
- 调用栈:压入执行同步任务
- 宏任务:定时器、事件监听器、UI 渲染、MessageChannel;MessageChannel 用于 React 内部调度,替代 requestIdleCallback;由宿主环境(浏览器、Node)发起
- 微任务:JS 引擎 API 发起;包括 Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node 中优先级最高)
- Web API
- 执行顺序说明:
- 先执行同步代码,再清空微任务队列,再执行一个宏任务,再清空微任务
- requestAnimationFrame 执行时机:微任务之后、下一个宏任务之前执行
- 浏览器与 Node 事件循环区别:
- 浏览器:每次执行一个宏任务,就清空一次微任务队列
- Node:微任务清空在每个阶段后;宏任务队列是批量执行,不是单个执行
- 示例:两个 setTimeout 都进入 Timers 宏任务队列,执行顺序为:定时器 1 执行 → 定时器 2 执行 → 微任务 1 → 微任务 2
- 底层差异:浏览器由浏览器内核驱动,Node 由底层 libuv 库驱动
- Node 无 UI 渲染,存在最高优先级的 process.nextTick
6. 作用域与闭包
- 作用域是什么:变量生效的范围和规则
- 词法作用域:作用域由代码定义的位置决定,而非调用的位置
- 分类:
- 全局作用域
- 局部作用域
- 函数作用域
- 块级作用域
- React 中的函数作用域陷阱:需要用函数式更新、useEffect 来解决
- 作用域链:链式嵌套结构,解决查找外部作用域变量的机制
- 工程应用:webpack 作用域提升,避免嵌套作用域查询效率低下
- 变量提升:早期 JS 的代码容错机制,提早预解析提升效率,但影响工程规范;后续引入 let、const 形成暂时性死区,不允许提前访问,要求先定义后访问
- 闭包:
- 定义:词法作用域下,引用外部局部作用域自由变量的函数,二者的组合就是闭包
- 注意:单纯引用全局变量不是闭包;闭包的价值之一是延长局部作用域生命周期,全局变量本身不需要闭包
- 应用场景:缓存、防抖节流、变量私有化、偏函数、模块化(立即执行函数)
- 闭包带来的问题:引用的局部变量不会被销毁,造成内存泄漏;如定时器未清除
- 解决方式:及时断开引用、使用 WeakMap 弱引用、组件卸载时及时清除定时器
- WeakMap:
- 键只能是对象
- 不可被迭代,键值对随时可被垃圾回收
- 弱引用特性
7. this 指向
- 是什么:动态的上下文注入机制
- 绑定方式:call、apply、bind(显式绑定)、隐式绑定
- 绑定优先级:
- new 绑定:优先级最高,其余绑定规则都会失效;this 始终指向实例对象
- 显式绑定:call、apply、bind
- 隐式绑定:谁调用,this 指向谁
- 默认绑定:独立调用时默认指向 window
- 箭头函数:
- this 始终为外部词法作用域的 this
- 没有原型对象
- 无法作为构造函数
- 无法使用 new 操作
- 没有 arguments 对象,可用剩余参数替代
- this 丢失场景:
- 定时器:回调独立调用,默认指向 window
- forEach、map:回调都是独立调用,默认指向 window;可通过传入 this 参数解决
- 事件监听函数:普通函数指向目标 DOM 对象(内部自动绑定);箭头函数继承外层作用域 this
- 继承场景特殊规则:super 调用后,父构造函数的 this 指向子类实例对象
8. 原型与原型链
- 原型是什么:原型模式用于实现继承;从结构上就是对象,构造函数的 prototype 指向原型对象
- 作用:能通过原型链查询,实现属性方法的复用
- 继承实现方式:原型继承、组合继承、寄生继承、寄生组合继承、ES6 class
- 弊端:原型污染(覆盖原型链上的属性方法)
- 规避方式:规范与 ESLint 禁止覆盖原型;使用 Object.create(null)、Map
- Map:键独立存储,不查询原型链
- hasOwnProperty:检查自有属性
- Object.create(null):创建无原型链的纯净对象,避免原型链干扰
- 相关问题:如何判断一个纯对象?1.
__proto__是否指向 null(Object.create(null));2. 隐式原型的 constructor 是否指向 Object 构造函数
9. 异步编程
- 价值:解决非阻塞线程问题,突破单线程语言限制
- 发展历程:回调地狱 → Promise → Generator → async/await
- Promise:
- 特性:一旦状态改变绝对不可逆
- 三种状态:pending、fulfilled、rejected
- 实例方法:then、catch、finally
- 静态方法:all、race、allSettled、any、resolve、reject
- 取消方式:加入标志位、AbortController
- Generator 生成器
- async/await:
- 规则:async 函数如果返回普通值,会被 Promise.resolve 包裹;如果是 Promise 对象则直接返回
- 原理:依赖 Promise 外加 Generator + next 自执行综合实现
10. ES6 特性
- var、let、const 区别
- Symbol / BigInt
- Map、Set、WeakMap:哈希表结构,O(1) 查询操作;WeakMap 通过弱引用解决 Map 引发的内存泄漏问题
- 解构、展开运算符:常用于简化取值操作
- 箭头函数:简化函数写法,注意 this 指向问题
十、TypeScript
1. TS 是什么
- JS 的超集,静态类型语言
- 解决的问题:解决 JS 动态类型运行时才发现类型错误的问题(JS 报错排行第一就是类型相关错误)
- 提升项目的易读性、健壮性
- 本质:用 TS 实现了一个编译器
2. 编译原理
- parse 解析 TS:词法分析、语法分析
- 类型检测:检测类型是否规范,不规范则报错
- 代码生成:去除所有类型内容并进行转译,输出浏览器可识别的 JS
3. 常用类型
- 基础 8 种数据类型
- 元组:解决数组元素类型不同的场景
- 枚举:
- 字段更语义化、可扩展;数字枚举支持反向映射
- enum 枚举:编译期会生成完整对象,可遍历、可反向映射
- const enum:编译期不生成对象,运行时直接返回值,不可遍历
- 联合类型:类型存在多种可能性
- 交叉类型:实现类似类型继承的效果
- any、unknown(类型安全的 any)、never(抛错时、绝对无返回值场景)、void(函数无返回值)
- type 类型别名:可定义任意类型别名,提高可维护性;实现复用和语义化
- type 与 interface 区别:
- type 扩展用交叉类型,interface 用 extends
- type 支持任意类型别名,interface 仅支持对象和函数
- type 专注于任意类型别名,interface 专注于对象、继承
- as 类型断言:
- 本质:编译期指定具体类型
- 用途:对 any、unknown 类型恢复类型判断;对联合类型收窄类型
- 泛型:提升扩展性
- 分类:函数泛型、类泛型、参数泛型、接口泛型
- 泛型约束:通过 extends 对泛型进行类型约束、收窄
- 逆变与协变:
- 协变:子类型可以赋值给父类型
- 逆变:父类型可以赋值给子类型
十一、React
1. React 是什么
- 数据驱动视图的 UI 库(state/props 变化 → reconcile 协调 → 计算最小变化 → DOM 更新 → UI 变化)
- 核心思想:函数式编程、严格单向数据流、声明式写法、JSX 描述 UI、组件化(实现解耦、复用)
2. Fiber
- 是什么:
- 数据结构层面:链表结构,包含组件信息、优先级、指针
- 为什么用链表:通过指针指向,便于中断和恢复执行
- 结构:
- type:节点类型
- memorizedState:当前状态
- child:子节点指针
- sibling:兄弟节点指针
- return:父节点指针
- alternate:指向 workInProgress 树
- key:节点标识
- 算法层面:协调算法,通过优先级调度、时间分片、双缓存树 diff 对比
- 双缓存树:
- 优势:保障 UI 一致性,无中间状态;支持可中断、可恢复
- 通过 alternate 实现双缓存树互相指向、交替轮换
- 时间分片:
- 任务拆分为 5ms 一个单位
- 判断当前帧有无空闲,超时则让出控制权,执行下一个 fiber 单元
- 每次指向当前 fiber 单元,检测是否超时;超时后保存下一个工作单元,执行完毕后再返回
- 配合宏任务 MessageChannel 模拟时间窗口调度
- 工作循环:
- 循环执行 fiber 任务(深度优先遍历 DFS)
- 每次执行前判断当前帧是否有空闲:有则继续循环执行;没有则跳出执行循环并保存工作单元,下一次继续执行
- 解决的问题:
- 解决 React 15 及之前同步更新阻塞的问题(根源是 JS 是单线程语言)
- 目标:实现可中断、可恢复的更新,支持并发特性(调度、时间分片、协调算法等)
3. 协调与提交
- 协调阶段(Reconcile)
- beginWork:处理当前 fiber 节点,根据 props 和 state 计算新的 workInProgress;执行 diff 对比,同层级打标签(增删改)
- completeWork:根据更新后的 fiber 树,创建/更新 DOM 节点,不立即插入
- 配合 Scheduler 实现任务调度、时间分片、协调 diff
- 提交阶段(Commit)
- 同步更新,不可中断
- beforeMutation:调度 useEffect,DOM 操作前执行
- mutation:根据协调阶段 diff 打的标签,执行 DOM 更新操作
- layout:执行 useLayoutEffect,更新 ref
- 职责:将副作用同步到 DOM 上,并先后触发 useLayoutEffect 和 useEffect
4. 优先级调度
- 通过 Scheduler 调度
- 通过 Lane 模型分配优先级
- 遇到高优先级,会重新执行 render 逻辑,丢弃之前进行中的 workInProgress 构建
- Lane 模型:基于位运算,高效;可同时存在多个优先级,便于复杂调度
- Scheduler + Lane 模型 + 可中断的 render 共同实现并发更新
- 为什么不用 requestIdleCallback:兼容性差,且无法精细化调度
- 通过 MessageChannel 宏任务进行模拟:在下一次事件循环执行,不会阻塞浏览器渲染
5. Diff 算法
- 是什么:协调对比算法,找出最小差异更新,保证最少 DOM 操作,减少回流
- 三个策略:
- 元素 key 对比:循环列表通过 key 对比
- 组件层级对比:类型不同(如 div 变 span)直接删除重建
- 树层级对比:同层节点不一致,所有子节点全部删除重建
- 时间复杂度低
- 分类:
- 单节点对比:挨个对比,标记副作用;同时判断 key 和 type
- 多节点对比:两次遍历,第一次找可复用节点,第二次处理移动;新节点遍历完、旧节点还有剩余则删除;新节点未完、旧节点无则创建
- key 的作用:用于 diff 过程的节点复用
- key 随机/不唯一的弊端:会导致复用错误、状态错误
6. Hooks
- 是什么:函数式编程的体现,解决函数组件本身无状态的问题
- 本质:代表函数式编程中的组合模式,常规场景优于继承,解决工程中的封装和扩展问题
- 为什么不能在条件语句中使用:
- Hooks 在编译期通过链表节点的 next 指针确定调用顺序
- 更新时会按照定义的 hooks 调用顺序依次执行更新
- 运行时如果顺序变换,会导致状态更新错误、渲染异常
- useState 流程:
- 创建 hook 链表,memorizedState 存储状态值
- 初始化状态值
- 维护更新队列,next 指向下一个 hook
- set 方法:触发推入更新队列,批量执行
- 渲染阶段:基于更新队列,按照链表顺序更新状态,返回新值和改值操作
- useEffect:
- beforeMutation 阶段调度 useEffect
- 绘制后异步执行 useEffect 回调
- 初始化创建 effect 对象,包含 deps、执行回调、清理函数、next 指针
- 更新时从 memorizedState 读取旧依赖,和传入的新依赖做浅比较
- 执行顺序:先立即执行清理函数,再执行内部回调
- 自定义 Hook
- useImperativeHandle 适用场景
7. 合成事件
- 是什么:React 自己模拟浏览器事件机制,主要处理事件冒泡,将监听事件集中代理
- 为什么做合成事件:
- 屏蔽浏览器兼容差异
- 可控性强,便于调度
- 与 Fiber 融合,支持可中断处理
- 原理:
- 事件代理到根节点(React 17 及之后代理到 root 容器,16 及之前代理到 document)
- 提升性能,降低内存占用;方便多个 React 版本共存,彼此隔离
- 执行顺序:
- 原生事件先执行,合成事件在 root 冒泡阶段才执行,时机更晚
- 原生事件捕获、冒泡完毕后,再执行合成事件的冒泡
- 原生事件的 e.stopPropagation 无法阻止合成事件
- 阻止方式:1. 通过 e.nativeEvent 原生事件阻止冒泡;2. 设置事件捕获模式
- 规范上不建议原生事件与合成事件混用
8. Redux
- 是什么:全局状态管理库
- 解决的问题:深层组件状态共享造成的重渲染难题
- 核心原则:
- 单一 store:易于持久化、统一管理
- 纯函数修改:具备确定性、可测试
- 数据不可变
- 单向数据流
- react-redux:
- 作用:将状态和 UI 绑定,从而驱动页面更新
- 原理:基于 createStore 创建数据源;定义 action 决定对数据的处理;reducer 接收 dispatch 发出的 action 并返回新的 state;组件订阅 state 的变化从而重新渲染
- 中间件:
- 定义:增强 dispatch 功能的函数
- 执行时机:处于 dispatch 之后,到达 reducer 函数之前
- 原理:洋葱圈模型,高阶函数实现
9. 组件生命周期
- 挂载时:constructor → componentWillMount → componentDidMount
- 更新时(props/状态更新触发):componentWillReceiveProps → shouldComponentUpdate → componentWillUpdate → render → componentDidUpdate
- 卸载时:componentWillUnmount
10. JSX 本质
- createElement 的语法糖
- React 17 版本后 JSX 转换的修改
11. 版本差异
- React 17 相比于 16 的改进
- React 18 的改进
十二、浏览器渲染
1. 渲染进程与流程
- 词法分析:拆分字符(UTF-8)为最小 token 单位
- 语法分析:根据结构信息构建语法树
- 构建 DOM 树
- 构建 CSS 树(CSSOM):与 DOM 树并行处理,互不阻塞
- 作用:计算可视元素样式,提供访问接口,生成 CSS 树
- 不阻塞 DOM 树构建,但会阻塞后续渲染树构建
- 渲染树(Render Tree):DOM 树和 CSS 树合成构建,会被 CSS 阻塞;只包含可视节点,display: none 的节点不包含
- 布局(回流):重新计算元素几何位置
- 绘制(重绘):重新绘制图像像素
- 合成:合并多个图层,GPU 处理合成图像
2. JS 阻塞问题
- DOM 和 CSS 构建遇到 JS 都会阻塞,因为 JS 可能操作 DOM 或 CSS,需要等待其执行完
- CSS 放头部的原因:避免阻塞渲染树构建
- defer 与 async 区别:
- 共同点:都是异步加载,不阻塞 DOM 解析
- defer:加载完毕推迟执行,DOM 解析完毕后执行,保证执行顺序,强调依赖顺序
- async:加载完毕立刻执行,不保证顺序,适合无依赖的脚本
3. 回流与重绘
- 回流
- 定义:重新计算布局
- 触发场景:元素几何属性、位置信息变化,操作 DOM
- 如何减少回流:
- 批量/离线操作 DOM
- 虚拟滚动,减少 DOM 数量
- 防抖/节流
- 提升到合成层(跳过回流),如 transform、will-change
- 结论:回流一定触发重绘,重绘不一定触发回流
- 重绘
- 定义:重新绘制图像像素
- 触发场景:元素颜色、样式等非几何属性变化
- 流程:从背景到元素逐节点绘制;光栅化转换为像素数据
- 合成层
- 独立的绘制层,由 GPU 处理
- 触发方式:transform、opacity、will-change
- 注意:合成层不宜过多
- GPU 进程:负责 3D 绘制、硬件加速
- 插件进程、网络进程
4. 多进程优势
- 充分利用多核能力,提升效率
- 分治思想:保障稳定安全,避免单进程崩溃导致整个浏览器崩溃
十三、HTTP
1. 定义
传输协议,用于数据请求和传输,处于应用层
2. HTTP 1.0 / 1.1 区别
- 连接方式:1.0 是短链接、串行请求(一个请求一个响应);1.1 支持长连接,通过 Connection: keep-alive 可设置过期时间
- 缓存:1.0 只有 expires 过期时间;1.1 支持 Cache-Control、Etag
- 新增 host 头、状态码等
- host:区分同一个 IP 地址下不同域名的请求
- origin:跨域标识,包含协议、端口、域名
- referer:请求来源,用于行为监控统计
- 常见状态码:301、302、304、307
- 307 等价于 302 的保障版,临时重定向,保持请求方法一致
3. HTTP 2.0
- 多路复用:多个请求并发,复用一个 TCP 连接,应用层不阻塞
- 二进制帧 + 流 ID 机制:服务端通过 ID 重组数据,不阻塞应用层
- 请求头压缩:HPACK 算法,维护索引映射表,只传输索引,减少传输消耗
4. HTTP 3.0
十四、性能优化
1. 性能指标
- TTFB(首个字节响应时间)
- 优化方向:HTTP2、服务端缓存、CDN、默认开启 keep-alive
- FP(首次绘制):首个任意内容绘制
- FCP(首次内容绘制):首个文本、图片、canvas 等内容绘制
- LCP(最大内容绘制) :视口内最大内容绘制时间,内容动态变化时值也动态变化
- 统计对象:img、背景图、包含文本的块级元素
- 优化方向:预加载、多级缓存、CDN、CSS 内联关键样式、JS 延迟加载(async)
- FID(首次交互延迟)
- 优化方向:Web Worker 开启辅助线程降低主线程压力;useTransition 提高用户操作优先级;分包、按需加载减少 JS 体积
- TTI(完全可交互时间):技术指标,FID 更贴近实际用户感知
- 测量工具:Lighthouse、Performance Timing、PerformanceObserver、web-vitals
- 上报方式:集中通过 sendBeacon / fetch 兼容上报
2. 加载性能优化
- 资源压缩
- 预加载系列:
- pre-dns:提前 DNS 解析
- pre-connect:提前建立 TCP、TLS 连接
- preload:预加载并立即执行,用于关键 CSS、字体资源
- prefetch:空闲时执行,用于非首屏关键资源
- CDN
- HTTP 2.0 多路复用
- Tree-Shaking
- 按需加载
3. 渲染性能优化
- 虚拟滚动
- Web Worker 离线计算
- requestIdleCallback 空闲执行
- React.memo 等组件缓存
- 减少直接操作 DOM 的回流行为
- 防抖和节流
十五、前端监控
1. 行为监控
- rrweb:监控回放用户行为,追踪完整链路
- 采集粒度:页面级别、元素级别
- 上报方式:sendBeacon、fetch、img(img 只有 get 方法,受浏览器参数限制)
2. 性能监控
- performance.timing:记录各种性能指标时间
- performanceObserver:检测性能指标及渲染性能,更全面
- resource-timing:资源加载时间统计
3. 错误监控
- JS 错误:监听 error 事件
- Promise 错误:监听 unhandledrejection
- 资源加载错误:通过 target 区分 window 与资源
- 请求错误捕获等
- sourceMap:源码映射压缩文件,用于定位错误位置;只在服务器上传一份,不暴露到前端
4. 白屏检测
- 采样检测:检测是否有关键节点 DOM 元素
- MutationObserver 检测 body 子节点变化次数
十六、系统设计
1. 组件库设计
- 主题系统:Context + CSS 变量控制
- 多语言
- 按需加载:通过 babel-import-plugin 自动切换路径
- 公共库:轻量化
- 组件开发:TS 类型完善、规范接口、尽可能做受控组件、配套可用文档
- 配套监控
2. 文件上传设计
- 大文件切分:固定大小切分
- 并发上传:按照切分后的模块进行并发限制上传
- 续传:记录已上传成功的分片及对应索引
十七、CSS 补充
1. HTML 语义化
- 优势:扩展性、可读性好,SEO 友好
- 常用标签:header、title、nav、main 等
2. H5 新 API
- localStorage、WebSocket、Web Worker、Canvas、SVG
3. 盒模型
- 标准盒模型(content-box):宽高不包含 border 和 padding
- IE 盒模型(border-box):宽高包含 border 和 padding,更符合直觉,width 就是实际宽度
- 通过 box-sizing 设置
4. BFC(块级格式化上下文)
- 定义:独立的渲染区域,内部的元素布局不影响外部布局
- 解决的问题:
- 清除浮动:父元素触发 BFC 后,内部浮动元素会被计算高度
- margin 重叠:包裹子元素避免 margin 重叠
- 自适应布局:浮动元素 + 右侧 BFC 元素实现自适应宽度
- 触发方式:
- float 不为 none
- overflow: hidden(元素不溢出,触发 BFC)
- display: flex / flow-root(flow-root 专门用于生成 BFC)
- 验证:通过 computed 查看 block 标识,判断是否清除浮动、margin 是否重叠
5. 清除浮动
- 伪元素清除浮动:
.clearFix::after { content: ''; display: block; clear: both; } - 原理:通过 after 伪元素添加一个空内容块级元素,设置 clear: both 实现清除
- 触发 BFC 也可清除浮动
6. Flex 布局
- 容器属性(父元素):
- flex-direction:row(横向)、column(竖直)
- flex-wrap:wrap、nowrap
- justify-content:center、space-between、space-around、flex-start、flex-end
- align-items:center、flex-start、flex-end
- 元素属性:
- flex-grow:0 / 1,放大比例
- flex-shrink:0 / 1,缩小比例
- flex-basis:初始大小
- 0%:完全由 grow 决定大小
- auto:初始由自身内容决定
十八、缓存
1. 强缓存与协商缓存
- 强缓存:通过 Cache-Control 的 max-age 控制
- 协商缓存:通过 Etag、Last-Modified 验证
- 验证字段:
- If-Modified-Since:对应 Last-Modified
- If-None-Match:对应 Etag
2. 缓存整体流程
- 为什么要有缓存:避免每次请求都让服务器重新发送资源,提升访问效率
- 可缓存资源:静态资源(img、css、js)
- 不建议缓存:HTML(结构可能随需求变更)
- 强缓存流程:
- 首次浏览器向服务器发起 HTTP 请求
- 服务器返回资源,响应头包含 Cache-Control: max-age(资源失效时间)
- 浏览器将资源与缓存标识(Last-Modified、Etag)存放在本地缓存
- 第二次请求资源时,先访问本地资源,同时判断资源是否过期(依据 Cache-Control max-age / expires)
- 未过期:本地缓存直接返回资源
- 过期:进入协商缓存流程
- 协商缓存流程:
- 携带之前服务器返回的缓存标识(Etag / Last-Modified)发送请求给服务器
- 服务器对比自己最新的标识与客户端发来的标识
- 一致:返回 304,告知资源未更新,继续使用本地缓存
- 不一致:返回 200 和新的资源
- 相关属性说明:
- expires:HTTP 1.0 字段,绝对服务器时间,与客户端时间有误差
- Cache-Control:
- max-age:以客户端时间做差值计算
- no-cache:不命中强缓存,直接走协商缓存
- no-store:没有任何缓存,既不走强缓存也不走协商缓存,每次都请求最新资源
- public:可以被代理服务器缓存
- private:不能被代理服务器缓存
- Etag / Last-Modify:
- Etag:服务器依据内容生成唯一标识字符串,会消耗服务器资源;精度更高,防止误判
- Last-Modify:资源最新的修改时间
- 适用场景:Etag 适合动态接口做精准校验;静态资源用 Last-Modified 更高效
十九、HTTPS
1. 是什么
基于 HTTP 协议,下层依赖 TLS 握手保证传输安全
- 解决 HTTP 明文传输的弊端
- 端口号 443(HTTP 为 80)
- 核心特性:加密、验签、摘要,共同保证安全
2. 混合加密机制
- 对称加密:AES 算法,安全中等、性能好,适用于对话传输阶段;平衡性能与安全
- 非对称加密:ECDH 算法,安全性高、性能差,适用于密钥交换阶段;保证传递的密钥未被更改
3. TLS 握手流程
-
HTTPS 1.2:2 个 RTT
- 客户端:发送版本、密码套件、随机数
- 服务端:返回版本、密码套件、随机数、证书
- 客户端:验证证书链,生成预主密钥
- 服务端:接收后,综合三个密钥生成主密钥,开始加密通话
- 验签原理:CA 私钥加密证书摘要,客户端用公钥解密摘要,再通过摘要算法对比证书内容是否改变
-
HTTPS 1.3:1 个 RTT 优化,支持 0-RTT 缓存优化
二十、TCP / UDP
1. TCP 是什么
传输控制协议,解决端到端的、面向连接的、字节流的可靠传输;基于传输层
2. 三次握手
- 客户端向 TCP 发送连接请求:SYN=1,发送 seq
- 服务端接收后:返回 seq 和基于上一个 seq 的确认 ACK
- 客户端:再次发送 ACK,开始通信
- 为什么不是两次:两次无法进行双向确认,会被旧链接干扰
- 为什么是三次:保障双向确认,避免旧链接请求干扰
3. 四次挥手
- 发送方:发送 FIN 终止报文和 seq
- 接收方:返回 ACK 确认
- 接收方:待剩余数据发送完毕,发送 FIN 终止报文和 seq
- 发送方:返回 ACK 确认,等待 2MSL 后进入 CLOSED 状态
- 等待 2MSL 原因:防止服务端未收到最后一个 ACK,重发 FIN 时客户端可重发 ACK;保证两个报文的最大生命周期内都能处理
- 为什么是四次:全双工通信需要双向确认;服务端结束前通常还有剩余数据要发送
4. TCP 特性
- 面向连接
- 可靠性保障
- 拥塞控制:慢开始(指数增长窗口)、拥塞避免(线性增长)、快重传、快恢复
- 流量控制:防止发送方数据过快,确保服务端可处理;通过滑动窗口机制保障
- 头部开销:20 字节
- 适用场景:HTTP、HTTPS、FTP 文件传输等所有需要准确完整的场景
5. TCP 安全与问题
- SYN 泛洪攻击:只发请求不确认;防范:限制同 IP 地址请求速度
- 粘包和拆包:
- 粘包:TCP 为了优化会合并小数据包,导致数据粘连
- 拆包:服务端接收能力有限,超过限制会拆包
- 解决:固定包大小、包体加长度标识
6. UDP
- 无连接、无可靠机制保障
- 头部开销:8 字节
- 适用场景:语音、直播、DNS
二十一、架构设计与编码规范
1. MVC
- 分层:
- Model 模型层:单独处理业务逻辑
- View 视图层:UI 组件仅承接数据展示,不涉及业务及数据处理
- Controller 控制层:涉及数据获取、数据格式化相关
- 价值:使项目层级结构分明,易于维护
2. 虚拟滚动
- 适用场景:万级数据列表渲染
- 目的:避免同时渲染过多 DOM 造成卡顿
- 原理(固定高度场景):
- 确定整个可视区域高度
- 将区域划分为上缓冲区、可视区域、下缓冲区
- 记录用户滚动距离、可视区域与缓冲区上下边界,动态计算各元素距顶部距离,实现动态加载
- 不定高度场景的实现思路
3. Node 相关
- Node 处理了哪些场景
- 遇到大文件怎么处理读取