前言
本手册整合 JS 基础、浏览器端、Node 服务端、异步底层、元编程、工程化、Web 高级 API、安全、手写源码、TS 配套知识,覆盖入门到高级架构面试全部内容,可作为长期学习、查漏、面试复习工具书。
整体学习路线:基础语法 → ES6+ → 函数 / 原型面向对象 → 异步与事件循环 → DOM/BOM 浏览器 API → 网络与跨域 → 二进制 / 正则 → Node.js → 元编程底层 → 性能 / GC / 安全 → 工程化 TS → Web 高级能力 → 手写源码实战
第一部分 ECMAScript 标准语法(JS 语言核心)
模块 1 基础语法与数据类型
1.1 变量声明与作用域
(1) var / let / const 完整底层区别(面试满分版)
var:函数作用域、存在变量提升、允许重复声明、无暂时性死区、可随意修改、顶层变量挂载 window、易全局污染
let:块级作用域({}/if/for/while)、存在变量提升但有 TDZ 暂时性死区、禁止重复声明、可修改值、不挂载全局 window
const:块级作用域、存在变量提升+TDZ、禁止重复声明、仅锁定变量引用地址(对象/数组内部属性可修改)、声明必须初始化、不挂载全局
(2) 变量提升
底层原理JS 代码执行分两步:编译预解析阶段 + 逐行执行阶段
预解析:扫描当前作用域所有变量/函数声明,提升至作用域顶部,只提升声明,不提升赋值与逻辑
优先级:函数声明提升 > var 变量提升
let/const 存在提升,但被 TDZ 冻结,无法在声明前访问
(3)TDZ 暂时性死区(核心重难点)
定义:let/const 变量已提升,但从「代码块开始」到「变量声明语句执行前」的区间,即为暂时性死区
规则:TDZ 内访问变量直接报错 Cannot access before initialization
作用:彻底解决 var 变量提升带来的脏数据、变量泄露问题
触发场景:块级作用域、函数参数、for 循环、嵌套作用域
(4)JS 四大作用域(全覆盖)
全局作用域:脚本顶层,全局生效,生命周期贯穿页面
函数作用域:仅函数内部生效,var 专属作用域
块级作用域:{} 代码块,let/const/import 专属,隔离性极强
模块作用域:ESM 模块顶层变量,完全隔离,不污染全局
(5)作用域链
底层机制规则:变量查找从当前作用域向上逐层查找,直至全局作用域,找不到则抛出引用错误
核心特性:词法作用域(静态作用域),作用域链在定义时确定,与调用位置无关
单向性:只能向上查找,无法向下查找父作用域变量
(6)for 循环变量经典坑(必考)
var 循环:无块级隔离,全程共用同一个变量,异步回调取值全部为最终值
let 循环:每一轮循环都会生成独立块级作用域,每轮变量相互隔离
(7)严格模式 use strict 变量与作用域约束
禁止未声明变量直接赋值,杜绝全局泄露
禁止删除变量、禁止函数重复形参
禁用 with、禁用 arguments.callee,规范作用域
普通函数 this 指向 undefined,避免全局污染
(8)开发规范:默认 const、可变变量用 let、彻底弃用 var
(9)变量与作用域 全套实战代码(可直接运行)
// ====================== 1. 变量提升 + TDZ 死区实战 ======================
// var 变量提升:声明提升,赋值不提升
console.log(varNum); // undefined
var varNum = 100;
// let/const 存在提升 + TDZ,声明前访问报错
// console.log(letNum); // Uncaught ReferenceError
let letNum = 200;
// const 必须初始化,不允许空声明
// const constNum; // 语法报错
const constNum = 300;
// ====================== 2. 块级作用域隔离实战 ======================
{
var a = 1; // 穿透块级,挂载全局
let b = 2; // 仅块内生效
const c = 3; // 仅块内生效
}
console.log(a); // 1
// console.log(b, c); // 报错:无法访问块级作用域变量
// ====================== 3. const 特性:只锁引用,不锁内容 ======================
const obj = { name: "JS" };
obj.name = "JavaScript"; // ✅ 允许修改属性
obj.age = 20; // ✅ 允许新增属性
// obj = {}; // ❌ 报错:禁止重定向引用地址
const arr = [1,2,3];
arr.push(4); // ✅ 允许修改数组
console.log(arr); // [1,2,3,4]
// ====================== 4. 作用域链查找规则 ======================
let outer = "外层变量";
function fn() {
let inner = "内层变量";
console.log(outer); // 外层变量(向上作用域查找)
function child() {
console.log(inner, outer); // 逐层向上查找
}
child();
}
fn();
// ====================== 5. 经典面试题:for循环 var/let 差异 ======================
// var 版本:所有定时器共享同一个i,循环结束i=3
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log("var循环i:", i); // 输出 3 3 3
}, 0);
}
// let 版本:每轮循环独立块级作用域,变量隔离
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log("let循环i:", i); // 输出 0 1 2
}, 0);
}
// ====================== 6. 严格模式实战 ======================
"use strict";
// test = 1000; // ❌ 报错:未声明变量不能赋值
function strictFn() {
console.log(this); // undefined(严格模式普通函数无全局绑定)
}
strictFn();
(10) 高频易错面试总结(必背)
1. 提升本质区别:var 有提升无死区,let/const 有提升但 TDZ 拦截,杜绝变量泄露;
2. 作用域优先级:模块作用域 > 块级作用域 > 函数作用域 > 全局作用域;
3. const 核心误区:不是常量不可改,是引用地址不可改,引用类型内部可修改;
4. 循环异步坑根源:var 无块级隔离,let 每轮生成独立作用域;
5. 作用域链特性:静态词法作用域,定义时确定,与调用位置无关。
(11) 高频易错面试总结(必背)
1. 变量声明优先级:函数声明提升 > var 变量提升,let/const 存在提升但受 TDZ 限制,声明前访问必报错。
2. TDZ 死区范围:只要进入当前块级作用域,未执行变量声明语句前,均为暂时性死区,与代码位置前后无关。
3. var 循环核心坑:无块级隔离,异步回调始终读取变量最终值,必须用 let/const 替代。
4. const 核心误区:不是完全常量,仅锁定引用地址,引用类型内部属性可正常修改。
5. 作用域核心规则:作用域链为词法静态作用域,定义时确定,与函数调用位置无关,仅向上单向查找。
6. 严格模式核心价值:杜绝全局变量泄露、修复普通函数 this 指向问题,现代项目必须开启。
7. 开发强制规范:废弃 var,默认使用 const,仅变量需要重新赋值时使用 let。
1.2 七大原始类型 + 引用类型
1.2.1 七大原始类型(基本数据类型)
String Number Boolean Null Undefined Symbol BigInt
(1)String 字符串:不可变原始值,所有字符串操作都是返回新字符串,原字符串不变
(2)Number 数字:包含整数、小数、Infinity、NaN;NaN 是唯一不等于自身的值,判断必须用 Number.isNaN
(3)Boolean 布尔值:只有 true / false;0、''、null、undefined、NaN 为假值
(4)Null 空值:代表「空对象」,typeof 判断为 object(JS 历史 bug)
(5)Undefined 未定义:变量声明未赋值、函数无返回值、对象不存在属性默认值
(6)Symbol 唯一标识:ES6 新增,值唯一不重复、不可遍历、可做私有键、解决命名冲突
(7)BigInt 大整数:解决 Number 精度丢失问题,后缀 n 声明,不支持与 Number 混合运算
特殊值考点:0 / -0 正负零,=== 判断相等,Object.is(0, -0) 判断不相等
(8)引用类型(复杂数据类型):Object Array Function Date RegExp
存储机制:栈存地址,堆存真实数据
赋值特点:赋值是地址共享,相互影响
比较特点:引用类型比较内存地址,不比较内容
1.2.2 自动装箱与拆箱(核心底层)
装箱:原始值调用属性/方法时,JS 自动临时创建包装对象(String/Number/Boolean)
拆箱:方法执行完毕,临时对象立即销毁,还原原始值
特点:原始值本身永远不可变
1.2.3 四大类型判断方案 & 优缺点
typeof:快速判断原始类型,无法区分 null、数组、对象
Object.prototype.toString.call():全网最精准类型判断,全覆盖
instanceof:判断引用类型原型关系,无法判断原始类型
constructor:简单判断,可被原型篡改,不稳定
1.2.4 原始类型 + 引用类型 全套实战代码(可直接运行)
// ====================== 1. 七大原始类型特性实战 ======================
// 1. String 不可变性
let str = "hello";
str.toUpperCase();
console.log(str); // hello(原始值不会变,操作返回新值)
// 2. Number 特殊值 NaN / Infinity / ±0
console.log(NaN === NaN); // false
console.log(Number.isNaN(NaN)); // true(正确判断)
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
let zero1 = 0;
let zero2 = -0;
console.log(zero1 === zero2); // true
console.log(Object.is(zero1, zero2)); // false(精准区分正负零)
// 3. Null / Undefined
console.log(typeof null); // object(经典bug)
let a;
console.log(a); // undefined
// 4. Symbol 唯一性
let s1 = Symbol("key");
let s2 = Symbol("key");
console.log(s1 === s2); // false
// Symbol 键不会被 for...in 遍历
let obj = { [s1]: "私有值" };
console.log(obj[s1]); // 私有值
// 5. BigInt 大整数精度
let big = 9007199254740993n;
console.log(big); // 精准大数
// console.log(big + 10); // 报错:不能混合 Number 运算
console.log(big + 10n); // 正常运算
// ====================== 2. 引用类型 地址引用实战 ======================
// 引用赋值:共享地址
let obj1 = { name: "JS" };
let obj2 = obj1;
obj2.name = "JavaScript";
console.log(obj1.name); // JavaScript(互相影响)
// 引用比较:比地址不比内容
let o1 = { a: 1 };
let o2 = { a: 1 };
console.log(o1 === o2); // false(两个不同堆地址)
// ====================== 3. 自动装箱拆箱实战 ======================
let word = "test";
word.length;
// 底层:new String(word).length → 获取后销毁临时对象
console.log(word.length); // 4
// ====================== 4. 四种类型判断完整实战 ======================
// 封装精准类型判断
function getType(v) {
return Object.prototype.toString.call(v).slice(8, -1);
}
console.log(getType("")); // String
console.log(getType(123)); // Number
console.log(getType(null)); // Null
console.log(getType(undefined));// Undefined
console.log(getType(Symbol()));// Symbol
console.log(getType(10n)); // BigInt
console.log(getType([])); // Array
console.log(getType({})); // Object
// typeof 局限性展示
console.log(typeof null); // object
console.log(typeof []); // object
// instanceof 引用类型判断
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
1.2.5 高频面试易错总结
(1). 原始值特点:存栈、不可变、赋值拷贝值,互相不影响;
(2). 引用值特点:栈存地址、堆存数据、赋值拷贝地址、互相影响;
(3). NaN 考点:不自等,必须用 Number.isNaN 判断;
(4). null 考点:typeof 判定为 object,属于 JS 遗留 bug;
(5). Symbol 考点:绝对唯一、不可枚举、适合做私有键;
(6). BigInt 考点:解决大数精度丢失,禁止与 Number 混算;
(7). 精准类型判断:开发/面试统一使用 Object.prototype.toString.call。
1.3 运算符体系(完整版 + 底层规则 + 实战代码)
1.3.1 运算符完整分类
(1)算术运算符:
+ - * / % ++ --特点:除加法外,其余运算会自动将操作数转为数字
++/-- 前置后置差异:前置先自增后取值,后置先取值后自增
特殊:Infinity、NaN 参与运算结果永为非有效数字
(2)赋值运算符:= += -= *= /= %=
(3)比较运算符:
> < >= <= == === != !==== 宽松相等:自动隐式类型转换,只比较值
=== 严格相等:先比较类型,再比较值,开发强制优先使用
(4)传统逻辑运算符:
&& ||短路特性:&& 遇假终止,|| 遇真终止
返回值规则:返回最后执行的表达式原值,不强制返回布尔值
(5)ES6+ 新增核心运算符(高频项目使用)
可选链:?. — 安全访问深层属性,杜绝嵌套报错
空值合并:?? — 仅兜底 null/undefined
逻辑赋值:&&= ||= ??= — 逻辑判断后赋值
指数运算符:** — 幂运算,右结合优先级
(6)位运算符(底层优化常用):
& | ^ ~ << >> >>>所有位运算会自动转 32 位二进制整数
~ 快速取整、^ 异或去重/交换变量、移位运算提速
(7)一元运算符:
+ - ! typeof void delete一元 +:最快隐式转数字方案
void 0:获取纯净可靠的 undefined
(8)逗号运算符:
优先级最低,执行多个表达式,返回最后一个表达式结果
1.3.2 核心重难点 & 易错规则(面试必考)
-
可选链 ?. 禁忌:不可用于赋值
obj?.a = 1语法直接报错 -
?? 与 || 核心区别
||:拦截所有假值(0 '' false null undefined NaN) -
??:只拦截 null / undefined,0、空字符串、false 正常保留 -
指数运算符右结合:
2 ** 2 ** 3 === 2 ** 8从右往左计算 -
逻辑运算符返回值:不返回 true/false,返回实际表达式值
-
宽松相等特殊规则:
null == undefined // true,二者与其他值不相等 -
位运算精度限制:超过 32 位整数数值会失真,不适合超大数运算
1.3.3 运算符优先级极简口诀(高频)
一元运算 > 算术 > 移位 > 比较 > 位运算 > 逻辑 > 赋值 > 逗号
重点:括号优先级最高,复杂运算建议手动加括号,避免歧义
1.3.4 运算符全套实战代码(可直接运行)
// ====================== 1. 算术运算符 & 特殊场景 ======================
console.log(1 + "2"); // "12"(字符串优先拼接)
console.log(1 - "2"); // -1(除+外自动转数字)
console.log(10 / 0); // Infinity
console.log(10 % 0); // NaN
// 前置后置自增
let a = 10;
console.log(a++); // 10 先取值后自增
console.log(a); // 11
let b = 10;
console.log(++b); // 11 先自增后取值
// ====================== 2. 宽松相等 vs 严格相等 ======================
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(1 == "1"); // true
console.log(1 === "1"); // false
// ====================== 3. || 与 ?? 核心区别(业务高频) ======================
let num1 = 0;
console.log(num1 || 100); // 100 0被判定为假值兜底
console.log(num1 ?? 100); // 0 仅null/undefined兜底
let num2 = null;
console.log(num2 || 200); // 200
console.log(num2 ?? 200); // 200
// ====================== 4. 可选链 ?. 安全访问 ======================
let user = null;
console.log(user?.name?.age); // undefined 不报错
// user?.gender = 1; // 语法报错:可选链不能赋值
let info = { name: "JS" };
console.log(info?.name); // JS
// ====================== 5. 逻辑赋值运算符 ======================
let n = 0;
n ||= 10;
console.log(n); // 10
let m = 0;
m ??= 20;
console.log(m); // 0
// ====================== 6. 指数运算符右结合特性 ======================
console.log(2 ** 2 ** 3); // 256 等价于 2 ** 8
console.log((2 ** 2) ** 3); // 64 加括号改变顺序
// ====================== 7. 位运算实战(取整/交换值) ======================
console.log(~~3.99); // 3 快速向下取整
console.log(~~-2.1); // -2
// 异或交换变量(无需临时变量)
let x = 10, y = 20;
x ^= y;
y ^= x;
x ^= y;
console.log(x, y); // 20 10
// ====================== 8. 逗号运算符 ======================
let res = (1 + 2, 3 + 4, 5 + 6);
console.log(res); // 11 返回最后一个表达式结果
// ====================== 9. 一元+ 快速转数字 ======================
console.log(+"123"); // 123
console.log(+true); // 1
console.log(+false); // 0
1.3.5 高频面试错题总结
1. || 会误伤有效假值:0、空字符串、false 会被兜底,数值默认值优先用 ??;
2. 可选链仅可读不可写,不能用于赋值操作;
3. 指数运算符右结合,是 JS 独有特性,容易运算出错;
4. 逻辑运算符返回原值,不是布尔值,可用于变量赋值兜底;
5. 开发规范:比较一律用 ===,杜绝 == 隐式转换带来的 bug;
6. 一元+ 是最轻量、最快的字符串转数字方案。
1.3.5 高频面试错题总结 & 开发规范
1. == 宽松相等高频坑:null 仅与 undefined 相等,与 0、空字符串、false 均不相等,是面试高频判断题考点。
2. || 与 ?? 混用规范:|| 会误拦截 0、空字符串等有效假值,业务兜底优先用 ??,仅需拦截空值;禁止无意义混用。
3. 可选链禁忌:?. 仅用于读取属性,绝对不能用于赋值操作,直接触发语法错误。
4. 指数运算符特殊性:唯一右结合运算符,多层幂运算默认从右向左执行,与常规数学运算顺序不同。
5. 逻辑运算符返回值:不返回布尔值,返回最后执行的原始表达式值,可直接用于变量赋值兜底。
6. 位运算限制:仅支持 32 位整数,超大数值运算会失真,仅用于简单取整、变量交换、性能优化场景。
7. 开发规范:比较判断优先 ===,复杂运算手动加括号明确优先级,避免依赖默认运算顺序引发 bug。
1.4 流程控制(全体系语法 + 底层规则 + 实战代码)
JS 代码执行三大结构:顺序结构、分支结构、循环结构,所有复杂逻辑均基于流程控制实现。
1.4.1 分支流程(条件判断)
(1)if / else if / else 条件分支执行规则:从上至下依次匹配条件,匹配成功立即终止后续判断
支持隐式真假判断:自动将表达式转为布尔值
语法特点:无大括号时,仅紧跟的第一行代码属于语句块(高频bug点)
适用场景:区间判断、多条件复杂逻辑、真假互斥逻辑
(2)switch 条件分支匹配规则:使用 === 严格相等 匹配,不做隐式类型转换
穿透问题:无 break 会触发case穿透,顺延执行后续代码
default:所有case不匹配时执行,位置不影响优先级(规范放末尾)
适用场景:固定等值匹配、多分支等值逻辑,代码比if更简洁
(3)三元表达式 ? :
语法:条件 ? 真值表达式 : 假值表达式
特性:表达式而非语句,可赋值、可嵌套、有返回值
规范:禁止多层嵌套堆砌,仅用于简单二元判断
1.4.2 循环流程(全类型覆盖)
(1)for 基础循环
结构:初始化变量; 循环条件; 迭代更新
执行顺序:初始化 → 条件判断 → 执行循环体 → 迭代更新
优势:可控性强,支持精准控制循环次数、索引、迭代步长
适配场景:已知循环次数、数组下标遍历、精准迭代
(2)while 前置循环
规则:先判断、后执行,条件为真才执行循环体
可能一次都不执行,无初始化语法,自定义迭代变量
(3)do while 后置循环
规则:先执行、后判断,至少执行一次循环体
适用场景:表单校验、弹窗交互、至少执行一次的逻辑
(4)for...in 遍历键名
遍历目标:对象可枚举属性、数组下标(字符串类型)
缺陷:会遍历原型链可枚举属性、遍历顺序不稳定、不适合纯数组遍历
适用场景:纯对象键遍历
(5)for...of 遍历值(ES6 核心)
遍历目标:所有可迭代对象(Array、String、Map、Set、NodeList、arguments)
优势:只遍历自身值、不遍历原型、支持 break/continue、遍历顺序稳定
搭配 entries() 可同时获取索引和值
适用场景:数组、字符串、集合、DOM集合遍历(开发首选)
(6)for await...of 异步遍历(ES2018)
专属场景:遍历异步可迭代对象、批量异步请求串行执行
解决普通循环无法等待 Promise 的问题
1.4.3 循环控制语句
break:终止当前所在循环/switch,跳出循环体
continue:终止本次循环,直接进入下一次迭代
label 标签语句:标记外层循环,配合 break/continue 实现多层循环精准跳出
1.4.4 流程控制全套实战代码(可直接运行)
// ====================== 1. if/else 基础与易错点 ======================
// 无大括号bug:仅第一行生效
if (false)
console.log("执行1");
console.log("执行2"); // 该行不受if控制,一定会执行
// 多条件分支
let score = 85;
if (score >= 90) {
console.log("优秀");
} else if (score >= 60) {
console.log("及格"); // 命中
} else {
console.log("不及格");
}
// ====================== 2. switch 穿透与精准匹配 ======================
let week = 2;
// 基础用法(严格匹配)
switch (week) {
case 1:
console.log("周一");
break;
case 2:
console.log("周二"); // 命中
break;
default:
console.log("未知星期");
}
// 利用case穿透实现多条件匹配
let month = 2;
switch (month) {
case 1:
case 2:
case 3:
console.log("第一季度"); // 命中
break;
default:
console.log("其他季度");
}
// ====================== 3. 三元表达式 ======================
let age = 20;
let isAdult = age >= 18 ? "成年" : "未成年";
console.log(isAdult); // 成年
// ====================== 4. 三大基础循环 ======================
// for 循环
for (let i = 0; i < 3; i++) {
console.log("for循环:", i); // 0 1 2
}
// while 循环
let w = 0;
while (w < 3) {
console.log("while循环:", w);
w++;
}
// do while 至少执行一次
let d = 3;
do {
console.log("doWhile循环:", d); // 3
d++;
} while (d < 3);
// ====================== 5. for...in 遍历对象/数组(避坑) ======================
let obj = { name: "JS", age: 20 };
for (let key in obj) {
console.log("对象键:", key, obj[key]);
}
// 数组for in缺陷:下标为字符串、遍历原型属性
Array.prototype.test = "原型属性";
let arr = [10, 20];
for (let key in arr) {
console.log("forIn数组下标:", key); // 0 1 test(遍历原型)
}
// ====================== 6. for...of 全能遍历(开发首选) ======================
let list = ["A", "B", "C"];
// 直接遍历值
for (let item of list) {
console.log("forOf值:", item);
}
// 搭配entries获取索引+值
for (let [index, item] of list.entries()) {
console.log("索引:", index, "值:", item);
}
// 遍历字符串、Set、Map
let str = "js";
for (let s of str) {
console.log("字符串字符:", s);
}
// ====================== 7. break / continue 控制 ======================
for (let i = 0; i < 5; i++) {
if (i === 2) continue; // 跳过本次,不执行后续代码
if (i === 4) break; // 终止整个循环
console.log("循环控制:", i); // 0 1 3
}
// ====================== 8. label 多层循环跳出(面试高频) ======================
outer: for (let i = 1; i <= 2; i++) {
for (let j = 1; j <= 2; j++) {
if (j === 2) break outer; // 直接跳出外层循环
console.log("多层循环:", i, j); // 1 1
}
}
// ====================== 9. 异步 for await...of 实战 ======================
// 模拟异步任务
const asyncTaskList = [
Promise.resolve("任务1完成"),
Promise.resolve("任务2完成")
];
async function asyncLoop() {
for await (let res of asyncTaskList) {
console.log("异步遍历:", res); // 串行依次执行
}
}
asyncLoop();
1.4.5 高频面试易错 & 开发规范总结
(1). if 无大括号坑点:不写大括号仅单行生效,复杂逻辑必须加代码块;
(2). switch 匹配规则:严格 === 匹配,无隐式转换,不加 break 会穿透;
(3). for in 禁用场景:禁止遍历数组,会遍历原型属性、下标为字符串、顺序混乱;
(4). 循环首选原则:数组/可迭代对象统一用 for...of,仅对象键遍历用 for...in;
(5). do while 特性:唯一至少执行一次的循环,适合初始化校验场景;
(6). 异步遍历方案:普通 for 循环无法等待 Promise,批量异步串行必须用 for await...of;
(7). label 用途:专门解决多层循环无法精准跳出的问题,日常用得少、面试常考。
1.4.5 高频面试易错 & 开发规范总结
1. if 无大括号坑:if/else 不写大括号时,仅紧邻的第一行代码属于语句块,后续代码不受条件控制,极易产生隐性 bug。
2. switch 穿透规则:case 无 break 会默认穿透执行后续代码,可利用该特性实现多条件匹配,常规场景必须加 break。
3. 循环适配场景:已知循环次数用 for、至少执行一次用 do-while、对象键遍历用 for...in、数组/可迭代对象遍历优先用 for...of。
4. for...in 核心缺陷:会遍历原型链可枚举属性、遍历顺序不稳定,禁止用于数组遍历,仅适配纯对象键值遍历。
5. 循环控制规则:continue 仅终止本次循环,break 终止整个循环;多层循环跳出必须搭配 label 标签。
6. 异步遍历规范:普通循环无法等待 Promise,批量异步串行执行必须使用 for await...of。
7. 开发规范:禁止嵌套多层三元表达式、优先使用 for...of 替代 for...in、复杂多条件判断优先 switch 简化代码。
1.5 类型转换底层规则(全网最全 + 底层算法 + 实战代码)
JS 是弱类型动态语言,运算过程中会自动触发隐式类型转换,90% 面试诡异 bug 均来自类型转换底层规则。
类型转换分为:显式转换(手动)、隐式转换(自动),
核心底层依赖三套内置算法:ToNumber、ToString、ToBoolean、ToPrimitive。
1.5.1 四大底层转换算法(ES 规范)
(1)ToBoolean 转布尔值(判定真假)
假值列表(仅 6 个):0、-0、''、null、undefined、NaN → 转为 false
其余所有值均为真值:空数组[]、空对象{}、Infinity、'0'、'false' 全部为 true
适用场景:if 判断、三元表达式、!! 快速布尔化
(2)ToNumber 转数字(核心隐式转换)
undefined → NaN
null → 0
false → 0,true → 1
纯数字字符串 → 对应数字,非纯数字 → NaN
空字符串 '' → 0,空数组 [] → 0
空对象 {} → NaN
(3)ToString 转字符串
null → 'null',undefined → 'undefined'
0/-0 → '0',NaN → 'NaN',Infinity → 'Infinity'
数组:自动扁平化,[1,2] → '1,2'、[] → ''
对象:默认返回 '[object Object]'
(4)ToPrimitive 引用类型转原始值(重中之重)
转换优先级:Symbol.toPrimitive(hint) > valueOf() > toString()
hint 取值:number/string/default,由运算场景自动决定
规则:先执行优先级高的方法,返回原始值则终止,否则继续向下执行
1.5.2 显式类型转换(手动可控)
(1)Number() 强制转数字:最全规则转换,严格解析,容错低
(2)String() 强制转字符串:无丢失精准转换
(3)Boolean() 强制转布尔:严格遵循6大假值规则
(4)快速转换简写:!!转布尔、+转数字、''+转字符串
1.5.3 隐式类型转换触发场景(高频考点)
(1)算术运算:- * / % 强制全体转数字;+ 只要一侧是字符串则拼接
(2)比较运算:> < >= <= == 自动触发隐式转换
(3)逻辑判断:if、while、三元、&& || 自动转布尔
(4)宽松相等 == 专属转换规则(面试核心) 同类型:直接比较值
不同类型:优先转数字比较
特例:null == undefined → true,二者不与任何其他值相等
NaN 与任何值(包括自身)== 结果均为 false
1.5.4 类型转换全套实战代码(可直接运行)
// ====================== 1. ToBoolean 真假值实战 ======================
// 6大假值
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean('')); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined));// false
console.log(Boolean(NaN)); // false
// 高频真值误区
console.log(Boolean('0')); // true(非空字符串)
console.log(Boolean('false')); // true
console.log(Boolean([])); // true(空数组是真值)
console.log(Boolean({})); // true(空对象是真值)
console.log(Boolean(Infinity)); // true
// ====================== 2. ToNumber 转数字实战 ======================
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number('')); // 0
console.log(Number('123')); // 123
console.log(Number('123a')); // NaN
console.log(Number([])); // 0
console.log(Number([123])); // 123
console.log(Number({})); // NaN
// 快速转数字
console.log(+'666'); // 666
// ====================== 3. ToString 转字符串实战 ======================
console.log(String(null)); // 'null'
console.log(String(undefined)); // 'undefined'
console.log(String(NaN)); // 'NaN'
console.log(String(Infinity)); // 'Infinity'
console.log(String([1,2,3])); // '1,2,3'
console.log(String([])); // ''
console.log(String({})); // '[object Object]'
// ====================== 4. 隐式转换 - 加法运算坑点 ======================
console.log(1 + '2'); // '12' 字符串优先拼接
console.log(1 + true); // 2 true转1
console.log(1 + null); // 1 null转0
console.log(1 + undefined); // NaN
// 减法/乘法/除法 强制转数字
console.log('10' - 2); // 8
console.log('10' * '2'); // 20
console.log('10a' - 2); // NaN
// ====================== 5. 宽松相等 == 经典面试题 ======================
console.log(null == undefined); // true
console.log(null == 0); // false
console.log(undefined == 0); // false
console.log(NaN == NaN); // false
// 经典诡异转换
console.log([] == 0); // true:[]→''→0
console.log(![] == 0); // true:![]→false→0
// ====================== 6. ToPrimitive 底层转换劫持实战 ======================
let testObj = {
// 最高优先级
[Symbol.toPrimitive](hint) {
console.log('hint:', hint);
return hint === 'number' ? 100 : 'hello';
},
valueOf() {
return 200;
},
toString() {
return 'world';
}
};
console.log(testObj + 1); // hint:default → 'hello1'
console.log(testObj - 1); // hint:number → 99
console.log(String(testObj));// hint:string → 'hello'
// ====================== 7. 空数组/空对象经典转换坑 ======================
console.log([] == ![]); // true
// 解析:![]=false → 0;[]→''→0 → 0==0 → true
1.5.5 高频面试易错总结(必背)
(1). 假值只有6个:空数组、空对象、字符串'0'、'false' 全部为真值;
(2). null/undefined 特例:仅互相宽松相等,和所有值都不相等;
(3). 加法运算特殊性:唯一会触发字符串拼接的算术运算符,其余运算全部转数字;
(4). 引用类型转换优先级:Symbol.toPrimitive > valueOf > toString; (
5). 空数组转换:数学运算/相等判断中转为 0,极易产生诡异结果;
(6). 开发规范:杜绝 == 宽松相等、杜绝依赖隐式转换,全部使用显式转换 + === 严格相等,规避所有隐性 bug。
1.5.5 高频面试易错总结(必背)
1. 六大假值必记:0、-0、''、null、undefined、NaN,其余所有值(空数组、空对象、'false'等)均为真值。
2. 空值转换核心坑:空数组 [] 转数字为 0,空对象 {} 转数字为 NaN,是类型转换高频考题。
3. 加法运算特殊性:+ 号一侧为字符串则触发拼接,其余算术运算符统一强制转换为数字运算。
4. 宽松相等经典误区:[] == 0、![] == 0 结果为 true,本质是隐式转换逐层拆解为数字对比。
5. ToPrimitive 优先级:Symbol.toPrimitive > valueOf > toString,可手动劫持对象转换规则。
6. 隐式转换核心逻辑:不同类型比较/运算优先转数字,null/undefined 为特殊特例,不参与其他值转换对比。
7. 开发规范:业务代码禁止依赖隐式类型转换,优先使用显式 Number/String/Boolean 转换,规避诡异 bug。
1.6 冷门语句实战(with/label/void)
// 1. label 标签语句(跳出多层循环)
outer: for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
if (j === 2) break outer; // 直接跳出外层循环
console.log(i, j);
}
}
// 2. void 安全获取 undefined
console.log(void 0 === undefined); // true
// 3. with 语句(了解即可,禁止生产使用)
let withObj = { name: "JS", age: 20 };
with(withObj) {
console.log(name, age); // JS 20
}
核心实战总结
1. 变量核心:var可提升无块级作用域,let/const有TDZ、块级作用域,const仅锁引用不锁属性;
2. 类型核心:NaN、±0、Symbol、BigInt为高频易错原始类型,引用类型存地址;
3. 判断核心:精准类型检测优先使用 Object.prototype.toString.call;
4. 转换核心:宽松相等会隐式转换,严格相等 === 先判断类型再判断值,开发优先使用;
5. 运算符核心:??只判空、||判假值、?.安全访问,是项目高频语法。
模块 2 对象、数组、属性描述符
2.1 对象核心 API(全体系底层解析 + 实战代码 + 面试易错)
对象是 JS 核心引用类型,所有普通对象、数组、函数、内置实例均继承自 Object.prototype。对象核心 API 覆盖创建、属性操作、属性描述、对象锁定、遍历、拷贝、原型操作全场景,是原型、响应式、深浅拷贝的底层基础。
2.1.1 对象三种创建方式(优劣对比)
(1)字面量创建(开发首选)
语法:const obj = { key: value }
优势:简洁高效、支持属性简写、计算属性、方法简写
底层:自动继承 Object.prototype
(2)Object 构造函数创建
语法:const obj = new Object()
特点:空对象初始化,功能等价字面量,代码冗余,极少使用
(3)Object.create() 原型定制创建(面试高频)
语法:Object.create(原型对象, 属性描述符)
核心特性:自定义实例原型、可创建纯净空对象(无原型)
场景:实现原型继承、创建无干扰纯净对象、重置原型链
2.1.2 对象属性操作(增删改查 + 解构进阶)
(1)基础增删改查
访问:obj.key(静态属性)、obj[key](动态变量/特殊键)
新增/修改:直接赋值,存在则覆盖,不存在则新增
删除属性:delete obj.key(仅删除自有可枚举属性,无法删除原型属性、const 声明引用)
属性存在判断:key in obj(遍历原型链)、obj.hasOwnProperty(key)(仅判断自有属性)
(2)解构赋值进阶(业务高频)
基础解构、别名解构、默认值、嵌套解构
易错点:默认值仅属性为 undefined 时生效,null/0/'' 不触发默认值
剩余解构 ...rest:收集剩余自有可枚举属性,实现简易浅拷贝
2.1.3 属性分类(底层本质)
(1)自有属性 vs 原型属性
自有属性:对象自身定义的属性,不来自原型链
原型属性:继承自构造函数原型、Object 原型的属性
(2)可枚举 vs 不可枚举属性
可枚举:enumerable: true,可被 for...in、Object.keys 遍历
不可枚举:enumerable: false,内置原型属性大多不可枚举(如 toString、hasOwnProperty)
(3)数据属性 vs 访问器属性
数据属性:value、writable,直接存储值
访问器属性:get、set,无固定值,读写触发函数劫持(响应式核心)
2.1.4 属性描述符(Vue2 响应式底层核心)
通过 Object.defineProperty / defineProperties 精准控制对象属性行为,是 JS 底层元编程核心。
(1)六大描述符配置项
value:属性默认值
writable:是否可修改值(默认 false)
enumerable:是否可枚举遍历(默认 false)
configurable:是否可删除、可修改描述符(默认 false,一旦设为 false 不可逆)
get:读取属性触发的回调,返回属性值
set:修改属性触发的回调,接收新值
(2)硬性规则:数据属性(value/writable)与访问器属性(get/set)互斥,不能同时配置
(3)默认值陷阱:字面量新增属性默认 true,defineProperty 新增属性默认 false
2.1.5 对象锁定三级 API(权限逐级收紧)
三组 API 从宽松到严格,控制对象的增删改权限,底层均通过修改属性描述符实现。
(1)Object.preventExtensions(obj) 禁止拓展
权限:禁止新增属性,可修改、可删除原有属性
检测:Object.isExtensible(obj),返回布尔值
(2)Object.seal(obj) 密封对象
权限:禁止新增、禁止删除属性,可修改原有可写属性值
底层:自动将所有属性 configurable = false
检测:Object.isSealed(obj)
(3)Object.freeze(obj) 冻结对象(最高权限)
权限:禁止新增、删除、修改属性,属性只读
底层:所有属性 configurable = false、writable = false
缺陷:浅冻结,仅冻结顶层属性,嵌套对象仍可修改
检测:Object.isFrozen(obj)
2.1.6 对象全维度遍历方案(精准区分场景)
-
for...in:遍历自身+原型链所有可枚举属性,需搭配
hasOwnProperty过滤原型属性 -
Object.keys(obj):返回自有可枚举属性键数组,无原型、无不可枚举、无 Symbol 键
-
Object.values(obj):返回自有可枚举属性值数组
-
Object.entries(obj):返回自有可枚举 [键, 值] 二维数组,可直接解构遍历
-
Reflect.ownKeys(obj):最全遍历,返回所有自有属性(可枚举+不可枚举+Symbol键),不遍历原型
-
Object.getOwnPropertySymbols(obj):仅获取自有 Symbol 类型键
-
Object.getOwnPropertyNames(obj):获取自有字符串键(可枚举+不可枚举)
2.1.7 对象拷贝方案(深浅拷贝全覆盖)
(1)浅拷贝方案
Object.assign(目标, 源):合并对象、单层浅拷贝,忽略不可枚举、Symbol 属性可拷贝
{...obj} 展开运算符:语法简洁,单层浅拷贝,业务首选
(2)原生深拷贝方案
structuredClone(obj):浏览器/Node 原生标准深拷贝
优势:支持循环引用、Date、RegExp、Map、Set、ArrayBuffer
缺陷:不支持函数、Symbol、undefined
(3)废弃方案
JSON.parse(JSON.stringify),丢失 undefined/函数/Symbol、无法处理循环引用、篡改 Date 格式
Object.getPrototypeOf(obj):获取对象隐式原型 __proto__(标准写法,替代私有属性)
Object.setPrototypeOf(obj, proto):修改对象原型(性能差,不推荐频繁使用)
obj.isPrototypeOf(实例):判断当前对象是否为目标实例的原型
2.1.9 对象核心 API 全套实战代码(可直接运行)
// ====================== 1. 对象三种创建方式 ======================
// 1. 字面量创建
const obj1 = { name: "JS", age: 20 };
// 2. 构造函数创建
const obj2 = new Object();
obj2.name = "JavaScript";
// 3. Object.create 定制原型创建
const parent = { type: "父原型" };
const obj3 = Object.create(parent);
console.log(obj3.type); // 继承原型属性:父原型
const pureObj = Object.create(null); // 纯净无原型对象
console.log(pureObj.__proto__); // undefined
// ====================== 2. 属性增删改查 + 属性判断 ======================
const user = { name: "前端", skill: "JS" };
// 新增属性
user.age = 18;
// 修改属性
user.name = "前端开发";
// 删除属性
delete user.skill;
// in:遍历原型链判断
console.log("toString" in user); // true(继承原型方法)
// hasOwnProperty:仅判断自有属性
console.log(user.hasOwnProperty("toString")); // false
console.log(user.hasOwnProperty("name")); // true
// ====================== 3. 对象解构进阶 ======================
const info = { a: 1, b: undefined };
// 别名 + 默认值 + 嵌套
const { a: num = 0, b = 10, c = 20 } = info;
console.log(num, b, c); // 1 10 20
// 剩余解构浅拷贝
const { ...restInfo } = info;
console.log(restInfo); // { a: 1, b: undefined }
// ====================== 4. 属性描述符实战(get/set 劫持) ======================
const data = {};
let _value = 100;
Object.defineProperty(data, "count", {
// 访问器属性
get() {
console.log("读取count属性");
return _value;
},
set(val) {
console.log("修改count属性", val);
_value = val;
},
enumerable: true,
configurable: true
});
console.log(data.count); // 读取count属性 100
data.count = 200; // 修改count属性 200
// 描述符默认值陷阱
const testObj = {};
Object.defineProperty(testObj, "key", { value: 666 });
// 新增属性默认 enumerable/writable/configurable 均为false
testObj.key = 777; // 修改无效
console.log(testObj.key); // 666
// ====================== 5. 对象三级锁定 ======================
// 1. 禁止拓展
const extObj = { x: 1 };
Object.preventExtensions(extObj);
extObj.y = 2; // 新增无效
console.log(Object.isExtensible(extObj)); // false
// 2. 密封对象
const sealObj = { x: 1 };
Object.seal(sealObj);
delete sealObj.x; // 删除无效
sealObj.x = 2; // 修改有效
console.log(Object.isSealed(sealObj)); // true
// 3. 冻结对象(浅冻结)
const freezeObj = { x: 1, nested: { y: 2 } };
Object.freeze(freezeObj);
freezeObj.x = 999; // 顶层修改无效
freezeObj.nested.y = 999; // 嵌套对象可修改(浅冻结)
console.log(Object.isFrozen(freezeObj)); // true
// ====================== 6. 全维度遍历实战 ======================
const enumObj = { a: 1, b: 2 };
// 添加不可枚举属性
Object.defineProperty(enumObj, "hide", { value: 999, enumerable: false });
// 添加Symbol键
const sym = Symbol("key");
enumObj[sym] = 1000;
console.log(Object.keys(enumObj)); // ['a','b'] 仅自有可枚举字符串键
console.log(Reflect.ownKeys(enumObj)); // ['a','b','hide', Symbol(key)] 全量自有键
console.log(Object.getOwnPropertySymbols(enumObj)); // [Symbol(key)]
// ====================== 7. 对象拷贝实战 ======================
// 浅拷贝
const source = { name: "拷贝", nested: { num: 1 } };
const assignObj = Object.assign({}, source);
const spreadObj = { ...source };
// 原生深拷贝 structuredClone
source.nested.num = 999;
const deepObj = structuredClone(source);
source.nested.num = 1000;
console.log(deepObj.nested.num); // 999 不受原对象影响
// ====================== 8. 原型API实战 ======================
const proto = { type: "原型" };
const ins = Object.create(proto);
console.log(Object.getPrototypeOf(ins) === proto); // true
console.log(proto.isPrototypeOf(ins)); // true
2.1.10 高频面试易错 & 开发规范总结
(1). defineProperty 默认陷阱:手动新增属性三大描述符默认 false,字面量属性默认 true;
(2). 属性互斥规则:数据属性(value/writable)和访问器属性(get/set)不能同时配置;
(3). configurable 不可逆:一旦设为 false,无法再次修改描述符、无法删除属性;
(4). freeze 浅冻结:仅锁定顶层属性,嵌套引用类型仍可修改,深冻结需递归处理;
(5). 遍历范围差异:keys/entries 忽略不可枚举、Symbol、原型属性,Reflect.ownKeys 全覆盖;
(6). 深拷贝选型:优先使用 structuredClone,废弃 JSON 序列化深拷贝,规避数据丢失问题;
(7). 原型判断规范:统一使用 Object.getPrototypeOf,禁止直接操作私有属性 __proto__。
2.2 数组完整体系(全分类API + 底层规则 + 实战代码 + 面试避坑)
数组是 JS 最常用的引用类型,本质是特殊的有序对象(键为数字索引、默认继承 Array.prototype)。
数组 API 严格分为:静态方法、原地修改API、返回新数组API、遍历迭代API、查询判定API,同时包含稀疏数组、类数组转换等高频面试重难点。
2.2.1 数组创建方式 & 底层特性
(1)字面量创建(开发首选)
语法:const arr = [1,2,3]
特性:支持多行书写、空值空位、任意类型混合存储
(2)Array 构造函数创建
new Array(长度):创建稀疏空位数组,无实际元素,仅设置 length
new Array(1,2,3):创建对应元素的数组
易错点:单数字参数仅代表长度,多数字参数代表元素
(3)静态方法创建(精准创建)
Array.of(...args):统一创建数组,解决构造函数单数字歧义,无稀疏空位
Array.from(类数组/可迭代对象):转换类数组、字符串、Set/Map,支持映射回调
2.2.2 数组静态方法(Array 挂载)
(1)Array.of()
作用:基于传入参数创建数组,参数全部作为数组元素
解决问题:new Array(5) 创建稀疏数组的缺陷
示例:Array.of(5) // [5]、new Array(5) // 长度为5的空位数组
(2)Array.from()
语法:Array.from(target, mapFn, thisArg)
支持转换:类数组、字符串、Set、Map、NodeList、arguments
核心优势:原生支持遍历映射,无需额外 map,自动剔除稀疏空位
(3)Array.isArray()
唯一精准判断数组的原生方法,优于 typeof/instanceof
可精准判断跨窗口、跨框架数组实例(instanceof 跨环境失效)
2.2.3 原地修改数组 API(原数组改变,面试高频坑点)
以下方法执行后,原数组直接被修改,开发需谨慎使用,避免副作用
(1)增删首尾元素
push(...items):尾部新增,返回新数组长度
pop():删除尾部最后一个元素,返回删除值
unshift(...items):头部新增,返回新数组长度(性能差,需移动所有元素)
shift():删除头部第一个元素,返回删除值(性能差)
(2)任意位置增删改
splice(start, deleteNum, ...addItems)
全能API:删除、新增、替换元素,返回被删除元素数组
参数规则:start支持负数(倒数位置),deleteNum为0则只新增不删除
(3)排序与反转
sort(fn):原地排序,默认按字符串Unicode排序,数字必须传比较函数
reverse():原地反转数组顺序,返回反转后数组
(4)填充与去空
fill(value, start, end):批量填充数组,常用于初始化定长数组
copyWithin(target, start, end):数组内部元素覆盖复制,原地修改
2.2.4 返回新数组 API(原数组不变,无副作用,开发首选)
所有方法均返回全新数组,不修改原数据,符合函数式编程思想,适合业务开发、状态更新
(1)映射转换
map((item, index, arr) => 新值):一对一映射,返回等长度新数组
特性:遍历所有有效元素,跳过稀疏空位,保留空位结构
(2)过滤筛选
filter((item, index, arr) => 布尔值):筛选符合条件元素,返回新数组
特性:自动剔除稀疏空位、假值元素,数组长度可变
(3)拼接与截取
concat(...arr):数组合并,支持多参数拼接,浅拷贝合并
slice(start, end):截取数组片段,左闭右开,支持负数索引,可实现数组浅拷贝
(4)扁平化处理
flat(depth):数组扁平化,默认1层,Infinity 无限扁平化,自动剔除空位
flatMap():先map映射,再flat(1)扁平化,适合二维数组一键处理
2.2.5 遍历、查询、判定 API(返回非数组值)
(1)遍历迭代
forEach():纯遍历,无返回值,无法break/continue,跳过空位
entries()/keys()/values():返回迭代器,可for...of遍历索引/值
(2)条件判定(返回布尔)
every():所有元素满足条件返回true,遇假终止遍历
some():任意一个元素满足条件返回true,遇真终止遍历
(3)元素查找
find():查找第一个符合条件的元素值,无则undefined
findIndex():查找第一个符合条件的索引,无则-1
findLast()/findLastIndex():从后往前查找(ES2022)
indexOf()/lastIndexOf():严格匹配(===)查找索引,无则-1,不识别NaN
includes():判断元素是否存在,可识别NaN,返回布尔值
(4)累计迭代
reduce((prev, curr, index, arr) => 新累计值, 初始值):全能累计器
reduceRight():从右往左累计
场景:求和、去重、统计、数组转对象、复杂数据聚合
2.2.6 稀疏数组核心考点(面试必考)
稀疏数组:数组存在空位 empty,无实际元素、无索引值,由new Array(长度) 创建
(1)空位特性:空位不等于 undefined,空位无任何数据
(2)各API对空位处理规则跳过空位:forEach、map、every、some、indexOf、lastIndexOf
(3)视为undefined:includes、find、findIndex、reduce
(4)剔除空位:filter、flat、Array.from、扩展运算符
(5)开发规范:禁止使用稀疏数组,创建数组优先 Array.of/Array.from
2.2.7 类数组转换方案(DOM/arguments 高频)
类数组特征:含 length 属性、数字索引、无数组原型方法,
常见:arguments、NodeList、DOM集合、字符串
(1)Array.from(类数组):最优方案,支持映射,自动去空位
(2)[...类数组]:扩展运算符,简洁高效,仅限可迭代对象
(3)Array.prototype.slice.call(类数组):兼容低版本浏览器经典方案
2.2.8 数组全套实战代码(可直接运行)
// ====================== 1. 数组创建 & 静态方法 ======================
// 构造函数歧义
const arr1 = new Array(5); // [empty × 5] 稀疏数组
const arr2 = new Array(1,2,3); // [1,2,3]
// Array.of 解决歧义
const arr3 = Array.of(5); // [5]
const arr4 = Array.of(1,2,3); // [1,2,3]
// Array.from 转换类数组/可迭代对象
const strArr = Array.from("js"); // ['j','s']
const setArr = Array.from(new Set([1,2,2,3])); // [1,2,3] 去重
// 自带映射功能
const mapArr = Array.from([1,2,3], item => item * 2); // [2,4,6]
// 精准判断数组
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
// ====================== 2. 原地修改API实战 ======================
let arr = [1,2,3];
// 首尾增删
arr.push(4); // [1,2,3,4] 返回长度4
arr.pop(); // [1,2,3] 返回4
arr.unshift(0); // [0,1,2,3] 返回长度4
arr.shift(); // [1,2,3] 返回0
// splice 增删改全能
let spliceArr = [10,20,30];
spliceArr.splice(1,1); // 删除索引1的1个元素 => [10,30]
spliceArr.splice(1,0,20); // 索引1位置新增20 => [10,20,30]
spliceArr.splice(0,1,99); // 替换索引0元素 => [99,20,30]
// sort 排序陷阱
let numArr = [10,2,30];
console.log(numArr.sort()); // [10,2,30] 默认字符串排序
console.log(numArr.sort((a,b) => a - b)); // [2,10,30] 数字升序
console.log(numArr.sort((a,b) => b - a)); // [30,10,2] 数字降序
// fill 填充初始化
let initArr = new Array(3).fill(0); // [0,0,0]
// ====================== 3. 无副作用新数组API实战 ======================
const origin = [1,2,3];
// map 映射
const mapRes = origin.map(item => item + 1); // [2,3,4]
// filter 筛选
const filterRes = origin.filter(item => item > 1); // [2,3]
// slice 截取+浅拷贝
const sliceRes = origin.slice(1,3); // [2,3]
const copyArr = origin.slice(); // 纯浅拷贝
// concat 合并
const concatRes = origin.concat([4,5], [6]); // [1,2,3,4,5,6]
// flat 扁平化
const flatArr = [1,[2,[3,[4]]]];
console.log(flatArr.flat()); // [1,2,3,[4]] 扁平化1层
console.log(flatArr.flat(Infinity)); // [1,2,3,4] 无限扁平化
// flatMap 先映射再扁平化
const flatMapArr = [1,2,3];
const flatMapRes = flatMapArr.flatMap(item => [item, item*2]);
console.log(flatMapRes); // [1,2,2,4,3,6]
// ====================== 4. 查询 & 判定API实战 ======================
const testArr = [1,2,3,NaN];
// includes 可识别NaN
console.log(testArr.includes(NaN)); // true
console.log(testArr.indexOf(NaN)); // -1 无法识别
// find / findIndex
console.log(testArr.find(item => item > 2)); // 3
console.log(testArr.findIndex(item => item > 2)); // 2
// every / some
console.log(testArr.every(item => typeof item === "number")); // true
console.log(testArr.some(item => item === NaN)); // true
// reduce 全能累计
// 求和
const sum = [1,2,3].reduce((pre, cur) => pre + cur, 0); // 6
// 数组去重
const unique = [1,2,2,3,3,3].reduce((pre, cur) => {
!pre.includes(cur) && pre.push(cur);
return pre;
}, []);
console.log(unique); // [1,2,3]
// ====================== 5. 稀疏数组空位实战 ======================
const emptyArr = [1,,3]; // 存在空位
console.log(emptyArr.map(item => item * 2)); // [2, empty, 6] 跳过空位
console.log(emptyArr.filter(item => true)); // [1,3] 剔除空位
console.log(emptyArr.includes(undefined)); // true 空位视为undefined
// ====================== 6. 类数组转换实战 ======================
// 模拟类数组
const arrLike = { 0: "a", 1: "b", length: 2 };
// 三种转换方案
const trans1 = Array.from(arrLike);
const trans2 = Array.prototype.slice.call(arrLike);
console.log(trans1, trans2); // ['a','b']
2.2.9 高频面试易错 & 开发规范总结
1. 数组变异方法坑点:push/pop/shift/unshift/splice/sort/reverse 会直接修改原数组,业务开发需谨慎使用,避免污染原数据。
2. sort 默认排序缺陷:默认按字符串 Unicode 排序,数字排序必须传入回调函数(a-b 升序、b-a 降序)。
3. 数组查询方法差异:indexOf 无法识别 NaN,includes 可精准识别 NaN,判断数组包含值优先使用 includes。
4. 稀疏数组特性:map 会跳过空位、filter 会剔除空位、includes 将空位视为 undefined,遍历稀疏数组需单独处理空位。
5. reduce 全能考点:必须设置初始值,否则空数组调用 reduce 会直接报错,是面试高频易错点。
6. 类数组转换规范:DOM 集合、arguments 类数组遍历,优先使用 Array.from 转换,简洁且支持第二个参数映射处理。
7. 数组遍历选型规范:单纯遍历用 forEach、映射改值用 map、筛选数据用 filter、精准查询用 find、逻辑判断用 every/some。
8. 性能优化:大数据量遍历优先用原生 for 循环,高阶数组方法可读性高但性能略低,按需选用。
- 原地修改禁忌:Vue/React 状态数组禁止直接使用 splice/push/sort 等原地API,会导致响应式异常、视图不更新,优先用 map/filter/扩展运算符返回新数组;
- sort 排序坑:数字排序必须传入
a-b / b-a比较函数,默认Unicode排序会出错; - NaN 判断差异:indexOf 无法识别 NaN,查询 NaN 优先使用 includes/find;
- 稀疏数组规避:业务代码杜绝 new Array(长度),一律用 Array.of/Array.from 初始化;
- forEach 局限性:不支持中断、无返回值,需要遍历终止优先用 for 循环/for...of;
- flatMap 场景:一维转二维、批量展开数组,替代先map再flat的冗余写法;
- reduce 万能性:所有数组聚合、转换、统计场景均可通过 reduce 实现,是面试手写高频API。
模块 3 函数、闭包、this、高阶函数(核心重难点)
函数是 JS 一等公民,是异步编程、闭包、高阶函数、组件封装的核心基础,本模块全覆盖函数分类、参数规则、this 终极指向、作用域与闭包底层、高阶函数实战,解决 90% 函数相关面试与业务难点。
3.1 函数全分类 & 底层差异
JS 函数根据声明方式、特性、用途分为 8 大类,不同函数的 this 指向、变量提升、执行规则完全不同,是底层核心区分点。
3.1.1 各类函数特性详解
(1) 函数声明
语法:function fn() {}
核心特性:存在函数提升,优先级高于 var 变量提升,可在声明前调用;拥有独立 this、arguments、prototype 原型;可作为构造函数 new 实例。
(2) 函数表达式
语法:const fn = function() {}
核心特性:本质是变量赋值,遵循变量提升规则(let/const 存在 TDZ),声明前无法调用;无单独提升优先级,开发中最常用,稳定性更高。
(3) IIFE 立即执行函数
语法:(function(){})() / (function(){}())
核心特性:定义即执行、执行后立即销毁;创建独立私有作用域,隔离全局变量污染;ES6 块级作用域普及前,是模块化隔离的核心方案。
(4) 箭头函数(ES6 核心)
语法:() => {}
核心特性:无独立 this、无 arguments、无 prototype 原型、不能被 new、无 constructor、无法作为生成器;this 继承外层词法作用域,永久固定无法修改。
(5) 生成器函数 Generator
语法:function* fn() { yield }
核心特性:分段执行、可暂停可恢复;解决异步回调地狱,是早期异步迭代核心方案;配合 next()/throw()/return() 控制执行流程。
(6) 异步函数 Async
语法:async function fn() { await }
核心特性:Promise 语法糖,将异步逻辑扁平化;返回值自动包装为 Promise 实例;await 阻塞当前函数执行,不阻塞主线程。
(7) 构造函数
语法:首字母大写 function Person() {}
核心特性:配合 new 关键字生成实例;内部 this 指向实例;挂载原型方法实现复用;无 return 或 return 原始值则返回实例,return 引用值则替换实例。
(8) 对象/类访问器方法
语法:get/set 访问器函数
核心特性:读取/修改属性自动触发;用于数据劫持、响应式监听;无独立调用时机,依附于属性操作。
3.2 函数参数全体系(底层规则 + 易错点)
函数参数分为形参、实参、默认参数、剩余参数,同时存在参数作用域、解构失效、arguments 伪数组等高频考点。
3.2.1 核心参数类型
(1) 默认参数
语法:function fn(a = 10) {}
核心规则:默认参数拥有独立局部作用域,作用域优先级高于函数体;仅实参为 undefined 时触发默认值,null/0/'' 等假值不触发。
(2) 剩余参数 ...rest
语法:function fn(...rest) {}
核心规则:收集所有剩余实参,返回真实数组;必须放在参数最后一位;与 arguments 不可混用,优先使用 rest(数组可直接调用方法)。
(3) arguments 伪数组
特性:仅普通函数拥有,箭头函数无;存储所有传入实参;是类数组对象,无数组原生方法;严格模式下禁止修改、禁止 callee 调用。
3.2.2 参数解构与失效场景(高频易错)
1. 参数解构默认值仅在参数为 undefined 时生效,传 null、空值均不触发;
2. 函数参数解构优先级高于函数体内部赋值,参数作用域独立;
3. 剩余参数与解构搭配时,仅能收集剩余参数,无法过滤指定参数;
4. 多个默认参数存在时,参数作用域相互独立,可相互引用前置参数。
3.3 箭头函数终极限制(面试必背)
箭头函数是 JS 最特殊的函数,无五大核心能力,所有限制均源于「无独立执行上下文」:
1. 无独立 this:永久继承外层词法作用域 this,call/apply/bind 无法修改;
2. 无 arguments 对象:无法通过 arguments 获取参数,只能用剩余参数替代;
3. 无 prototype 原型:无法挂载原型方法,不能作为构造函数;
4. 不能 new 实例:无 this 指向、无 constructor,new 直接报错;
5. 无法 yield 暂停:不能作为 Generator 生成器函数;
6. 无 super 关键字:无法用于 class 继承场景。
3.4 this 全场景指向(终极核心难点,100%考点)
this 指向由调用方式决定(箭头函数除外),与定义位置无关,全网最全 6 大场景全覆盖,无遗漏。
3.4.1 普通函数 this 指向规则
(1) 全局独立调用
非严格模式:this 指向 window 全局对象;
严格模式:this 指向 undefined(杜绝全局污染)。
(2) 对象方法调用
规则:谁调用,this 指向谁;
示例:obj.fn() 中 fn 内部 this 指向 obj;
易错点:解构赋值提取对象方法、单独赋值调用,this 会丢失,变为全局/undefined。
(3) 构造函数 new 调用
new 四步底层:创建空实例 → 绑定原型 → this 指向当前实例 → 执行代码 → 返回实例;
核心:构造函数内部 this 永远指向新创建的实例对象。
(4) call / apply / bind 手动绑定
三者均可手动修改函数 this 指向,第一个参数为绑定的 this 对象;
差异:call/apply 立即执行函数,传参格式不同;bind 返回新函数,延迟执行;
终极规则:多次 bind 绑定,仅第一次绑定生效,后续绑定无效。
(5) 定时器 / DOM 事件回调
setTimeout/setInterval 普通回调:this 默认指向 window;
DOM 事件绑定:this 指向触发事件的 DOM 元素。
(6) 箭头函数 this(唯一特殊规则)
无独立 this,继承外层最近的词法作用域 this;
this 指向在定义时固定,调用方式、bind/call/apply 均无法修改;
完美解决定时器、回调函数 this 丢失问题。
3.4.2 this 丢失高频场景与解决方案
1. 对象方法解构赋值调用:const fn = obj.fn; fn() → this 丢失;
2. 定时器嵌套普通函数:内层 this 指向 window,非实例/对象;
3. class 原型方法单独调用:丢失实例 this;
解决方案:箭头函数绑定、bind 预绑定、保存外层 this 变量。
3.5 作用域、执行上下文、闭包(JS 底层灵魂)
3.5.1 三层作用域体系
作用域:变量的可访问范围,词法作用域在定义时确定,与调用位置无关。
1. 全局作用域:页面/模块顶层,全局生效,生命周期贯穿程序运行;
2. 函数作用域:函数内部专属,var 变量专属作用域,调用创建、执行销毁;
3. 块级作用域:{}/if/for/while 代码块,let/const/import 专属,隔离性最强。
3.5.2 执行上下文底层机制
JS 代码执行依托执行上下文栈,分为两种核心上下文:
1. 全局执行上下文:程序启动自动创建,唯一存在,栈底常驻;
2. 函数执行上下文:函数调用时创建,执行完毕出栈销毁;
核心组成:变量对象 VO、活动对象 AO、词法环境、作用域链、this 指向。
作用域链:变量查找的层级链路,从当前作用域向上逐层查找,直至全局,单向不可逆。
3.5.3 闭包(面试最高频·满分完整版)
1. 闭包权威定义:在 JavaScript 中,内层函数引用外层函数的局部变量/参数,且外层函数执行结束后,内层函数被外部作用域持有,使得外层函数的词法作用域不会被销毁、变量常驻内存,这种函数嵌套+作用域保留的组合形态,即为闭包。
2. 闭包必备三大形成条件(缺一不可)
(1). 函数嵌套:存在内层函数与外层函数的层级结构;
(2). 变量引用:内层函数主动引用外层函数的局部变量、参数或函数;
(3). 外部持有:内层函数脱离外层函数作用域,被全局变量、定时器、事件回调等外部场景持有,阻止作用域销毁。
3. 闭包底层核心原理
JS 执行机制中,普通函数执行完毕后,其函数执行上下文会立即出栈销毁,内部局部变量随之被 GC 垃圾回收。 而闭包的核心是:内层函数的词法环境永久绑定外层函数的词法作用域,只要内层函数引用不被释放,外层作用域的变量就会被永久保留,不会被垃圾回收,打破了「函数执行完毕即销毁」的默认机制。
4. 闭包核心特性(面试必背)
(1)变量私有化、隔离全局污染:外层函数变量仅能被闭包函数访问,全局作用域无法直接读写,完美实现变量封装,杜绝全局变量冲突。
(2)变量常驻内存:函数执行销毁,引用的变量永久驻留内存,可实现数据缓存、状态持久化。
(3)永久保留词法作用域:闭包的作用域链在定义时固定,与调用位置、调用时机无关,永久绑定外层定义环境。
(4)参数/变量复用:可缓存函数初始参数、中间结果,避免重复传参、重复计算。
5. 高频实战应用场景(业务+面试全覆盖)
(1)封装私有变量/私有方法:JS 原生无真正私有变量,依靠闭包实现模块化私有成员,仅暴露指定公共方法。
(2)高阶工具函数底层:防抖、节流、函数柯里化、记忆函数,均依靠闭包缓存定时器、状态、计算结果。
(3)循环缓存变量:解决 var 循环定时器变量穿透问题,实现每轮循环变量独立隔离。
(4)模块化开发:ES6 模块普及前,IIFE+闭包是前端模块化的核心方案,隔离模块内部变量。
(5)状态持久化:计数器、表单状态缓存、接口请求缓存,无需全局变量即可保留状态。
(6)事件回调缓存数据:异步回调、DOM 事件监听中,保留定义时的变量状态。
6. 闭包优缺点(面试简答题高频)
优点:
(1). 实现变量私有化,避免全局污染;
(2). 缓存数据,提升代码执行性能;
(3). 保留词法作用域,解决异步变量丢失问题;
(4). 支持函数式编程核心能力(柯里化、偏函数)。
缺点:
(1). 内存泄漏风险:常驻变量无法自动回收,长期大量使用会导致页面内存占用过高、卡顿;
(2). 过度使用会造成代码嵌套复杂,可读性、可维护性降低;
(3). 无法直接访问私有变量,调试排查问题成本更高。
7. 内存泄漏优化方案(工程必备)
(1). 手动释放引用:闭包使用完毕后,将闭包函数、缓存变量手动置空(赋值 null),解除引用,触发 GC 回收;
(2). 避免全局常驻闭包:禁止在全局作用域长期持有大量闭包引用;
(3). 及时解绑事件/定时器:闭包绑定的事件监听、定时器,组件销毁/逻辑结束后立即清除;
(4). WeakMap/WeakSet 优化缓存:存储闭包缓存数据,依靠弱引用特性自动回收无用数据。
8. 经典面试真题+解析(必考)
真题1:var 循环定时器输出 3 3 3 的原因?如何解决?
解析:var 无块级作用域,循环全程共用同一个 i 变量,定时器异步执行时循环已结束,i 最终为 3;
解决方案:① 用 let 声明循环变量(块级作用域+闭包);② 自定义闭包包裹每轮循环。
真题2:闭包会不会造成内存泄漏?
解析:正常临时使用的闭包不会泄漏,长期无用的闭包引用未释放才会造成内存泄漏,并非闭包本身的问题,而是引用未销毁导致。
真题3:如何用闭包实现计数器?优势是什么?
解析:外层函数定义 count 私有变量,内层函数实现加减逻辑并返回,全局无法直接修改 count;
优势:状态私有、无全局污染、状态持久化。
9. 极简闭包代码示例(可直接运行)
// 1. 基础闭包:私有变量封装
function createCounter() {
let count = 0; // 闭包缓存私有变量
// 内层函数引用外层变量,形成闭包
return {
add: () => ++count,
reduce: () => --count,
getVal: () => count
};
}
const counter = createCounter();
console.log(counter.add()); // 1
console.log(counter.add()); // 2
console.log(counter.getVal()); // 2
// console.log(counter.count); // undefined 私有变量无法直接访问
// 2. 循环闭包经典问题与解决
// 旧方案:IIFE 闭包隔离
for (var i = 0; i < 3; i++) {
(function(i) {
setTimeout(() => {
console.log("IIFE闭包i:", i); // 0 1 2
}, 0);
})(i);
}
// 新方案:let 块级作用域(本质也是闭包隔离)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log("let闭包i:", i); // 0 1 2
}, 0);
}
// 3. 闭包内存释放优化
function closeClosure() {
counter.add = null; // 解除闭包引用
}
closeClosure();
3.6 高阶函数全体系(业务+面试实战)
高阶函数定义:接收函数作为参数、或返回新函数的函数,是函数式编程核心。
3.6.1 基础高阶函数(原生内置)
数组遍历高阶函数:map/filter/reduce/every/some/find 均为高阶函数;
核心特点:接收回调函数,封装迭代逻辑,简化代码、解耦逻辑。
3.6.2 五大核心高阶工具函数
(1) 函数柯里化
定义:将多参数函数,拆分为多个单参数嵌套函数,逐次接收参数、延迟执行;
作用:参数复用、逻辑缓存、函数解耦;
场景:正则校验、表单校验、接口参数封装。
(2) 偏函数
定义:固定函数部分参数,生成新的定制化函数;
与柯里化区别:偏函数固定部分参数,柯里化逐次接收所有参数。
(3) 记忆函数 memoize
定义:缓存函数执行结果,相同参数直接返回缓存值,避免重复计算;
场景:递归计算、大数据处理、接口缓存、防抖底层。
(4) 函数组合 compose/pipe
compose:从右往左执行多个函数,函数嵌套执行扁平化;
pipe:从左往右执行,顺序更符合直觉;
场景:数据链式处理、中间件机制、状态管理。
(5) 防抖 & 节流(业务高频)
防抖:触发事件后延迟执行,期间重复触发重置延迟,只执行最后一次;
场景:搜索输入、窗口缩放、表单输入校验;
节流:固定时间内只执行一次,限制执行频率;
场景:滚动监听、窗口拖拽、频繁点击、上传进度监听。
3.7 本模块全套可运行实战代码
// ====================== 1. 函数分类与核心特性 ======================
// 1. 函数提升(函数声明优先于变量)
console.log(declFn()); // 声明函数可前置调用
function declFn() { return "函数声明"; }
// 2. 函数表达式无提升
// console.log(exprFn()); // 报错 TDZ
const exprFn = () => "函数表达式";
// 3. IIFE 独立作用域
(function() {
const num = 10; // 私有变量,外部无法访问
console.log("IIFE 立即执行", num);
})();
// ====================== 2. 函数参数与解构规则 ======================
// 默认参数独立作用域、仅undefined生效
function paramFn(a = 10, b = a + 5) {
console.log(a, b);
}
paramFn(); // 10 15
paramFn(0); // 0 5(假值不触发默认)
// 剩余参数替代arguments
function restFn(...rest) {
console.log(rest); // 纯数组 [1,2,3]
}
restFn(1,2,3);
// 参数解构默认值失效场景
function objFn({ val = 20 } = {}) {
console.log(val);
}
objFn({ val: null }); // null 不触发默认值
objFn(); // 20 触发默认
// ====================== 3. this 全场景实战 ======================
// 1. 全局调用
function globalFn() {
console.log(this);
}
globalFn(); // window
"use strict";
function strictGlobalFn() {
console.log(this);
}
strictGlobalFn(); // undefined
// 2. 对象方法调用
const thisObj = {
name: "对象",
fn() {
console.log(this.name);
}
};
thisObj.fn(); // 对象
// this丢失场景
const loseThis = thisObj.fn;
loseThis(); // 全局undefined
// 3. 箭头函数this继承外层
const arrowThisObj = {
name: "箭头",
fn() {
setTimeout(() => {
console.log(this.name); // 继承fn的this = arrowThisObj
}, 0);
}
};
arrowThisObj.fn(); // 箭头
// 4. bind/call/apply绑定
function bindFn() {
console.log(this.name);
}
bindFn.call({ name: "call绑定" }); // call绑定
const newBindFn = bindFn.bind({ name: "bind绑定" });
newBindFn(); // bind绑定
// ====================== 4. 闭包实战 ======================
// 私有变量封装
function createCount() {
let count = 0; // 闭包缓存变量
return {
add() { count++; return count; },
get() { return count; }
};
}
const counter = createCount();
console.log(counter.add()); // 1
console.log(counter.get()); // 2
// count 私有,外部无法直接访问
// 循环闭包经典问题(let块级作用域解决)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log("闭包循环i:", i); // 0 1 2 独立作用域
}, 0);
}
// ====================== 5. 高阶函数实战 ======================
// 1. 函数柯里化
function curry(fn) {
return function curried(...args) {
if(args.length >= fn.length) return fn(...args);
return (...newArgs) => curried(...args, ...newArgs);
};
}
const add = (a,b) => a + b;
const curryAdd = curry(add);
console.log(curryAdd(1)(2)); // 3
// 2. 记忆函数
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = args.join(",");
if(cache.has(key)) return cache.get(key);
const res = fn(...args);
cache.set(key, res);
return res;
};
}
const memoAdd = memoize(add);
console.log(memoAdd(2,3)); // 5 计算
console.log(memoAdd(2,3)); // 5 缓存读取
// 3. 函数组合 compose
function compose(...fns) {
return (...args) => fns.reduceRight((res, fn) => fn(res), ...args);
}
const fn1 = x => x + 1;
const fn2 = x => x * 2;
console.log(compose(fn1, fn2)(2)); // 5 (2*2+1)
// 4. 防抖实战
function debounce(fn, delay = 300) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 5. 节流实战
function throttle(fn, delay = 300) {
let flag = false;
return function(...args) {
if(flag) return;
flag = true;
setTimeout(() => {
fn.apply(this, args);
flag = false;
}, delay);
};
}
3.8 高频面试易错 & 开发规范总结
(1). 函数声明与表达式核心差异:仅函数声明存在变量提升,可前置调用;函数表达式、箭头函数受 TDZ 限制,声明前调用直接报错。
(2). 箭头函数六大禁忌:不能作为构造函数、无 this/arguments/prototype/constructor、无法使用 yield/super,场景误用必出 bug。
(3). this 指向终极口诀:普通函数看调用方式、对象方法看调用主体、箭头函数看外层词法作用域、bind 仅第一次绑定生效。
(4). 参数默认值坑点:默认值仅在实参为 undefined 时生效,null、0、空字符串等假值不会触发默认赋值,参数拥有独立作用域。
(5). 闭包核心利弊:可实现变量私有化、状态缓存,但长期未释放的闭包会导致内存泄漏,使用后需手动清空引用。
(6). 高阶函数规范:防抖节流必须用于高频触发事件(输入、滚动、点击),柯里化/记忆函数适合参数复用、重复计算场景。
(7). this 丢失高频场景:对象方法解构赋值、定时器回调、class 原型方法单独调用,优先用箭头函数或 bind 预绑定修复。
(8).开发编码规范:常量函数用 const、动态逻辑用普通函数、简短回调用箭头函数,杜绝混合滥用,保证代码可读性。
模块 4 ES6+ 全特性(完整版·语法+底层+实战+面试)
ES6+ 是现代 JS 开发的核心标准,涵盖语法糖、数据结构、迭代协议、模块化、类、异步语法等全套能力,彻底解决 ES5 语法冗余、作用域混乱、无模块化、数据结构单一等问题。本模块全覆盖ES6~ES2025高频特性,配套实战代码、底层原理与面试易错点,适配开发与面试场景。
4.1 核心语法糖(开发高频必用)
4.1.1 模板字符串 ``
语法:反引号包裹字符串,支持换行、变量插值、表达式运算
核心优势:彻底摒弃字符串拼接 +,支持原生换行、嵌入变量/三元运算/函数调用
底层原理:解析时提取 ${} 内部表达式执行,将结果与静态字符串拼接为新字符串
标签模板(高级用法):可对模板字符串进行二次加工,实现XSS过滤、国际化、数值格式化
// 基础用法:换行+变量插值
const name = "ES6";
const str = `学习${name}全特性
支持原生换行
表达式运算:${10 + 20}`;
console.log(str);
// 标签模板实战(XSS过滤)
function safeHtml(strings, ...values) {
return strings.reduce((res, str, index) => {
const val = values[index] || "";
// 转义特殊字符,防止XSS攻击
const safeVal = val.replace(/</g, "<").replace(/>/g, ">");
return res + str + safeVal;
}, "");
}
const html = safeHtml`<div>${"<script>恶意代码</script>"}</div>`;
console.log(html); // 自动转义特殊字符
面试易错点:模板字符串永远返回新字符串,不可变;标签模板第一个参数为静态字符串数组,第二个及以后为插值变量。
4.1.2 解构赋值(数组+对象+嵌套)
解构是ES6核心语法,快速从数组、对象中提取值,支持默认值、重命名、嵌套解构、剩余解构,大幅简化赋值代码。
核心规则:
1. 默认值仅在对应值为 undefined 时生效,null/0/"" 不触发;
2. 对象解构无序,数组解构有序;
3. 解构属于浅解构,仅拷贝一层引用类型;
4. 剩余解构 ... 必须放在解构最后一位。
// 1. 数组解构
const arr = [10, 20, 30];
const [a, b, c] = arr;
// 跳过占位、默认值、剩余解构
const [x, , z, d = 40, ...rest] = [10, 20, 30];
console.log(x, z, d, rest); // 10 30 40 []
// 2. 对象解构(重命名+默认值)
const obj = { name: "JS", age: 20 };
const { name: title, age, sex = "男" } = obj;
console.log(title, age, sex);
// 3. 嵌套解构
const nested = { a: 1, b: { c: 2 } };
const { b: { c } } = nested;
console.log(c); // 2
// 4. 函数参数解构(业务高频)
function getUserInfo({ name = "未知", age = 0 } = {}) {
console.log(name, age);
}
getUserInfo();
4.1.3 展开/剩余运算符 ...
两种形态:
1. 剩余运算符:...变量,收集剩余值(解构、函数参数);
2. 展开运算符:...可迭代对象,展开数组、对象、字符串、Set/Map。
核心特性:浅拷贝、支持合并数据、快速转换可迭代对象。
// 1. 数组展开与合并
const arr1 = [1, 2], arr2 = [3, 4];
const newArr = [...arr1, ...arr2]; // [1,2,3,4]
// 2. 对象展开(合并、覆盖属性)
const obj1 = { name: "JS" };
const obj2 = { age: 20, ...obj1 };
// 3. 剩余参数替代arguments
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
// 4. 字符串展开
console.log([...'hello']); // ['h','e','l','l','o']
4.1.4 对象简写语法
键名与变量名一致时可简写,函数可省略 function 关键字,简化对象定义。
const name = "ES6";
// 完整简写
const obj = {
name, // 等价于 name: name
// 方法简写
say() {
console.log("简写方法");
},
// 动态属性名
[`key_${name}`]: "动态值"
};
console.log(obj);
4.2 全新数据结构(ES6核心拓展)
4.2.1 Set / WeakSet 集合
Set:无序、唯一、可迭代的类数组集合,自动去重,支持任意类型值。
核心API:add/delete/has/clear/size、keys/values/entries
WeakSet:弱引用Set,仅存储对象、不可迭代、无size、自动GC回收,适合临时存储DOM节点、实例对象。
// Set 数组去重
const arr = [1, 2, 2, 3, 3, NaN, NaN];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1,2,3,NaN] 可去重NaN
// WeakSet 弱引用特性
const ws = new WeakSet();
let dom = document.querySelector("div");
ws.add(dom);
dom = null; // 解除引用,WeakSet中对应值自动被GC回收
4.2.2 Map / WeakMap 键值对集合
Map:升级版对象,支持任意类型键名(对象/数组/基本类型)、有序、可迭代、键值唯一。
对比普通Object:Object键只能是字符串/Symbol,Map键无限制、遍历有序、可快速获取长度。
WeakMap:弱引用Map,键必须是对象、不可迭代、无size、自动GC,适合存储对象私有数据、缓存临时状态。
// Map 任意类型键
const map = new Map();
const keyObj = { id: 1 };
map.set(keyObj, "对象键值");
console.log(map.get(keyObj));
// WeakMap 私有数据存储
const wm = new WeakMap();
function Person() {}
const p = new Person();
wm.set(p, "私有数据"); // 实例销毁,私有数据自动回收
4.2.3 四大数据结构核心区别(面试必背)
1. Set/Map:强引用、可迭代、有size、支持清空删除,适合常规去重、键值存储;
2. WeakSet/WeakMap:弱引用、不可迭代、无size、自动GC,适合内存优化、临时缓存;
3. Weak系列键/值限制:WeakSet仅存对象,WeakMap键必须是对象。
4.3 迭代器与遍历协议(Iterator)
核心定义:Iterator 是JS统一的可迭代协议,所有部署了 Symbol.iterator 方法的对象,均可被 for...of 遍历。
原生可迭代对象:Array、String、Map、Set、arguments、NodeList、TypedArray
迭代器核心规则:调用 Symbol.iterator 返回迭代器对象,迭代器拥有 next() 方法,返回 {value, done},done为true时遍历结束。
// 1. 手动实现迭代器
const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // {value:10, done:false}
console.log(iterator.next()); // {value:20, done:false}
console.log(iterator.next()); // {value:30, done:false}
console.log(iterator.next()); // {value:undefined, done:true}
// 2. 自定义可迭代对象
const myIter = {
list: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
return index < this.list.length
? { value: this.list[index++], done: false }
: { value: undefined, done: true }
}
}
}
};
// 支持for...of遍历
for (const item of myIter) {
console.log(item);
}
4.4 Generator 生成器函数
定义:function* 声明的分段执行函数,通过 yield 暂停、next() 恢复执行,是异步编程的早期核心方案。
核心特性:
1. 函数执行可暂停、可恢复,保留执行上下文;
2. yield 产出值,next() 传入参数,实现数据双向传递;
3. yield* 委托迭代,嵌套执行生成器;
4. 拥有 return()/throw() 手动终止/抛出异常。
// 基础生成器
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next().value); // 1
console.log(g.next().value); // 2
// yield* 委托迭代
function* child() {
yield 10;
yield 20;
}
function* parent() {
yield* child(); // 委托子生成器
yield 30;
}
[...parent()]; // [10,20,30]
4.5 Class 类与继承(ES6面向对象)
ES6 Class 是原型继承的语法糖,简化构造函数、原型、继承写法,支持私有属性、静态属性、装饰器等高级能力。
4.5.1 基础语法与核心特性
1. 类无变量提升,必须先定义后使用;
2. constructor 构造函数,new实例时自动执行;
3. 方法默认挂载原型,不重复创建,节省内存;
4. 支持get/set访问器、静态属性、私有属性。
4.5.2 继承与高级特性
extends 继承:实现子类继承父类原型与属性
super 关键字:子类构造函数必须优先调用super(),继承父类实例属性;可调用父类原型方法
私有字段 #:ES2022新增,#变量/#方法 实现真正私有,外部无法访问
静态块 static {}:ES2022新增,类初始化时自动执行,用于初始化静态属性
// 完整Class实战
class Person {
// 私有属性
#password = "123456";
// 静态属性
static type = "人类";
// 静态代码块
static {
console.log("类初始化完成");
}
constructor(name, age) {
this.name = name;
this.age = age;
}
// 原型方法
sayHi() {
console.log(`我是${this.name}`);
}
// 访问器
get userInfo() {
return `${this.name}-${this.age}`;
}
}
// 子类继承
class Student extends Person {
constructor(name, age, score) {
super(name, age); // 继承父类属性
this.score = score;
}
// 重写父类方法
sayHi() {
super.sayHi(); // 调用父类方法
console.log(`分数:${this.score}`);
}
}
const s = new Student("小明", 18, 90);
s.sayHi();
console.log(Student.type);
4.6 ESM 模块化体系(现代前端标准)
ESM 是浏览器+Node通用的标准模块化方案,彻底解决全局变量污染、模块依赖混乱问题,是现代项目默认模块化规范。
4.6.1 导出规范
1. 命名导出:export const a = 1、export {a,b},支持多个导出;
2. 默认导出:export default 变量,每个模块仅一个默认导出;
3. 混合导出:同时存在命名导出+默认导出。
4.6.2 导入规范
1. 命名导入:import {a,b} from "./xxx",支持重命名 import {a as newA};
2. 默认导入:import xxx from "./xxx";
3. 整体导入:import * as all from "./xxx";
4. 按需导入:import("./xxx") 动态导入,返回Promise,支持按需加载、路由懒加载。
4.6.3 顶级能力
1. 顶层await:无需包裹async函数,直接使用 await 等待模块/接口请求;
2. import.meta:获取模块元信息(路径、url等);
3. 模块作用域:变量仅模块内生效,不污染全局。
4.7 字符串/数值/数组拓展API
4.7.1 字符串新增方法
判定类:includes()包含、startsWith()开头、endsWith()结尾
填充类:padStart()头部填充、padEnd()尾部填充(时间、编号格式化高频)
清理类:trimStart()/trimEnd() 精准去除首尾空格
重复:repeat(n) 重复字符串n次
4.7.2 数值与Math拓展
数值判定:Number.isInteger()是否整数、Number.isFinite()是否有限数
Math工具:Math.trunc() 去除小数、Math.sign() 判断正负、Math.hypot() 勾股定理
4.7.3 数组拓展(前文已覆盖核心,补充ES6+新增)
Array.from() 类数组转真数组、支持映射;Array.of() 精准创建数组(解决new Array缺陷)
4.8 ES6+ 高频面试易错总结
1. 解构默认值坑点:仅undefined触发默认值,null、空字符串、0均不生效,函数解构务必加空对象兜底 {} = {};
2. Weak系列特性:弱引用不阻塞GC,无遍历、无size,适合内存优化,面试常考与强引用区别;
3. Iterator核心:for...of本质是调用迭代器next方法,仅可迭代对象支持,普通对象需手动部署迭代协议;
4. Class关键误区:Class无变量提升、方法不可枚举、私有属性#外部绝对无法访问,区别于自定义模拟私有变量;
5. ESM与CommonJS核心差异:ESM静态编译、支持tree-shaking、顶层await、模块作用域;CJS运行时加载、无tree-shaking;
6. Generator特性:分段执行、保留上下文,是async/await底层原理,可解决异步回调地狱;
7. 开发规范:优先使用解构、展开语法简化代码;高频去重优先Set;模块化统一使用ESM规范。
模块 5 错误处理体系(完整底层+实战+工程规范+面试)
JS 错误处理是工程稳定性的核心,可规避线上崩溃、白屏、逻辑中断问题。本模块覆盖内置错误类型、同步/异步错误捕获、自定义错误、全局异常监听、错误堆栈分析、工程落地规范,补齐 JS 错误处理所有盲区,适配业务开发、异常监控、面试场景。
5.1 JS 七大内置错误类型(原生规范)
JS 内置 7 大错误类,所有运行时报错均为对应错误类的实例,可精准区分错误场景,便于针对性捕获与上报。
5.1.1 基础父类 Error
所有错误的基类,其他错误类型均继承自 Error。核心属性:message(错误信息)、stack(错误堆栈)、name(错误类型名),是错误定位、日志上报的核心依据。
5.1.2 六大细分运行时错误
(1) SyntaxError 语法错误:代码书写不符合 JS 语法规范,编译阶段直接报错,无法被 try/catch 捕获。常见场景:括号不匹配、关键字拼写错误、缺少分号、解构语法非法。
(2) ReferenceError 引用错误:访问未声明的变量、函数、对象属性,或访问 TDZ 暂时性死区的变量。高频场景:变量名拼写错误、作用域访问越界、let/const 变量声明前访问。
(3) TypeError 类型错误:变量类型与操作不匹配,是项目中最高频错误。常见场景:调用非函数、对只读属性赋值、基本类型调用不存在方法、参数类型不合法。
(4) RangeError 范围错误:数值超出合法取值范围。常见场景:数组长度设为负数/超大数、递归死循环、数字精度溢出、定时器参数非法。
(5) URIError URI 解析错误:URI 编码/解码参数不合法。常见场景:decodeURI、encodeURI、decodeURIComponent 传入非法字符串。
(6) AggregateError 批量错误(ES2021):聚合多个错误,用于批量异步任务报错场景。专属场景:Promise.any 全部失败时抛出,可一次性收集所有子错误信息。
5.2 同步错误捕获:try / catch / finally(核心)
唯一同步代码错误捕获方案,可捕获运行时错误,无法捕获语法错误、异步错误。
5.2.1 语法与执行规则
1. try:包裹可能报错的风险代码,出错后立即终止 try 内后续代码执行;
2. catch:捕获错误,接收错误对象,可自定义错误处理逻辑;
3. finally:无论是否报错,一定会执行,常用于资源释放、loading 关闭、状态重置;
4. 支持嵌套 try/catch,内层报错优先被内层捕获,未捕获则冒泡至外层。
5.2.2 关键禁忌与坑点
1. 仅捕获运行时错误,编译阶段语法错误直接阻断程序执行;
2. 无法捕获定时器、Promise、异步回调等异步代码错误;
3. catch 必须兜底,禁止空 catch(隐藏错误,导致线上 bug 无法排查);
4. finally 内 return 会覆盖 try/catch 的返回值,禁止在 finally 中写返回逻辑。
5.3 异步错误全套捕获方案(工程高频)
异步错误无法被同步 try/catch 捕获,需适配不同异步场景使用专属捕获方案。
5.3.1 Promise 错误捕获
1. 单个 Promise:优先使用 .catch() 链式捕获,错误会向上冒泡,可捕获当前 Promise 及内部所有子错误;
2. Promise 静态方法:all/race/allSettled/any 错误单独处理,allSettled 可规避批量任务单个失败导致整体崩溃;
3. 禁止 Promise 裸写(无 catch),会触发全局未捕获错误。
5.3.2 async/await 异步错误捕获
async/await 是 Promise 语法糖,可直接使用 try/catch 捕获异步错误,是现代项目首选方案,代码扁平化、可读性强。
5.3.3 定时器/事件回调错误
setTimeout、setInterval、DOM 事件回调内的错误,无法被外层 try/catch 捕获,需在回调内部单独 try/catch 兜底。
5.4 自定义错误类(工程封装必备)
原生错误信息过于笼统,业务中需自定义错误,区分参数错误、接口错误、权限错误、业务逻辑错误,便于精准排查、日志分类、前端弹窗提示。支持继承 Error 基类,扩展错误码、错误类型、请求信息等自定义字段。
5.5 全局异常监听(兜底容错)
用于捕获未被局部捕获的全局错误,实现项目兜底容错、线上错误监控上报,杜绝页面整体崩溃。区分浏览器与 Node 双环境。
5.5.1 浏览器端全局监听
1. window.onerror:捕获全局同步运行时错误、脚本加载错误,可获取错误信息、文件地址、行号列号;
2. window.unhandledrejection:捕获所有未被 catch 的 Promise 异步错误,是异步全局兜底核心;
3. 可结合错误上报接口,自动收集线上错误日志。
5.5.2 Node.js 全局监听
1. process.on('uncaughtException'):捕获同步未捕获错误,需手动兜底进程状态;
2. process.on('unhandledRejection'):捕获未处理的 Promise 异步错误;
3. 生产环境禁止直接退出进程,需容错重启、日志记录。
5.6 错误堆栈分析与排查技巧
1. error.stack:错误堆栈信息,精准定位报错代码行、调用链路,是排查线上 bug 核心依据;
2. 生产环境配合 SourceMap,可解析压缩混淆代码,还原源码报错位置;
3. 优先捕获精准错误类型,避免笼统捕获,提升排查效率。
5.7 全套可运行实战代码
// ====================== 1. 七大内置错误实战 ======================
// 1. ReferenceError 引用错误
try {
console.log(undefinedVar); // 未声明变量
} catch (e) {
console.log("引用错误:", e.name, e.message);
}
// 2. TypeError 类型错误
try {
const num = 123;
num.toUpperCase(); // 数字无此方法
} catch (e) {
console.log("类型错误:", e.name, e.message);
}
// 3. RangeError 范围错误
try {
new Array(-10); // 非法数组长度
} catch (e) {
console.log("范围错误:", e.name, e.message);
}
// 4. AggregateError 批量错误
const p1 = Promise.reject("任务1失败");
const p2 = Promise.reject("任务2失败");
try {
await Promise.any([p1, p2]);
} catch (e) {
console.log("批量错误:", e.errors); // 收集所有子错误
}
// ====================== 2. try/catch/finally 完整实战 ======================
function syncErrorTest() {
let res = "默认值";
try {
res = 100 + undef;
} catch (e) {
console.log("捕获同步错误:", e.message);
res = "异常兜底值";
} finally {
console.log("执行finally:状态重置、关闭loading");
}
return res;
}
console.log(syncErrorTest());
// ====================== 3. 异步错误捕获:async/await + try/catch ======================
async function asyncErrorTest() {
try {
// 模拟接口请求失败
const res = await Promise.reject("接口请求超时");
console.log(res);
} catch (e) {
console.log("捕获异步错误:", e);
// 业务兜底:重试、弹窗提示、日志上报
}
}
asyncErrorTest();
// ====================== 4. Promise 错误捕获 .catch 冒泡 ======================
Promise.resolve()
.then(() => {
throw new Error("链式逻辑报错");
})
.catch((e) => {
console.log("捕获链式错误:", e.message);
});
// ====================== 5. 自定义错误类封装(工程通用) ======================
class BusinessError extends Error {
constructor(message, code, type) {
super(message);
this.name = "BusinessError"; // 自定义错误名
this.code = code; // 错误码
this.type = type; // 错误类型
Error.captureStackTrace(this, this.constructor); // 保留完整堆栈
}
}
// 业务抛出自定义错误
function loginCheck(age) {
if (!age) {
throw new BusinessError("年龄不能为空", 10001, "参数错误");
}
if (age < 18) {
throw new BusinessError("未成年禁止登录", 10002, "权限错误");
}
}
try {
loginCheck(16);
} catch (e) {
console.log("自定义错误:", e.code, e.type, e.message);
}
// ====================== 6. 浏览器全局错误兜底 ======================
// 全局同步错误
window.onerror = function (msg, url, line, col, error) {
console.log("全局同步错误捕获:", { msg, url, line, col, stack: error?.stack });
// 此处可对接错误上报接口
return true; // 阻止浏览器默认报错
};
// 全局异步Promise错误
window.addEventListener("unhandledrejection", (e) => {
console.log("全局异步错误捕获:", e.reason);
e.preventDefault(); // 阻止默认控制台报错
});
// ====================== 7. 定时器异步错误兜底(必须内部捕获) ======================
setTimeout(() => {
try {
console.log(timerVar);
} catch (e) {
console.log("定时器错误捕获:", e.message);
}
}, 0);
5.8 高频面试易错 & 工程开发规范(必背)
1. 错误捕获核心边界:try/catch 仅捕获同步运行时错误,语法错误、异步错误(定时器/Promise)无法被外层捕获,异步需专属方案兜底。
2. Promise 错误特性:Promise 内部错误不会阻断主线程,无 catch 会触发全局未捕获错误,项目中所有 Promise 必须加 catch 或全局兜底。
3. 空 catch 禁忌:禁止空 try/catch {},会隐藏线上隐性 bug,必须补充错误日志、兜底逻辑或上报。
4. finally 核心规则:无论报错与否必执行,禁止在 finally 中使用 return、throw,会覆盖上层执行结果。
5. 自定义错误价值:原生错误无法区分业务场景,工程中必须封装自定义错误,实现错误分类、精准排查、分级上报。
6. 全局兜底规范:线上项目必须配置 onerror、unhandledrejection 全局监听,杜绝单点错误导致页面白屏、崩溃。
7. AggregateError 考点:ES2021 新增批量错误,仅由 Promise.any 全部失败时抛出,可聚合多个错误信息,区别于普通 Error。
8. 错误排查核心:优先读取 error.stack 堆栈信息,结合 SourceMap 定位源码,而非仅依赖 message 信息。
第二部分 异步编程 & Event Loop(JS 核心灵魂)
模块 1 Promise 完整规范(ES6 标准·底层原理·全API·手写源码·面试满分版)
Promise 是 ES6 规范标准化的异步解决方案,彻底解决回调地狱问题,是 async/await、异步并发、事件循环的底层核心。本模块覆盖规范定义、状态机制、核心API、链式原理、错误机制、手写源码、高频面试坑,无死角补齐 Promise 所有知识点。
1.1 Promise 核心规范定义(ES 标准)
1.1.1 三大不可逆状态(核心基石)
Promise 实例仅有三种状态,状态一旦变更永久不可逆,这是 Promise 所有特性的底层前提:
-
pending 等待态:初始状态,创建 Promise 后默认状态,未成功也未失败,可切换为 fulfilled/rejected
-
fulfilled 成功态:异步操作执行成功,状态永久锁定,不可再变更,存储成功结果值
-
rejected 失败态:异步操作执行失败,状态永久锁定,不可再变更,存储失败原因
规范硬性规则:Promise 状态只能从 pending → fulfilled 或 pending → rejected,成功/失败后,后续所有回调固定读取当前状态结果,不会二次变更。
1.1.2 执行机制核心规则
1. 同步执行器:Promise 传入的 executor 执行器函数 (resolve, reject) => {},创建即同步立即执行,不属于异步任务;
2. 回调异步延迟:.then/.catch/.finally 回调属于微任务,会放入微任务队列,等待同步代码执行完毕后执行;
3. 单状态触发:resolve、reject 仅第一次调用生效,后续调用直接忽略,保证状态唯一性;
4. 值穿透机制:若 Promise 已敲定(fulfilled/rejected),后续绑定的 then/catch 会直接执行,无需等待。
1.2 核心实例方法(全特性+底层规则)
1.2.1 then() 核心链式方法
语法:promise.then(onFulfilled, onRejected)
参数规则:
-
onFulfilled:成功回调,状态为 fulfilled 时触发,接收成功 value;可缺省
-
onRejected:失败回调,状态为 rejected 时触发,接收失败 reason;可缺省
链式调用底层原理(面试高频):
then 方法永远返回全新 Promise 实例,而非原实例,因此支持无限链式调用。返回值规则:
-
回调返回普通值(原始值/对象):新 Promise 状态为 fulfilled,值为返回值;
-
回调返回新 Promise:外层 Promise 状态跟随内层 Promise 状态;
-
回调抛出异常:新 Promise 状态为 rejected,捕获异常信息;
-
回调无返回值:默认返回 undefined,新 Promise 为成功态。
1.2.2 catch() 错误捕获方法
语法:promise.catch(onRejected)
本质:then(null, onRejected) 的语法糖,专门捕获失败状态与链式错误。
错误冒泡核心机制(必考):
Promise 链式调用中,错误会向上冒泡穿透所有未捕获节点,直至遇到最近的 catch 终止;若全程无 catch,会触发全局 unhandledRejection 错误。
关键特性:catch 执行完毕后,返回的 Promise 默认是成功态,可继续链式调用 then,不会中断链路。
1.2.3 finally() 最终执行方法(ES2018)
语法:promise.finally(callback)
核心规则:
-
无论 Promise 成功/失败,回调必然执行一次,用于资源释放、loading 关闭、状态重置;
-
无参数:无法接收成功值/失败原因,不区分执行状态;
-
不穿透结果:默认继承上一层 Promise 的状态与结果,回调返回 Promise 会等待执行完毕;
-
回调内部抛出异常,会将状态改为 rejected,抛出新错误。
1.3 六大静态方法(全场景覆盖+优先级规则)
1.3.1 Promise.resolve() 快速创建成功态
作用:快速返回一个 fulfilled 状态的 Promise 实例。
特殊规则(重难点):
-
传入普通值:返回成功态 Promise,值为入参;
-
传入Promise 实例:直接返回原实例,不创建新实例;
-
传入thenable 对象(含 then 方法):会解析该对象,跟随其最终状态。
1.3.2 Promise.reject() 快速创建失败态
作用:快速返回一个 rejected 状态的 Promise 实例。
唯一规则:直接原样返回失败原因,不解析 thenable 对象、不跟随状态,与 resolve 完全不同。
1.3.3 Promise.all() 并行全部成功
规则:接收 Promise 可迭代数组,所有子 Promise 成功才成功,一个失败直接失败(短路报错)。
-
成功返回:结果有序数组,顺序与入参数组顺序完全一致(即使异步执行快慢不同);
-
失败返回:第一个失败的子 Promise 错误信息,立即终止执行;
-
入参非 Promise:自动包裹为成功态 Promise。
适用场景:多个接口并行请求,需全部成功才执行后续逻辑。
1.3.4 Promise.race() 竞速执行
规则:谁先敲定(成功/失败)谁生效,其余未完成的 Promise 会继续执行,但结果被丢弃。
适用场景:接口超时拦截、竞速请求(请求成功 / 超时报错)。
1.3.5 Promise.allSettled() 全部完成(ES2020)
规则:等待所有子 Promise 全部执行完毕,无论成功失败,永不报错。
返回值格式:对象数组,每一项包含 status(fulfilled/rejected) + 对应 value/reason。
适用场景:批量任务执行,需统计全部成功/失败结果,不允许单个任务失败导致整体崩溃。
1.3.6 Promise.any() 容错成功(ES2021)
规则:一个成功即成功,全部失败才失败,与 all 完全相反。
-
成功返回:第一个成功的 Promise 结果;
-
失败返回:AggregateError 批量错误实例,聚合所有子任务错误信息;
适用场景:多节点备用请求、容错请求(只要一个成功即可)。
1.4 高频核心易错坑点(面试必背)
1. 执行顺序坑:Promise 执行器同步执行,then/catch 是微任务,永远晚于同步代码,早于宏任务;
2. 状态锁定坑:resolve/reject 仅第一次生效,后续调用无效,状态不可逆;
3. 错误冒泡坑:链式调用错误会逐层穿透,直至 catch,无 catch 会触发全局报错;catch 后链路恢复正常成功态;
4. finally 穿透坑:finally 不改变原有 Promise 结果,仅回调异常会覆盖状态;
5. all 有序坑:all 结果数组有序,与执行快慢无关,严格匹配入参顺序;
6. race 残留坑:race 胜出后,剩余 Promise 不会终止,仍会后台执行,仅结果被丢弃;
7.resolve 解析坑:resolve 会解析 thenable 对象,reject 直接原样抛出,不做解析;
8. 空 Promise 坑:裸写 Promise 无 resolve/reject 会永久 pending,导致链路阻塞。
1.5 可直接运行全套实战代码
// ====================== 1. 基础状态与执行顺序实战 ======================
console.log("同步代码优先执行");
const p1 = new Promise((resolve, reject) => {
console.log("Promise执行器同步执行");
resolve("成功结果"); // 首次生效
reject("失败结果"); // 二次调用无效,状态不改变
});
// then微任务异步执行
p1.then(res => console.log("then接收:", res));
console.log("同步代码结束");
// ====================== 2. 链式调用与错误冒泡 ======================
new Promise((resolve) => resolve(10))
.then(res => {
console.log("第一层:", res);
return res * 2; // 返回普通值,下一层成功
})
.then(res => {
console.log("第二层:", res);
throw new Error("链式报错"); // 抛出异常,触发失败态
})
.then(res => console.log("不会执行", res)) // 跳过
.catch(err => console.log("捕获链式错误:", err.message))
.then(() => console.log("catch后链路恢复正常")); // 正常执行
// ====================== 3. finally 特性实战 ======================
const p2 = Promise.resolve("接口数据");
p2.finally(() => {
console.log("关闭loading,重置状态");
// finally无参数,无法获取结果
}).then(res => console.log("继承结果:", res));
// ====================== 4. 六大静态方法全实战 ======================
// 1. Promise.all 全部成功
const taskAll = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
Promise.all(taskAll).then(res => console.log("all结果:", res)); // [1,2,3]
// 2. Promise.race 竞速(超时拦截)
const reqTask = new Promise(resolve => setTimeout(() => resolve("请求成功"), 2000));
const timeoutTask = new Promise((_, reject) => setTimeout(() => reject("请求超时"), 1000));
Promise.race([reqTask, timeoutTask]).catch(err => console.log("race结果:", err));
// 3. Promise.allSettled 全部完成
const taskSettle = [Promise.resolve("成功1"), Promise.reject("失败1"), Promise.resolve("成功2")];
Promise.allSettled(taskSettle).then(res => console.log("allSettled结果:", res));
// 4. Promise.any 容错执行
const taskAny = [Promise.reject("失败1"), Promise.resolve("优先成功"), Promise.reject("失败2")];
Promise.any(taskAny).then(res => console.log("any成功结果:", res));
// 全部失败触发AggregateError
const allFail = [Promise.reject(1), Promise.reject(2)];
Promise.any(allFail).catch(err => console.log("any批量错误:", err.errors));
// ====================== 5. resolve/reject 特殊规则 ======================
// resolve解析thenable对象
const thenable = { then(resolve) { resolve("thenable解析成功") } };
Promise.resolve(thenable).then(res => console.log("resolve解析:", res));
// reject不解析,直接返回
Promise.reject(thenable).catch(err => console.log("reject原样返回:", err));
1.6 极简手写 Promise(符合 ES 规范·面试满分版)
// 极简符合规范的Promise手写实现
class MyPromise {
// 三种状态
static PENDING = "pending";
static FULFILLED = "fulfilled";
static REJECTED = "rejected";
constructor(executor) {
this.status = MyPromise.PENDING; // 初始状态
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
// 成功处理函数
const resolve = (value) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(cb => cb());
}
};
// 失败处理函数
const reject = (reason) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(cb => cb());
}
};
// 捕获执行器异常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then核心链式方法
then(onFulfilled, onRejected) {
// 参数兜底,实现值穿透
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : val => val;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
// 返回新Promise实现链式调用
return new MyPromise((resolve, reject) => {
// 已成功状态
if (this.status === MyPromise.FULFILLED) {
setTimeout(() => {
try {
const res = onFulfilled(this.value);
resolve(res);
} catch (err) {
reject(err);
}
});
}
// 已失败状态
if (this.status === MyPromise.REJECTED) {
setTimeout(() => {
try {
const res = onRejected(this.reason);
resolve(res);
} catch (err) {
reject(err);
}
});
}
// 等待态,存入回调队列
if (this.status === MyPromise.PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const res = onFulfilled(this.value);
resolve(res);
} catch (err) {
reject(err);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const res = onRejected(this.reason);
resolve(res);
} catch (err) {
reject(err);
}
});
});
}
});
}
// catch方法
catch(onRejected) {
return this.then(null, onRejected);
}
// finally方法
finally(callback) {
return this.then(
val => MyPromise.resolve(callback()).then(() => val),
err => MyPromise.resolve(callback()).then(() => { throw err })
);
}
// 静态resolve
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
// 静态reject
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
// 静态all
static all(promiseList) {
return new MyPromise((resolve, reject) => {
const result = [];
let count = 0;
const len = promiseList.length;
if (len === 0) resolve([]);
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(res => {
result[i] = res;
count++;
if (count === len) resolve(result);
}).catch(err => reject(err));
}
});
}
// 静态race
static race(promiseList) {
return new MyPromise((resolve, reject) => {
for (let p of promiseList) {
MyPromise.resolve(p).then(resolve, reject);
}
});
}
}
1.7 面试真题总结(高频必刷)
(1). Promise 状态特点:三种状态不可逆,pending 可切换、成功失败后锁定,仅首次 resolve/reject 生效;
(2). 链式调用原理:then 每次返回新 Promise,通过解析回调返回值确定新状态,实现无限链式;
(3). 错误冒泡机制:链式错误逐层穿透,最近 catch 捕获,catch 后链路恢复成功态;
(4). all/race/allSettled/any 差异:all 全成成功、race 竞速、allSettled 全完成、any 一成成功;
(5). 微任务优先级:Promise 回调属于微任务,执行顺序优于宏任务,晚于同步代码;
(6). finally 核心考点:无参数、不改变原有结果、异常才覆盖状态,多用于通用收尾逻辑;
(7). 手写 Promise 核心:状态锁定、回调队列存储、异步延迟执行、链式返回新实例、参数兜底容错。
模块 2 async / await(异步终极方案·底层原理·全场景实战)
async/await 是 ES2017 推出的Promise 语法糖,彻底解决 Promise 链式调用嵌套冗余问题,将异步代码伪同步化,是现代 JS 异步编程的首选方案。底层完全基于 Promise + 微任务机制实现,不改变 JS 单线程异步本质,仅优化代码写法与可读性。
2.1 核心语法与基础特性
2.1.1 async 修饰符(函数标识)
async 用于修饰普通函数/箭头函数/类方法,被修饰的函数具备两大核心特性:
(1)返回值自动包装 Promise:无论函数内部返回何种值,最终结果一定是 Promise 实(2)例返回普通值(原始值/对象):自动包裹为 fulfilled 成功态 Promise
(3)返回Promise 实例:直接复用原 Promise 状态与结果
(4)函数内部抛出异常:自动包裹为rejected 失败态 Promise
async 函数内部可使用 await 关键字,普通函数无法使用
2.1.2 await 等待关键字(核心阻塞)
await 必须存在于 async 函数内部,顶级作用域、普通函数内直接使用会报语法错误。
核心作用:暂停当前 async 函数的执行,等待右侧 Promise 状态敲定,再恢复执行。
等待规则:
(1)右侧为 Promise 实例:等待其状态变更,成功返回 value,失败抛出 reason
(2)右侧为 普通值:自动包裹为成功态 Promise,直接返回原值,无等待延迟
(3)右侧为 thenable 对象:自动解析 thenable 状态,等待其执行完毕
2.2 底层执行机制(面试核心)
async/await 并非真正同步阻塞,而是语法层面的异步暂停机制,底层依托事件循环微任务队列实现:
(1)执行到 await 时:暂停当前 async 函数执行上下文,让出主线程,主线程继续执行外部同步代码
(2)Promise 状态未敲定:函数上下文挂起,存入等待队列
(3)Promise 状态敲定:将后续代码封装为微任务,推入微任务队列
同步代码执行完毕、清空所有微任务后,恢复 async 函数后续执行
(4)核心结论:await 仅阻塞当前 async 函数内部代码,不阻塞主线程、不阻塞其他异步任务。
2.3 错误处理全套方案(工程必备)
await 等待的 Promise 抛出失败时,会直接终止当前函数执行并抛出异常,必须手动捕获处理,否则程序报错。
2.3.1 核心捕获方案
(1)try/catch 全局捕获:包裹所有 await 逻辑,统一处理批量异步错误(最常用)
(2)单个 await 链式 catch:针对单个异步任务单独兜底,精准捕获单个错误
(3)全局异常监听:兜底未捕获的 async 函数错误,防止线上崩溃
2.3.2 错误处理规范
禁止裸写 await(无任何错误兜底);
批量异步优先 try/catch,单个异步优先 .catch() 兜底,兼顾精准性与代码简洁度。
2.4 异步执行顺序:串行、并行、并发控制
2.4.1 串行执行(默认特性)
多个 await 自上而下依次执行,前一个任务完成才执行下一个,适用于有依赖的异步任务(如:登录→获取用户信息→请求列表)。
2.4.2 并行执行(提速关键)
无依赖的异步任务,禁止串行 await,需通过 Promise.all 实现并行执行,大幅缩短耗时。核心:先批量创建 Promise,再统一 await 等待。
2.4.3 批量异步容错执行
多任务并行无需全部成功时,使用 Promise.allSettled 搭配 await,避免单个任务失败导致整体终止。
2.5 异步遍历:for await...of(专属场景)
普通 for/for...of 无法等待异步任务,ES2018 推出 for await...of,专门用于遍历异步可迭代对象、串行执行批量异步任务,完美解决循环内异步执行顺序混乱问题。
适用场景:批量异步接口串行请求、异步文件逐行读取、异步数据流遍历。
2.6 高频易错坑点(面试/开发必避)
-
语法禁忌:await 不能脱离 async 函数,顶级作用域、普通函数内使用直接报错
-
阻塞误区:await 只阻塞当前函数内部,不阻塞主线程,外部同步代码正常执行
-
串行性能坑:无依赖多异步直接串行 await,会造成不必要的耗时堆积,必须用 Promise.all 并行优化
-
裸 await 风险:未加 try/catch、未加 .catch() 的 await 报错会直接终止函数执行
-
返回值误区:async 函数永远返回 Promise,即使 return 普通值,外部接收仍需 await/then 获取结果
-
循环异步坑:普通 for...in/forEach 无法等待 await,批量异步遍历必须用 for await...of
2.7 全套可运行实战代码
// ====================== 1. async 函数返回值特性 ======================
// 1. 返回普通值,自动包装成功Promise
async function fn1() {
return "async成功结果";
}
console.log(fn1()); // Promise {<fulfilled>: 'async成功结果'}
fn1().then(res => console.log(res));
// 2. 函数抛出异常,返回失败Promise
async function fn2() {
throw new Error("async函数主动报错");
}
fn2().catch(err => console.log("捕获异常:", err.message));
// ====================== 2. await 基础等待机制 ======================
function sleep(time, value) {
return new Promise(resolve => setTimeout(() => resolve(value), time));
}
async function awaitBase() {
console.log("开始执行");
// 等待Promise敲定,获取返回值
const res1 = await sleep(1000, "第一个任务完成");
console.log(res1);
// 等待普通值,直接返回原值
const res2 = await 666;
console.log("普通值等待:", res2);
console.log("执行结束");
}
awaitBase();
// ====================== 3. 错误处理:try/catch + 单个catch兜底 ======================
async function errorTest() {
// 单个await链式兜底
const res = await Promise.reject("接口请求失败").catch(err => err);
console.log("单个任务兜底:", res);
// 批量任务try/catch统一捕获
try {
const data1 = await sleep(500, "数据1");
const data2 = await Promise.reject("批量任务报错");
console.log(data1, data2);
} catch (err) {
console.log("批量错误捕获:", err);
}
}
errorTest();
// ====================== 4. 串行 vs 并行 性能对比 ======================
// 串行执行:总耗时 ≈ 2000ms
async function serialTask() {
const start = Date.now();
await sleep(1000, "任务1");
await sleep(1000, "任务2");
console.log("串行耗时:", Date.now() - start);
}
// 并行执行:总耗时 ≈ 1000ms(性能翻倍)
async function parallelTask() {
const start = Date.now();
// 先创建所有Promise
const p1 = sleep(1000, "并行任务1");
const p2 = sleep(1000, "并行任务2");
// 统一等待
const res = await Promise.all([p1, p2]);
console.log("并行结果:", res);
console.log("并行耗时:", Date.now() - start);
}
serialTask();
parallelTask();
// ====================== 5. allSettled 容错并行 ======================
async function safeParallel() {
const tasks = [sleep(500, "成功任务"), Promise.reject("失败任务")];
const res = await Promise.allSettled(tasks);
console.log("容错并行结果:", res);
}
safeParallel();
// ====================== 6. for await...of 异步串行遍历 ======================
async function asyncLoop() {
// 模拟异步任务列表
const taskList = [sleep(300, "遍历任务1"), sleep(300, "遍历任务2"), sleep(300, "遍历任务3")];
// 串行依次执行,等待每个任务完成
for await (const res of taskList) {
console.log("异步遍历结果:", res);
}
console.log("异步遍历全部完成");
}
asyncLoop();
// ====================== 7. 经典误区:forEach 无法等待异步 ======================
async function errorLoop() {
const arr = [1, 2, 3];
// forEach 不支持await,不会阻塞执行
arr.forEach(async (item) => {
await sleep(200);
console.log("forEach异步:", item);
});
console.log("forEach直接执行完毕,不会等待异步");
}
errorLoop();
2.8 面试真题 & 工程开发规范(必背)
1. async/await 本质:Promise 语法糖,不改变 JS 单线程异步模型,仅优化代码可读性,底层依托微任务实现。
2. 执行阻塞范围:await 仅阻塞当前 async 函数内部代码,不阻塞主线程、不影响外部同步与其他异步任务。
3. 执行顺序选择:任务有依赖用串行 await,无依赖必须用 Promise.all 并行,杜绝无效耗时。
4. 异步遍历规范:普通 forEach 无法等待异步,批量异步串行遍历优先使用 for await...of。
5. 错误处理规范:所有 await 必须兜底,单任务用 .catch(),多任务用 try/catch,禁止裸写 await。
6. 返回值规范:async 函数始终返回 Promise,外部必须通过 await/then 才能获取最终结果,无法直接拿到原始值。
7. 顶级 await 拓展:ESM 模块中支持顶级 await,CommonJS 模块不支持,可直接在模块顶层使用 await 无需包裹 async 函数。
模块 3 事件循环 Event Loop(浏览器 + Node 双版本)
3.1 浏览器事件循环(完整底层机制·面试满分版)
浏览器事件循环(Event Loop)是JS 单线程非阻塞异步模型的核心底层,解决了单线程执行代码阻塞的问题,是异步代码执行顺序、宏微任务执行优先级、页面渲染时机的核心调度机制。JS 本身无异步调度能力,事件循环由浏览器宿主环境实现。
3.1.1 核心前置基础
1. JS 主线程:唯一执行线程,负责解析、执行 JS 代码,无法并行执行同步任务;
2. 宿主线程辅助:浏览器包含渲染线程、定时器线程、网络线程、IO 线程等后台线程,异步任务由后台线程触发,最终推入对应队列等待主线程执行;
3. 队列特性:任务队列先进先出,同一队列内按入队顺序执行,不同队列存在固定优先级。
3.1.2 完整执行闭环顺序(必考核心)
浏览器标准执行流程(一轮事件循环):执行同步代码(调用栈清空) → 清空所有微任务队列 → 执行一次页面渲染(可选) → 执行一个宏任务 → 循环往复
关键规则:
1. 同步代码优先级最高,调用栈未清空,绝不执行异步任务;
2. 微任务优先级高于宏任务,必须一次性清空所有微任务,才会进入后续流程;
3. 渲染操作在微任务清空后、宏任务执行前触发;
4. 每一轮循环仅执行一个宏任务,执行完毕后再次校验微任务、渲染,开启下一轮循环。
3.1.3 宏任务 & 微任务 完整细分(无遗漏)
(1)微任务(MicroTask):高优先级、本轮执行完毕
微任务是 ES6 规范定义的异步任务,依附于当前执行轮次,本轮必须清空,无跨轮次延迟。
完整包含类型:
-
Promise 系列:.then / .catch / .finally 回调
-
手动微任务:queueMicrotask() 原生 API
-
DOM 观测微任务:MutationObserver(监听 DOM 变更)
-
其他微任务:async/await 后续代码(本质 Promise 微任务)
特性:执行速度极快、无最小延迟、本轮闭环执行,适合高优先级异步逻辑。
(2)宏任务(MacroTask):低优先级、单轮单次执行
宏任务由浏览器宿主定义,任务耗时相对较长,每轮事件循环仅执行一个。
完整包含类型:
-
脚本任务:初始 script 整体代码(首个宏任务)
-
定时器任务:setTimeout / setInterval
-
IO 任务:文件读写、网络请求回调(AJAX/Fetch 回调)
-
UI 交互任务:鼠标、点击、键盘等用户交互事件回调
-
其他:setImmediate(仅浏览器旧版/Node 兼容)
3.1.4 特殊任务:requestAnimationFrame(渲染帧任务)
不属于宏任务、不属于微任务,是浏览器专属渲染优先级任务,独立调度:
执行时机:微任务清空后、页面渲染前执行,早于宏任务
核心特性:跟随浏览器刷新率(默认 60fps,约 16.7ms 一次),用于动画、帧更新,规避定时器卡顿问题。
3.1.5 嵌套任务执行规则(高频面试坑)
1. 微任务嵌套微任务:新微任务会加入当前微任务队列,本轮一次性全部清空,不会遗留到下一轮;
2. 宏任务嵌套宏任务:内层宏任务加入队列末尾,需等待当前轮次结束,下下轮才会执行;
3. 微任务嵌套宏任务:宏任务入队,等待当前所有微任务清空、渲染完成后,下一轮执行;
4. 宏任务嵌套微任务:宏任务执行过程中产生的微任务,当前宏任务结束后立即清空,再执行下一个宏任务。
3.1.6 经典执行顺序例题(吃透秒杀90%面试题)
console.log("1.同步代码");
// 宏任务
setTimeout(() => {
console.log("4.宏任务定时器");
// 宏任务嵌套微任务
queueMicrotask(() => {
console.log("5.宏任务内嵌套微任务");
});
}, 0);
// 微任务
queueMicrotask(() => {
console.log("3.原生微任务");
});
console.log("2.同步代码");
// 执行顺序:1 → 2 → 3 → 4 → 5
执行解析:
1. 优先执行所有同步代码,输出 1、2;
2. 同步代码执行完毕,清空微任务队列,输出 3;
3. 微任务清空,执行唯一宏任务,输出 4;
4. 宏任务执行中产生新微任务,立即清空,输出 5。
3.1.7 高频易错面试总结(必背)
1. 优先级终极排序:同步代码 > 微任务 > requestAnimationFrame > 宏任务
2. 微任务核心特点:批量清空、嵌套微任务本轮执行完毕,无延迟;
3. 宏任务核心特点:单轮执行一个、嵌套宏任务延后执行、存在最小延迟;
4. 渲染时机:微任务清空后才会渲染页面,因此微任务可批量修改 DOM,只触发一次渲染优化性能;
5. async/await 本质:await 之后的代码等价于 Promise.then 微任务;
6. 队列优先级误区:不存在宏任务插队微任务的情况,微任务永远本轮优先清空。
3.2 Node.js 事件循环(完整底层机制·面试必考)
Node.js 事件循环是服务端异步调度核心,基于 libuv 库实现,与浏览器事件循环完全不同,拥有独立的阶段队列、任务优先级、执行顺序,是 Node 异步代码执行、定时器、IO 调度的底层核心,也是高频面试重难点。
3.2.1 核心前置认知
1. Node 单线程模型:主线程执行 JS 代码,异步任务由 libuv 多线程池托管,完成后推入对应事件循环队列;
2. 执行整体流程:执行同步代码 > 处理微任务队列 > 开启事件循环轮询执行各阶段宏任务;
3. 核心差异:浏览器是「微任务清空 → 单次宏任务」,Node 是「单阶段宏任务执行完毕 → 清空对应微任务」。
3.2.2 事件循环六大阶段(严格执行顺序)
Node 事件循环每一轮循环,严格按照以下顺序执行六大阶段,从上到下不可逆,每个阶段有独立的任务队列,阶段内队列清空或达到执行上限后,进入下一阶段:
1. timers 阶段(定时器阶段)
执行任务:setTimeout、setInterval 到期回调
规则:执行所有到期的定时器回调,并非到时间立即执行,需等待本轮阶段调度;
特点:存在最小延迟误差,受事件循环阻塞影响。
2. pending callbacks 阶段(延迟回调阶段)
执行任务:延迟执行的系统回调,如 TCP 错误回调、部分 IO 异常回调;
特点:日常开发极少接触,底层系统级回调,无需手动处理。
3. idle / prepare 阶段(空闲预备阶段)
作用:Node 内部自用阶段,用于资源初始化、状态预备;
特点:开发者无自定义任务,可直接忽略。
4. poll 阶段(轮询阶段·核心重点)
核心作用:获取新的 IO 事件、执行 IO 回调(文件读写、网络请求等),是事件循环最繁忙的阶段。
执行规则:
1. 若 poll 队列有任务:一次性清空所有 IO 回调任务;
2. 若 poll 队列为空:
- 有 setImmediate 待执行任务:退出 poll 阶段,进入 check 阶段;
- 无 setImmediate 任务:阻塞等待,监听新的 IO 事件,避免空轮询消耗性能。
5. check 阶段(检查阶段)
执行任务:专属执行 setImmediate 回调函数;
优先级:同轮次中,执行时机晚于 poll、早于 close 阶段。
6. close callbacks 阶段(关闭回调阶段)
执行任务:关闭类回调,如 socket.on('close')、文件流关闭回调;
收尾作用:一轮事件循环最后收尾,执行资源销毁回调。
3.2.3 Node 微任务体系(优先级重中之重)
Node 微任务分为两类,优先级严格区分,远高于宏任务,执行时机穿插在事件循环各阶段之间:
微任务优先级排序(从高到低):process.nextTick > 其他微任务(Promise.then/catch/finally、queueMicrotask)
(1)nextTick 微任务(最高优先级)
隶属于 Node 专属微任务,优先级高于所有普通微任务;
核心规则:每执行完一个事件循环阶段,优先一次性清空所有 nextTick 任务,再清空普通微任务,最后进入下一阶段。
(2)普通微任务
包含:Promise 回调、queueMicrotask、async/await 后续代码;
执行时机:nextTick 队列清空后执行。
3.2.4 宏任务优先级与核心差异
Node 宏任务按事件循环阶段顺序执行,同级别阶段内先进先出,跨阶段优先级固定:
宏任务优先级:timers(定时器) < poll(IO) < check(setImmediate)
高频考点:setTimeout vs setImmediate 执行顺序
1. 全局顶级调用:顺序不确定(受事件循环启动耗时影响,定时器可能延迟 1ms);
2.IO 回调内部调用:setImmediate 永远先执行(IO 回调在 poll 阶段,执行完直接进入 check 阶段)。
3.2.5 Node 与浏览器事件循环核心区别(面试必背)
-
执行机制不同:浏览器一轮循环「清空微任务 → 执行1个宏任务」;Node 一轮循环「执行一个阶段所有宏任务 → 清空全部微任务」;
-
微任务体系不同:Node 有 nextTick 高优先级微任务,浏览器无;
-
宏任务队列不同:Node 分六大阶段队列,浏览器无阶段划分;
-
定时器优先级不同:浏览器 setTimeout 优先级高于 setImmediate,Node 场景下顺序不固定。
3.2.6 可直接运行实战代码(吃透执行顺序)
// ====================== 1. 微任务优先级:nextTick > Promise微任务 ======================
console.log("1.同步代码");
Promise.resolve().then(() => {
console.log("4.Promise普通微任务");
});
process.nextTick(() => {
console.log("3.nextTick高优先级微任务");
});
console.log("2.同步代码");
// 输出顺序:1 → 2 → 3 → 4
// ====================== 2. setTimeout 与 setImmediate 全局执行顺序(不确定) ======================
setTimeout(() => {
console.log("定时器 setTimeout");
}, 0);
setImmediate(() => {
console.log("立即执行 setImmediate");
});
// ====================== 3. IO回调内执行(setImmediate 优先) ======================
const fs = require("fs");
fs.readFile(__filename, () => {
setTimeout(() => {
console.log("IO内定时器");
}, 0);
setImmediate(() => {
console.log("IO内立即执行"); // 永远先执行
});
});
// ====================== 4. 完整事件循环执行链路真题 ======================
console.log("同步开始");
setTimeout(() => {
console.log("timer 定时器宏任务");
Promise.resolve().then(() => {
console.log("timer内微任务");
});
}, 0);
setImmediate(() => {
console.log("check 立即执行宏任务");
});
process.nextTick(() => {
console.log("nextTick 微任务");
});
console.log("同步结束");
3.2.7 高频面试易错总结(必背)
-
微任务优先级铁律:process.nextTick 优先级高于所有普通微任务,每阶段执行完毕优先清空 nextTick;
-
阶段执行规则:事件循环按六大阶段固定顺序执行,单个阶段队列清空后,统一清空所有微任务,再进入下一阶段;
-
定时器顺序坑:全局 setTimeout/setImmediate 顺序随机,IO 回调内 setImmediate 优先执行;
-
poll 阶段特性:队列为空且无 setImmediate 时会阻塞等待新 IO 事件,是 Node 进程不退出的核心原因;
-
新旧版本差异:Node11 之后调整为「阶段宏任务执行完即清空微任务」,逻辑趋近浏览器,彻底解决旧版本批量宏任务堆积问题;
-
空进程常驻:Node 进程若无任务直接退出,poll 阻塞等待是保持进程常驻的关键。
3.3 定时器细节(底层规则、坑点、优化方案·面试高频)
JS 定时器并非精准定时,依托事件循环机制实现,存在天然延迟误差,多数面试诡异时序问题、线上定时器 bug 均来自底层执行规则,以下为全网最全定时器细节与避坑方案。
3.3.1 setTimeout 核心底层规则与限制
1. 最小延迟 4ms 机制(浏览器标准规范)
浏览器为优化性能、避免高频定时器阻塞主线程,规定 setTimeout 存在最小 4ms 保底延迟:
- 当设置延迟 ≤ 0ms(0、-1 等),浏览器统一默认兜底为 4ms 延迟;
- 嵌套定时器强制叠加 4ms:定时器回调内部再次开启定时器,每嵌套一层,强制叠加 4ms 最小延迟,多层嵌套会出现明显时间偏差;
- 主线程阻塞时,延迟无限叠加:若同步代码、微任务、其他宏任务阻塞主线程,定时器回调会持续等待,实际执行时间 = 设定延迟 + 主线程阻塞时长。
2. 延迟参数容错规则
延迟参数非数字时会触发隐式转换,无法转换为有效数字时,默认延迟 0ms(兜底 4ms),极易产生隐性时序 bug。
3.3.2 setInterval 核心缺陷与时间漂移问题
1. 核心坑:时间漂移(累计误差)
setInterval 的执行逻辑是:固定间隔往宏任务队列推入回调,而非「执行完上一次,间隔后执行下一次」。
当回调执行耗时 > 设定间隔、或主线程存在阻塞时:
- 队列会堆积重复回调,出现连续执行、无间隔压缩问题;
- 误差会持续累计,长时间运行后定时精度严重失真(时间漂移);
- 极端场景会出现回调重叠执行,引发数据错乱、重复请求等线上 bug。
2. 关键缺陷汇总
- 不等待上一轮回调执行完毕,无脑定时入队;
- 存在最小 4ms 延迟叠加,长期运行误差累积;
- 页面隐藏/后台休眠时,浏览器会节流定时器,大幅降低精度;
- 开发强制规范:彻底弃用 setInterval,递归 setTimeout 替代。
3.3.3 定时器精准优化方案(递归 setTimeout)
递归式 setTimeout 核心优势:上一次回调执行完毕后,才开始计时下一次,无队列堆积、无时间漂移,精度远高于 setInterval。
适配场景:轮询接口、定时刷新数据、心跳检测、长期定时任务。
3.3.4 queueMicrotask 手动微任务(精准即时执行)
核心定位:ES2020 原生 API,用于手动创建高优先级微任务,无最小延迟、不依赖宏任务队列,执行时机远快于定时器。
核心特性:
- 属于微任务,本轮事件循环优先清空,无 4ms 延迟;
- 适合低延迟、高优先级的异步逻辑(DOM 批量更新、状态同步、日志上报);
- 替代老旧的 Promise.resolve().then() 空微任务写法,语义更清晰、性能更优。
3.3.5 页面休眠与定时器节流规则
浏览器为节省设备电量、减少性能消耗,对后台页面的定时器会强制节流:
1. 页面隐藏、切换后台、最小化时,定时器延迟会被统一节流至 1000ms 最低间隔;
2. 页面重新激活后,堆积的定时器会集中批量执行,引发瞬间高频回调;
3. 解决方案:页面可见性切换时(visibilitychange),暂停/重启定时任务,规避批量执行 bug。
3.3.6 定时器销毁与内存泄漏避坑
所有定时器为全局托管,不会随函数执行完毕自动销毁,遗漏清除会造成严重内存泄漏:
1. setTimeout:单次定时器,执行完毕自动销毁,无需手动清除;
2. setInterval:循环定时器,必须手动调用 clearInterval 销毁;
3. 页面卸载、组件销毁、路由跳转时,必须批量清空所有定时器,防止残留回调执行;
4. 规范:定时器 ID 统一存储,销毁时统一清空,杜绝匿名定时器残留。
3.3.7 全套可运行实战代码
// ====================== 1. setTimeout 最小4ms延迟 & 嵌套延迟 ======================
console.time('timer-delay');
// 0ms延迟实际兜底4ms
setTimeout(() => {
console.timeEnd('timer-delay'); // 输出约4~5ms
// 嵌套定时器,叠加4ms延迟
setTimeout(() => {
console.log('嵌套定时器,叠加4ms延迟');
}, 0);
}, 0);
// ====================== 2. setInterval 时间漂移问题 ======================
let count = 0;
// 间隔100ms执行,回调耗时200ms,产生队列堆积
const intervalTimer = setInterval(() => {
console.log('setInterval执行次数', ++count);
// 模拟耗时任务
const start = Date.now();
while (Date.now() - start < 200) {}
}, 100);
// 执行3次后清除,观察执行混乱问题
setTimeout(() => clearInterval(intervalTimer), 500);
// ====================== 3. 递归setTimeout 精准定时(无漂移) ======================
function accurateTimer() {
console.log('精准定时执行,无时间漂移');
// 执行完再计时,规避队列堆积
setTimeout(accurateTimer, 100);
}
// 启动精准定时
const accurateTimerId = setTimeout(accurateTimer, 100);
// 500ms后停止
setTimeout(() => clearTimeout(accurateTimerId), 500);
// ====================== 4. queueMicrotask 手动微任务(无延迟) ======================
console.log('同步代码开始');
queueMicrotask(() => {
console.log('手动微任务:优先于宏任务执行,无4ms延迟');
});
setTimeout(() => console.log('定时器宏任务:存在4ms延迟'), 0);
console.log('同步代码结束');
// ====================== 5. 页面可见性 定时器节流优化 ======================
let pageTimer = null;
// 页面激活开启定时
function startTimer() {
pageTimer = setInterval(() => {
console.log('页面活跃,正常轮询');
}, 500);
}
// 页面休眠清除定时
function stopTimer() {
clearInterval(pageTimer);
}
// 监听页面可见性切换
document.addEventListener('visibilitychange', () => {
document.hidden ? stopTimer() : startTimer();
});
// ====================== 6. 定时器统一销毁规范 ======================
// 统一存储所有定时器ID
const timerList = [];
timerList.push(setTimeout(() => {}, 1000));
timerList.push(setInterval(() => {}, 500));
// 批量清空定时器(组件卸载/页面退出通用)
function clearAllTimer() {
timerList.forEach(id => clearTimeout(id) || clearInterval(id));
}
// clearAllTimer();
3.3.8 高频面试必背总结
1. 定时器最小延迟:setTimeout 最低保底 4ms,嵌套定时器每层级叠加 4ms 延迟,无 0ms 绝对即时执行;
2. setInterval 致命缺陷:先入队后执行,不等待回调完成,存在队列堆积、时间漂移、回调重叠问题,生产环境禁用;
3. 精准定时方案:递归 setTimeout 实现「执行完毕再计时」,彻底解决时间漂移,是长期定时最优解;
4. 微任务与定时器差异:queueMicrotask 无延迟、优先级更高,适合即时异步逻辑,定时器适合延迟调度逻辑;
5. 后台节流规则:页面隐藏时定时器被节流至 1s 间隔,需配合 visibilitychange 暂停任务,避免批量执行 bug;
6. 内存泄漏规范:循环定时器必须手动清除,页面/组件销毁时批量清空所有定时器,杜绝残留;
7. 执行优先级:同步代码 > 微任务(queueMicrotask/Promise) > 定时器宏任务。
模块 4 异步并发优化(工程实战·性能核心)
JS 单线程异步开发中,串行慢、并行炸、并发失控是线上高频性能问题。本模块系统性补齐异步执行模型、并发限流、批量任务调度、多线程优化方案,适配接口批量请求、文件批量处理、海量数据渲染等工程场景,同时覆盖高频面试考点。
4.1 三大异步执行模型(串行 / 并行 / 有限并发)
异步任务分为三种执行模式,无依赖任务优先并行、有依赖任务串行、海量任务必须有限并发,按需选择是性能优化的核心。
4.1.1 串行执行(顺序执行)
核心逻辑:任务从上到下依次执行,前一个任务执行完毕,再开启下一个任务。
适用场景:任务存在依赖关系(登录→获取权限→请求业务数据)、接口调用有顺序要求、资源独占场景。
优缺点:执行稳定、无并发冲突、无报错雪崩问题;缺点是耗时最长,多任务总耗时为所有任务耗时叠加。
实现方式:普通顺序 await、for 循环遍历 await、for await...of 串行遍历。
4.1.2 并行执行(全速并发)
核心逻辑:一次性创建所有异步任务,同时推入任务队列,同步执行所有任务,总耗时取决于最慢的单个任务。
适用场景:无依赖批量任务(批量图片上传、批量接口查询、批量文件读取)、追求极致执行速度。
优缺点:执行速度最快,大幅缩短耗时;致命缺陷是无并发限制,任务量过大(几十/上百)会造成:浏览器请求队列阻塞、服务端接口限流报错、TCP 连接耗尽、内存溢出。
实现方式:Promise.all / Promise.allSettled 批量并行。
4.1.3 有限并发(工程最优方案)
核心逻辑:限制同时执行的异步任务最大数量,达到上限后自动排队,空闲一个任务再补一个,始终维持固定并发数,平衡速度与稳定性。
适用场景:海量批量任务(100+图片上传、批量数据同步、批量接口轮询)、需要规避接口限流、防止主线程阻塞的生产场景。
核心优势:既避免串行过慢,又杜绝全速并行的雪崩风险,是企业级批量异步处理的标准最优解。
4.2 四大批量异步 API 完整对比与选型规范
ES6 提供四种批量异步调度 API,适配不同容错场景,是并发优化的基础,面试高频考察差异对比。
|
API |
执行特性 |
失败机制 |
返回值 |
适用场景 |
|---|---|---|---|---|
|
Promise.all |
全部并行执行 |
一个失败、整体失败(短路报错) |
成功结果数组 |
多任务强依赖、缺一不可场景 |
|
Promise.allSettled |
全部并行执行 |
失败不终止,全部执行完毕 |
包含成功/失败状态的结果数组 |
批量任务容错执行、部分失败不影响整体 |
|
Promise.race |
并行竞争执行 |
首个敲定(成功/失败)即终止 |
首个完成任务结果 |
接口超时拦截、多接口抢快响应 |
|
Promise.any |
并行竞争执行 |
首个成功即终止,全部失败才报错 |
首个成功结果 |
多备用接口重试、择优获取数据 |
4.3 核心实战:手写有限并发调度器(面试必考 + 工程可用)
原生 API 无并发限流能力,工程中需手动实现异步并发调度器,支持限制最大并发数、任务自动排队、任务容错、执行完成回调,可直接用于批量上传、批量请求等场景。
// 异步有限并发调度器(完整版、可直接生产使用)
class AsyncScheduler {
// max: 最大并发数
constructor(max = 5) {
this.max = max; // 最大同时执行任务数
this.running = 0; // 当前正在执行的任务数
this.queue = []; // 等待排队的任务队列
}
// 添加异步任务
add(task) {
return new Promise((resolve, reject) => {
// 封装任务,存入队列
this.queue.push({ task, resolve, reject });
// 尝试执行任务
this.run();
});
}
// 执行任务核心逻辑
run() {
// 超出最大并发数 / 无排队任务,终止执行
if (this.running >= this.max || this.queue.length === 0) return;
// 取出队首任务执行
const { task, resolve, reject } = this.queue.shift();
this.running++;
task()
.then((res) => resolve(res))
.catch((err) => reject(err))
.finally(() => {
this.running--;
// 递归执行下一个排队任务
this.run();
});
}
}
// ====================== 实战测试 ======================
// 模拟异步任务(接口请求/文件处理)
function createTask(val, time) {
return () => new Promise((resolve) => setTimeout(() => resolve(val), time));
}
// 初始化调度器,限制最大3个并发
const scheduler = new AsyncScheduler(3);
const taskList = [
createTask("任务1", 1000),
createTask("任务2", 800),
createTask("任务3", 1200),
createTask("任务4", 900),
createTask("任务5", 1100),
createTask("任务6", 700),
];
// 批量添加任务
async function runTask() {
const start = Date.now();
const res = await Promise.all(taskList.map((item) => scheduler.add(item)));
console.log("全部任务执行完成", res);
console.log("总耗时:", Date.now() - start);
}
runTask();
4.4 并发优化高频工程方案
4.4.1 接口并发优化规范
1. 小批量无依赖接口:直接使用 Promise.all 并行请求,简化代码、提升速度;
2. 大批量接口请求:必须使用有限并发调度器,限制并发数(常规 3~5),规避浏览器同源请求限制(Chrome 同源最大6个并行请求);
3. 容错场景:使用 allSettled 替代 all,单独过滤失败任务,避免单个接口失败导致整体业务瘫痪;
4. 超时保护:所有批量请求搭配 Promise.race 做超时拦截,防止任务卡死。
4.4.2 异步任务防抖与节流(并发限流辅助)
针对高频触发的异步任务(搜索联想、滚动加载、窗口resize请求),通过防抖节流减少无效并发请求,降低服务器压力:
防抖:高频触发时,仅最后一次触发完成后执行一次,适用于搜索输入、表单实时校验;
节流:固定时间内仅执行一次,适用于滚动加载、窗口缩放、轮询刷新。
4.5 Web Worker 多线程并发优化(主线程解阻塞)
JS 主线程单线程特性,海量计算、大数据解析、复杂算法会阻塞 UI 渲染、卡死页面。Web Worker 允许创建后台子线程,实现多线程并发执行,主线程仅负责渲染与交互,子线程负责耗时计算。
4.5.1 核心特性与限制
1. 子线程无法访问 DOM、window、document,仅可执行纯 JS 计算逻辑;
2. 线程间通过 postMessage / onmessage 通信,数据传输为结构化克隆,无引用共享;
3. 支持多 Worker 并行计算,突破单线程性能限制;
4. 页面销毁必须手动终止 Worker,避免内存泄漏。
4.5.2 实战场景
海量数据排序、大数据 Excel 解析、Canvas 复杂绘图计算、加密解密算法、前端大数据可视化运算。
4.5.3 极简实战代码
主线程代码:
// 创建子线程
const worker = new Worker("./compute-worker.js");
// 接收子线程计算结果
worker.onmessage = (e) => {
console.log("耗时计算结果:", e.data);
// 任务完成,终止线程释放内存
worker.terminate();
};
// 向子线程发送计算任务
worker.postMessage({ num: 1000000 });
// 监听线程错误
worker.onerror = (err) => {
console.error("Worker线程报错:", err);
worker.terminate();
};
子线程 compute-worker.js 代码:
// 接收主线程任务
self.onmessage = (e) => {
const { num } = e.data;
// 模拟海量耗时计算
let res = 0;
for (let i = 0; i < num; i++) {
res += i;
}
// 返回计算结果
self.postMessage(res);
};
4.6 异步并发高频面试必背总结
-
执行模型选型:有依赖用串行、无依赖小批量用并行、大批量任务用有限并发;
-
API 核心差异:all 短路报错、allSettled 全量容错、race 竞速终止、any 择优成功;
-
并发痛点:原生无并发限制,海量并行会导致浏览器请求阻塞、服务端限流、内存溢出;
-
调度器核心原理:维护执行队列与并发计数器,任务完成后自动补位,维持固定并发数;
-
主线程阻塞解决方案:耗时计算全部移交 Web Worker 子线程,实现 UI 与计算并发;
-
工程规范:批量异步必须加容错、超时、并发限流,杜绝裸写批量 Promise,规避线上雪崩 bug。
第三部分 浏览器端 API:BOM + DOM
模块 1 BOM 浏览器对象模型(完整版·底层+实战+面试)
BOM(Browser Object Model)浏览器对象模型,是 JS 操作浏览器窗口、浏览器行为的底层 API 集合,核心依托 window 顶级全局对象 实现,无标准规范但所有浏览器统一兼容。BOM 专注浏览器窗口交互、页面跳转、网络状态、设备信息、本地存储等能力,区别于仅操作页面结构的 DOM,是前端浏览器开发的核心基础。
BOM 核心成员一览:window、location、history、navigator、screen、document(DOM入口)、定时器、存储API、剪贴板、通知API等。
1.1 核心顶层:window 全局对象
window 是浏览器的顶级全局根对象,所有 BOM/DOM API、全局变量、全局函数均挂载在 window 上,也是 JS 代码执行的全局作用域宿主。
1.1.1 核心特性
1. 全局挂载:所有 var 声明的顶层变量、全局函数会成为 window 属性,let/const/import 声明的变量仅存在模块作用域,不挂载 window;
2. 隐式省略:全局调用 window 的属性/方法时,可直接省略 window 前缀;
3. 窗口指代:一个浏览器标签页对应一个独立 window 对象,标签间完全隔离;
4. 特殊属性:window.window === window、window.self === window,用于严格判断全局环境。
1.1.2 高频全局方法与属性
窗口操作:
- window.open(url, 窗口名, 配置):新开浏览器窗口/弹窗,返回新窗口实例;弹窗易被浏览器拦截,仅用户主动触发可生效;
- window.close():关闭当前窗口,仅支持 open 打开的窗口或用户手动新建的空白窗口;
- window.resizeTo(width, height):自定义窗口尺寸;
- window.moveTo(x, y):移动窗口位置(部分浏览器受限)。
弹窗交互(阻塞主线程):
- alert(msg):普通提示弹窗,仅展示文本,无返回值;
- confirm(msg):确认弹窗,返回布尔值(确定true/取消false);
- prompt(提示, 默认值):输入弹窗,返回用户输入字符串,取消返回 null。
1.1.3 全局污染问题(开发核心避坑)
1. var 顶层变量挂载 window,易造成全局变量污染、变量覆盖冲突;
2. 未声明的变量自动挂载 window,引发隐性全局变量泄露;
3. 解决方案:废弃 var、使用 ESM 模块隔离、全局变量统一命名空间、开启严格模式。
1.2 location 地址栏对象(页面跳转/路由核心)
location 用于读取、修改当前页面 URL 信息,所有属性均可读写,修改属性会触发页面跳转/刷新,是前端页面跳转、参数获取的核心 API。
1.2.1 核心只读/可读写属性
完整 URL 结构拆解:https://user:pass@www.xxx.com:8080/path?name=123#hash
-href:完整 URL 地址,读写均可,赋值直接跳转页面;
- origin:协议+域名+端口,只读,代表当前源,跨域核心判断依据;
- protocol:协议(http/https/ftp);
- hostname:域名;host:域名+端口;
- port:端口号;
- pathname:页面路径(域名后、问号前);
- search:查询参数(?开头的参数字符串);
- hash:哈希值(#开头),锚点路由核心,修改 hash 不刷新页面、不触发请求。
1.2.2 三大核心方法(面试高频)
1. location.assign(url):跳转新页面,保留历史记录,可回退;等价于 window.location.href = url;
2. location.replace(url):跳转新页面,替换当前历史记录,不可回退,适合登录跳转、重定向场景;
3. location.reload(布尔值):刷新页面;默认 false(缓存刷新),true(强制从服务端刷新,忽略缓存)。
1.2.3 实战:URL 参数解析(原生实现)
原生 URL/URLSearchParams 是标准解析方案,无需正则,适配所有参数场景。
// 解析URL参数
const url = new URL(window.location.href);
// 获取单个参数
console.log(url.searchParams.get('name'));
// 设置参数(不刷新页面)
url.searchParams.set('age', 20);
// 删除参数
url.searchParams.delete('name');
// 获取所有参数
console.log(Object.fromEntries(url.searchParams));
// 单独解析search字符串
const searchParams = new URLSearchParams(location.search);
1.3 history 历史记录对象(前端路由底层)
history 用于操作浏览器会话历史,核心实现无刷新页面跳转,是 Vue/React history 模式路由的底层原理,无法获取历史记录具体地址,仅可操作跳转行为。
1.3.1 基础属性与方法
- history.length:当前会话历史记录条数;
- history.back():回退上一页,等价于浏览器返回按钮;
- history.forward():前进下一页;
- history.go(n):批量跳转,go(-1)回退、go(1)前进、go(0)刷新当前页。
1.3.2 无刷新路由核心API(面试必考)
1. history.pushState(state, title, url):新增历史记录,页面不刷新、不请求服务端;可携带自定义状态数据,实现路由传参;
2. history.replaceState(state, title, url):替换当前历史记录,不新增栈记录;
3. popstate 事件:浏览器前进/后退触发,pushState/replaceState 不会主动触发,仅用户手动跳转生效。
1.3.3 hash模式 vs history模式 核心差异
1. hash 模式:基于 location.hash,修改不刷新页面,兼容性极好,无需服务端配置;URL 带 #,不美观;
2. history 模式:基于 history API,URL 干净无 #;刷新会 404,必须服务端配置重定向,适配现代项目。
1.4 navigator 浏览器设备信息对象
navigator 用于获取浏览器、设备、系统相关信息,常用于设备适配、环境判断、权限调用。
1.4.1 高频核心属性
- navigator.userAgent:UA标识,判断浏览器类型、设备系统(安卓/iOS/PC);
- navigator.platform:操作系统平台;
- navigator.language:浏览器默认语言;
- navigator.onLine:布尔值,判断当前网络在线状态;
- navigator.cookieEnabled:判断浏览器是否开启 Cookie。
1.4.2 高级能力API(工程常用)
1. 剪贴板 Clipboard API(异步)
替代老旧 document.execCommand,支持异步读写剪贴板,适配现代浏览器:
// 写入剪贴板(复制)
async function copyText(text) {
await navigator.clipboard.writeText(text);
alert('复制成功');
}
// 读取剪贴板(粘贴,需用户授权)
async function pasteText() {
const text = await navigator.clipboard.readText();
console.log('粘贴内容:', text);
}
2. 桌面通知 Notification(消息推送)
浏览器桌面弹窗通知,需用户授权,适合后台消息提醒:
// 申请授权并推送通知
async function showNotice() {
// 申请权限
const res = await Notification.requestPermission();
if(res === 'granted') {
new Notification('消息提醒', { body: '您有新的待办任务' });
}
}
3. 地理位置 Geolocation API
获取用户设备地理位置,HTTPS 环境生效,HTTP 本地调试可用:
navigator.geolocation.getCurrentPosition(
(pos) => {
console.log('纬度:', pos.coords.latitude);
console.log('经度:', pos.coords.longitude);
},
(err) => console.log('获取位置失败:', err)
);
1.5 screen 屏幕对象
获取用户设备屏幕物理尺寸、分辨率信息,适配大屏适配、屏幕展示场景:
- screen.width/height:屏幕完整分辨率尺寸;
- screen.availWidth/availHeight:屏幕可用尺寸(排除任务栏、状态栏);
- screen.colorDepth:屏幕色彩深度。
1.6 页面尺寸与滚动体系(高频布局考点)
1.6.1 窗口可视尺寸
- window.innerWidth/innerHeight:包含滚动条的窗口可视尺寸,实时响应窗口缩放;
- document.documentElement.clientWidth:不含滚动条的可视尺寸,布局常用标准。
1.6.2 页面滚动信息
- window.scrollX / scrollY:页面水平/垂直滚动距离(标准只读);
- window.scrollTo(x, y):滚动到指定坐标,支持平滑滚动配置;
- window.scrollBy(x, y):基于当前位置增量滚动。
// 平滑滚动到顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
1.7 浏览器五大存储体系(完整对比+选型规范)
前端本地存储分为5类,容量、生命周期、特性、适用场景完全不同,是面试高频对比考点,也是业务开发必备选型基础。
|
存储方式 |
容量上限 |
生命周期 |
核心特性 |
适用场景 |
|
Cookie |
4KB(极小) |
可手动设置过期时间,默认会话级 |
随HTTP请求自动携带、支持HttpOnly/SameSite、跨标签共享、仅字符串 |
登录凭证、Token、用户身份校验 |
|
localStorage |
5MB |
永久存储,手动删除才失效 |
同源共享、持久化、仅字符串、不随请求携带 |
长期本地配置、静态偏好设置 |
|
sessionStorage |
5MB |
当前标签页关闭即销毁 |
标签隔离、跨标签不共享、会话级临时存储 |
单次页面会话临时数据、表单暂存 |
|
IndexedDB |
无上限(受设备限制) |
永久存储 |
异步数据库、支持二进制/对象、事务操作、高性能 |
海量本地数据、离线缓存、文件存储 |
|
CacheStorage |
无上限 |
手动管理过期 |
PWA专属、缓存静态资源/接口响应、配合Service Worker |
PWA离线访问、静态资源缓存优化 |
1.7.1 存储核心开发规范(必背)
1. 敏感数据禁止存 localStorage/sessionStorage,可被前端代码直接读取,存在XSS泄露风险,Token等凭证优先存 Cookie(开启HttpOnly);
2. 所有本地存储仅同源生效,跨域名无法读取;
3. localStorage 适合静态配置,临时会话数据优先 sessionStorage,海量数据必用 IndexedDB;
4. Cookie 容量极小,禁止存储大量业务数据,仅用于身份校验。
1.8 BOM 高频面试易错总结(必背)
1. window 全局坑:var 变量挂载 window,let/const 不挂载,严格模式杜绝全局变量泄露;
2. 跳转差异:assign 保留历史、replace 清空当前历史、reload 刷新页面;
3. 路由底层:history API 无刷新修改URL,刷新404需服务端重定向,hash模式无需服务端配置;
4. 存储选型核心:身份凭证用Cookie、永久配置用localStorage、临时数据用sessionStorage、海量离线数据用IndexedDB;
5. 权限类API限制:剪贴板、通知、地理位置API均需用户手动授权,且仅HTTPS环境生效(本地localhost除外);
6. 页面滚动规范:优先使用 scrollTo 平滑滚动,避免原生滚动样式冲突。
模块 2 DOM 文档对象模型(完整版·底层+实战+面试)
DOM(Document Object Model)文档对象模型,是浏览器将 HTML 文档结构化解析后的树形对象模型,是 JS 操作页面结构、元素、样式、事件的唯一标准接口。DOM 将每一个 HTML 标签、文本、注释都封装为独立节点对象,挂载在文档树中,支持增删改查、样式操作、事件绑定、状态监听,是前端页面交互的核心基础。
核心特性:跨浏览器标准、树形层级结构、节点类型区分、支持实时操作、可结合事件实现动态交互;
核心作用:JS 与 HTML 通信的桥梁。
2.1 DOM 核心基础:节点体系(全网最全分类)
DOM 文档树由无数节点(Node)组成,所有节点均继承自 Node 基类,拥有统一的节点属性与方法,不同节点类型功能不同。
2.1.1 七大核心节点类型(nodeType 枚举)
通过 nodeType 判断节点类型(面试高频),常用节点枚举如下:
-
文档节点(nodeType = 9):
document根节点,整页文档唯一顶层节点 -
元素节点(nodeType = 1):所有 HTML 标签(div/p/span),可操作样式、属性、事件
-
文本节点(nodeType = 3):标签内文本内容、空白换行、空格,无标签属性
-
注释节点(nodeType = 8):HTML 注释内容,仅做备注,无渲染效果
-
文档类型节点(nodeType = 10):
<!DOCTYPE html>文档声明 -
文档片段节点(nodeType = 11):
DocumentFragment,虚拟内存节点,无渲染、优化批量DOM操作 -
属性节点(nodeType = 2):标签原生属性(class/id/src),现代JS已废弃直接操作
2.1.2 核心节点属性(区分元素/文本节点)
(1)通用节点属性(所有节点都具备)
nodeType:节点类型枚举值(核心判断依据)
nodeName:节点名称(元素节点大写标签名、文本节点#text、文档节点#document)
nodeValue:节点值(文本/注释节点存内容,元素节点为 null)
parentNode:父节点(所有节点通用,顶层节点为 null)
childNodes:子节点集合(包含文本、注释、元素所有节点,动态集合)
(2)专属元素节点属性(仅标签节点可用)
tagName:大写标签名,等价于元素节点
nodeNamechildren:仅元素子节点集合,过滤文本/注释,开发常用firstElementChild / lastElementChild:首个/最后一个元素子节点previousElementSibling / nextElementSibling:上/下一个元素兄弟节点className:读写元素 class 类名(字符串格式)
classList:类名操作对象(add/remove/toggle/contains,高频开发)
id:读写元素 id 属性innerHTML:读写元素内部 HTML 结构(解析标签,存在 XSS 风险)
innerText:读写元素可视文本(自动过滤标签、合并换行、受样式影响)textContent:读写纯文本内容(保留原始文本、不受样式影响、性能更高)
2.1.3 静态集合 vs 动态集合(面试重难点)
DOM 查询返回的节点集合分为两种,核心差异是是否实时跟随页面DOM更新:
(1)静态集合 NodeList:querySelectorAll 返回,查询后固定快照,DOM 变化不更新,支持 forEach 遍历
(2)动态集合 HTMLCollection:getElementsByClassName/getElementsByTagName 返回,实时绑定DOM树,页面更新集合自动更新,无 forEach 原生方法
(3)开发规范:优先使用 querySelectorAll 静态集合,避免动态集合实时渲染带来的性能损耗和遍历bug。
2.2 DOM 元素查询与创建(全套实战API)
2.2.1 六大元素查询方法(选型规范)
// 1. 精准单元素查询(开发首选)
document.querySelector('#id / .class / 标签 / 复合选择器');
// 2. 多元素批量查询(静态集合)
document.querySelectorAll('div.item');
// 3. 动态标签查询(动态集合)
document.getElementsByTagName('div');
// 4. 动态类名查询(动态集合)
document.getElementsByClassName('box');
// 5. 精准ID查询(全局唯一,性能最高)
document.getElementById('app');
// 6. 表单特殊查询(返回表单元素集合)
document.forms;
2.2.2 DOM 节点创建与批量优化
常规创建节点会频繁触发页面渲染,DocumentFragment 是批量DOM操作最优解,在内存中完成所有操作后一次性渲染,极致优化回流重绘。
// 1. 基础节点创建
const div = document.createElement('div'); // 创建元素节点
const text = document.createTextNode('文本内容'); // 创建文本节点
// 2. 文档片段(批量渲染优化核心)
const fragment = document.createDocumentFragment();
// 批量添加节点(仅操作内存,不渲染页面)
for(let i = 0; i < 5; i++) {
const item = document.createElement('p');
item.innerText = `列表${i+1}`;
fragment.appendChild(item);
}
// 一次性挂载到页面,仅触发一次渲染
document.body.appendChild(fragment);
2.2.3 全套节点增删改替换API(全覆盖)
const parent = document.querySelector('.parent');
const child = document.createElement('span');
const target = document.querySelector('.target');
// 1. 新增节点
parent.appendChild(child); // 末尾追加
parent.prepend(child); // 头部前置插入
target.before(child); // 目标节点前面插入
target.after(child); // 目标节点后面插入
// 2. 替换节点
target.replaceWith(child); // 替换目标节点
// 3. 删除节点
target.remove(); // 直接移除自身(最简写法)
parent.removeChild(target); // 父节点移除子节点
// 4. 节点判断工具
target.isConnected; // 判断节点是否挂载到页面DOM树
parent.contains(target); // 判断是否包含目标子节点
2.3 DOM 属性与自定义属性(面试高频坑点)
2.3.1 原生属性 vs 自定义属性
-
原生标准属性:id/class/src/href/type 等,可直接通过元素对象读写,自动同步DOM
-
自定义属性:非标准属性,分为传统自定义属性和
data-*规范自定义属性
2.3.2 规范属性操作API
const dom = document.querySelector('.box');
// 1. 原生属性直接读写
dom.className = 'new-box';
dom.id = 'demo';
// 2. 通用属性操作(适配所有属性)
dom.setAttribute('title', '提示文本'); // 设置属性
dom.getAttribute('title'); // 获取属性
dom.removeAttribute('title'); // 删除属性
dom.hasAttribute('title'); // 判断是否存在属性
// 3. 规范自定义属性 data-*(推荐)
dom.dataset.name = 'js手册'; // 设置 data-name
console.log(dom.dataset.name); // 获取自定义属性
核心避坑:自定义属性不能直接通过 dom.xxx 读写,不会同步DOM;必须使用 setAttribute / dataset 操作。
2.4 样式操作与渲染底层(回流重绘核心)
2.4.1 行内样式与计算样式
DOM 样式分为行内样式(可读写)和计算后样式(只读), computedStyle 可获取css文件、style标签、行内所有样式。
const dom = document.querySelector('.box');
// 1. 读写行内样式(优先级最高,直接覆盖)
dom.style.width = '200px';
dom.style.backgroundColor = '#f5f5f5';
// 2. 获取最终计算样式(只读,包含所有css样式)
const style = getComputedStyle(dom);
console.log(style.width, style.color);
2.4.2 回流、重绘、合成(前端性能核心考点)
页面渲染三层机制,优先级:合成 > 重绘 > 回流,性能损耗逐级递增。
(1)回流
Reflow(重排版):元素布局尺寸/位置改变,浏览器重新计算页面布局,重新排版所有相关元素,性能损耗最大。 触发场景:宽高、边距、定位、字体大小、显示隐藏、新增删除DOM。
(2)重绘
Repaint:仅样式颜色/背景改变,不改变布局尺寸,无需重新排版,仅重新绘制像素,性能损耗较小。 触发场景:color、background、box-shadow、outline。
(3)合成
Composite:通过 GPU 分层渲染,仅触发图层合成,无回流无重绘,性能最优。 触发场景:transform、opacity 动画。
2.4.3 回流重绘优化方案(工程必用)
-
批量样式修改:统一修改 class 类名,不逐行修改 style 样式
-
DOM 离线操作:使用 DocumentFragment 批量渲染,减少页面重绘次数
-
高频动画启用 GPU 加速:优先使用 transform/opacity 实现动画
-
缓存 DOM 节点与布局属性,避免重复读取 offset/scroll 系列属性
-
脱离文档流渲染:弹窗、动画元素使用 fixed/absolute 定位,不影响普通文档流
2.4.4 常用布局尺寸属性(只读)
const dom = document.querySelector('.box');
// 元素可视尺寸(包含内边距、不含边框)
console.log(dom.clientWidth, dom.clientHeight);
// 元素占位尺寸(包含内边距+边框)
console.log(dom.offsetWidth, dom.offsetHeight);
// 元素滚动内容总尺寸
console.log(dom.scrollWidth, dom.scrollHeight);
// 元素相对父级偏移位置
console.log(dom.offsetTop, dom.offsetLeft);
// 滚动距离
console.log(dom.scrollTop, dom.scrollLeft);
2.5 DOM 事件机制(核心重难点+面试必考)
2.5.1 事件绑定三种方式
const btn = document.querySelector('button');
// 1. 行内绑定(废弃,结构行为耦合)
// <button onclick="fn()"></button>
// 2. 简单事件绑定(同一事件只能绑定一个处理函数,会覆盖)
btn.onclick = function() {};
// 解绑
btn.onclick = null;
// 3. 标准事件监听(开发首选,支持多绑定、可精准解绑)
btn.addEventListener('click', handleClick, false);
// 精准解绑(必须同名函数,匿名函数无法解绑)
btn.removeEventListener('click', handleClick);
2.5.2 完整事件流(捕获→目标→冒泡)
DOM 事件触发分为三个阶段,是事件委托、事件拦截的底层原理:
-
捕获阶段:事件从 window → document → 父级 → 目标元素 自上而下逐层捕获
-
目标阶段:事件到达触发目标元素,执行目标事件回调
-
冒泡阶段:事件从目标元素 → 父级 → document → window 自下而上逐层冒泡
绑定规则:addEventListener 第三个参数为 true 监听捕获阶段,false(默认)监听冒泡阶段。
2.5.3 三大事件拦截方法(高频面试区分)
// 1. preventDefault():阻止默认行为
// 适用:a标签跳转、表单提交、滚动默认事件、右键菜单
e.preventDefault();
// 2. stopPropagation():阻止事件冒泡/捕获,终止事件流传递
// 仅阻止层级传递,同元素其他监听仍会执行
e.stopPropagation();
// 3. stopImmediatePropagation():终极拦截
// 阻止事件传递 + 终止当前元素剩余所有同类型监听执行
e.stopImmediatePropagation();
2.5.4 事件委托(事件代理)工程核心
原理:利用事件冒泡机制,将子元素事件统一绑定到父元素,通过 e.target 判断触发源。
核心优势:减少事件绑定次数、节省内存、支持动态新增元素事件生效。
// 列表事件委托(适配静态+动态新增列表项)
const list = document.querySelector('.list');
list.addEventListener('click', (e) => {
// 精准匹配目标子元素
if(e.target.tagName === 'LI') {
console.log('点击列表项:', e.target.innerText);
}
});
2.5.5 高频事件类型与优化
-
鼠标事件:click、dblclick、mousedown/mousemove/mouseup、mouseover/mouseout(冒泡)、mouseenter/mouseleave(不冒泡)
-
键盘事件:keydown、keypress、keyup
-
表单事件:input、change、submit、focus、blur
-
触摸事件:touchstart/touchmove/touchend(移动端专属)
-
滚动事件:scroll,搭配 passive:true 优化滚动卡顿(禁止默认事件监听)
// 滚动性能优化:passive 不阻塞默认滚动行为
window.addEventListener('scroll', handleScroll, { passive: true });
2.5.6 自定义事件(组件通信方案)
支持自定义事件派发,实现父子组件、模块间解耦通信。
// 1. 创建自定义事件
const customEvent = new CustomEvent('update-data', {
detail: { name: 'JS手册', age: 20 }, // 自定义携带数据
bubbles: true, // 允许冒泡
cancelable: true // 允许取消
});
// 2. 绑定监听
document.addEventListener('update-data', (e) => {
console.log('自定义事件数据:', e.detail);
});
// 3. 派发事件
document.dispatchEvent(customEvent);
2.6 三大高性能观测API(现代前端核心)
均为异步微任务执行,不阻塞主线程,替代传统轮询监听,性能极致优化。
2.6.1 MutationObserver DOM 节点观测
监听DOM节点增删改、属性变化、文本变化,替代定时轮询。
const box = document.querySelector('.box');
// 创建观测实例
const observer = new MutationObserver((mutations) => {
console.log('DOM发生变化', mutations);
});
// 开启观测
observer.observe(box, {
childList: true, // 监听子节点增减
attributes: true, // 监听属性变化
characterData: true, // 监听文本变化
subtree: true // 监听所有后代节点
});
// 停止观测
// observer.disconnect();
2.6.2 IntersectionObserver 视口交叉观测
监听元素是否进入/离开可视区域,核心用于图片懒加载、上拉加载、曝光埋点、动画入场。
const imgList = document.querySelectorAll('.lazy-img');
const io = new IntersectionObserver((entries) => {
entries.forEach(item => {
if(item.isIntersecting) {
// 元素进入视口,加载图片
const img = item.target;
img.src = img.dataset.src;
// 加载后停止观测
io.unobserve(img);
}
});
});
// 遍历监听所有懒加载图片
imgList.forEach(img => io.observe(img));
2.6.3 ResizeObserver 尺寸观测
精准监听元素尺寸变化,替代 window.resize,支持单个元素独立监听。
const box = document.querySelector('.box');
const ro = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
console.log('元素尺寸变化:', width, height);
});
ro.observe(box);
// ro.disconnect();
2.7 Web Components 原生组件化规范
浏览器原生组件化方案,无需框架,实现样式隔离、结构复用、组件封装,是原生高阶开发核心。
2.7.1 四大核心组成
-
自定义元素 CustomElement:自定义HTML标签,支持生命周期
-
Shadow DOM:影子DOM,实现样式、结构完全隔离,不污染全局
-
template 模板:预定义HTML结构,内存存储,可重复复用
-
slot 插槽:实现组件内容分发,支持自定义嵌套内容
2.7.2 极简实战组件
// 1. 定义自定义组件类
class CustomCard extends HTMLElement {
constructor() {
super();
// 开启影子DOM隔离
const shadow = this.attachShadow({ mode: 'open' });
// 组件模板
shadow.innerHTML = `
<style>
.card { padding: 20px; border: 1px solid #eee; border-radius: 8px; }
</style>
<div class="card">
<slot name="title">默认标题</slot>
<slot>默认内容</slot>
</div>
`;
}
}
// 2. 注册自定义标签
customElements.define('custom-card', CustomCard);
// 3. 页面使用:<custom-card></custom-card>
2.8 DOM 高频面试易错总结(必背)
-
节点集合差异:querySelectorAll 静态快照、getElementsByXXX 动态实时集合,开发优先用静态集合。
-
文本属性区别:innerText 受样式影响、会重排;textContent 纯文本、性能更高、推荐优先使用。
-
事件流核心:默认冒泡触发,捕获自上而下、冒泡自下而上,事件委托依托冒泡实现。
-
拦截方法区分:preventDefault 阻默认行为、stopPropagation 阻冒泡、stopImmediatePropagation 全拦截。
-
渲染性能优先级:合成(transform/opacity) > 重绘(color/bg) > 回流(布局尺寸),动画优先GPU合成。
-
观测API优势:异步微任务执行,不阻塞主线程,替代轮询,适配懒加载、尺寸监听、DOM变化监听。
-
Web Components核心:Shadow DOM 实现原生样式隔离,是无框架组件化的核心方案。
-
批量DOM优化:必须使用 DocumentFragment 离线渲染,避免频繁操作DOM引发多次回流重绘。
模块 3 网络、二进制、文件处理(完整版·底层+实战+面试)
网络请求是前端与服务端数据交互的核心,二进制与文件处理是前端高阶能力,涵盖接口请求、跨域通信、长连接、二进制解析、本地文件读写、文件上传下载等业务高频场景,也是中高级面试核心重难点。本模块全覆盖原生API底层原理、实战代码、易错点与工程规范。
3.1 原生 AJAX(XHR 底层核心)
AJAX(异步 JavaScript 和 XML)核心作用:无刷新与服务端异步通信,是所有前端网络请求的底层基石,Fetch、Axios 均基于 XHR 二次封装。
3.1.1 XHR 五大就绪状态(readyState 面试必背)
XHR 通过 readyState 标识请求生命周期,共5个状态,全程不可逆递进:
-
readyState = 0:未初始化,创建 XHR 实例,未调用 open()
-
readyState = 1:请求已建立,已调用 open()、未调用 send(),可配置请求头
-
readyState = 2:请求已发送,响应头、响应状态返回,响应体未接收
-
readyState = 3:数据接收中,分段返回响应数据,部分内容可读取
-
readyState = 4:请求全部完成,数据接收完毕,可正常读取响应结果
核心规则:仅 readyState === 4 代表请求完整结束,可处理业务逻辑。
3.1.2 状态码层级区分(易错点)
1. xhr.status:HTTP 响应状态码(200/404/500/304),服务端返回的标准状态
2.xhr.readyState:请求本地生命周期状态,与服务端无关
3.1.3 XHR 完整实战(GET/POST/文件上传)
// 1. 基础 GET 请求
function xhrGet(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 初始化请求:请求方式、地址、是否异步
xhr.open("GET", url, true);
// 监听状态变化
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 请求完成
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject("请求失败:" + xhr.status);
}
}
};
// 超时配置 10s
xhr.timeout = 10000;
xhr.ontimeout = () => reject("请求超时");
// 发送请求
xhr.send(null);
});
}
// 2. 基础 POST 请求(传 JSON 参数)
function xhrPost(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
// 设置请求头
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
xhr.status >= 200 && xhr.status < 300
? resolve(JSON.parse(xhr.responseText))
: reject("请求失败");
}
};
xhr.send(JSON.stringify(data));
});
}
// 3. XHR 文件上传(带进度监听)
function uploadFile(url, file) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("file", file); // 塞入文件
xhr.open("POST", url);
// 上传进度监听
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
console.log("上传进度:", percent.toFixed(2) + "%");
}
};
xhr.onload = () => resolve(JSON.parse(xhr.responseText));
xhr.onerror = () => reject("上传失败");
xhr.send(formData);
});
}
3.1.4 XHR 核心特性与易错点
1. 支持同步/异步请求,同步请求会阻塞主线程,业务开发禁止使用
2. 可监听 upload/download 进度事件,适配大文件上传下载场景
3. 支持请求超时、请求终止(xhr.abort()),可取消 pending 状态请求
4. 兼容性极强,兼容所有浏览器,是低版本兼容方案首选
3.2 Fetch 现代化网络请求(ES6+ 标准)
Fetch 是浏览器原生标准化请求API,基于 Promise 实现,替代老旧 XHR,语法更简洁、异步友好,是现代项目默认请求方案。
3.2.1 核心特性与默认规则
1. 基于 Promise,支持 async/await 异步写法,无回调地狱
2. 默认不带 Cookie,需要手动配置 credentials 字段
3. 仅网络异常、断网会触发 reject,404/500 接口错误依旧 resolve
4. 响应体为流式读取,支持大文件分片、流式解析
3.2.2 核心参数配置(Request/Headers)
Fetch 第二个参数为配置对象,支持完整请求配置:请求方法、请求头、请求体、跨域凭证、缓存模式等。
// Fetch 完整配置实战
async function fetchDemo() {
const res = await fetch("https://xxx.com/api", {
method: "POST", // GET/POST/PUT/DELETE
headers: new Headers({
"Content-Type": "application/json",
"token": "xxx-xxx-xxx"
}),
body: JSON.stringify({ name: "test", age: 20 }), // 请求体
credentials: "include", // 跨域携带Cookie:include/ same-origin/ omit
cache: "no-cache", // 缓存策略
mode: "cors", // 跨域模式
redirect: "follow" // 重定向自动跟随
});
// 手动判断接口状态码
if (!res.ok) {
throw new Error("接口错误:" + res.status);
}
// 流式解析响应体
const data = await res.json(); // 解析JSON
// await res.text() // 解析文本
// await res.blob() // 解析二进制文件
// await res.formData() // 解析表单数据
console.log(data);
}
3.2.3 Fetch 工程封装(容错+超时+取消请求)
// 封装超时 + 取消请求 + 错误处理
function request(url, options = {}) {
// 取消请求控制器
const controller = new AbortController();
const { signal } = controller;
// 超时自动取消
const timer = setTimeout(() => controller.abort(), 10000);
return fetch(url, { ...options, signal, credentials: "include" })
.then(res => {
clearTimeout(timer);
if (!res.ok) throw new Error(`状态码${res.status}`);
return res.json();
})
.catch(err => {
clearTimeout(timer);
if (err.name === "AbortError") throw new Error("请求已取消/超时");
throw err;
});
}
// 使用:可手动取消请求
// const req = request("/api");
// controller.abort() // 手动终止
3.3 浏览器同源策略与跨域解决方案(面试核心)
3.3.1 同源策略核心规则
同源定义:协议、域名、端口 三者完全一致,任意一个不同即为跨域。
同源限制范围:
-
禁止 AJAX/Fetch 跨域请求接口
-
禁止读取跨域 DOM、Cookie、LocalStorage
-
禁止读取跨域脚本、资源数据
目的:防止恶意网站窃取用户数据,保障浏览器安全。
3.3.2 八大跨域解决方案(全覆盖+选型场景)
1. CORS 跨域资源共享(主流生产方案)
服务端配置响应头,浏览器自动放行跨域请求,无需前端改造,分为简单请求、预检请求。
简单请求:满足 方法(GET/POST/HEAD) + 无自定义请求头 + 无复杂body,直接放行,无预检。
预检请求OPTIONS:复杂请求先发 OPTIONS 试探请求,服务端校验通过后再发正式请求。
核心响应头:
-
Access-Control-Allow-Origin:允许跨域域名(* 代表全部,生产禁止) -
Access-Control-Allow-Credentials:是否允许携带Cookie -
Access-Control-Allow-Methods:允许的请求方法 -
Access-Control-Allow-Headers:允许的自定义请求头
2. JSONP(兼容老旧浏览器,仅GET请求)
利用 script 标签不受同源限制的特性,动态创建 script 标签请求接口,通过全局回调函数接收数据。
缺陷:仅支持 GET 请求、存在 XSS 风险、不支持 post/请求体。
3. Nginx 反向代理(生产常用)
本地启动 Nginx 服务,统一转发前端请求,同源访问 Nginx,Nginx 跨域请求服务端,规避浏览器跨域限制,适配所有请求场景。
4. Node 中间件代理(开发环境首选)
基于 http-proxy-middleware,Vue/React 项目开发环境默认方案,前端请求本地服务,Node 服务代为转发接口。
5. postMessage 跨页面跨域通信
用于 iframe、新窗口跨域通信,不受同源限制,安全可控。
// 父页面发送消息
iframe.contentWindow.postMessage({ data: "测试" }, "*");
// 子页面监听消息
window.addEventListener("message", (e) => {
console.log("跨域数据:", e.data);
// e.origin 校验来源域名,防止恶意请求
});
6. WebSocket 长连接跨域
WebSocket 协议不受同源策略限制,可直接跨域建立双向长连接,适配实时通信场景。
7. 开启浏览器跨域调试(本地开发临时方案)
关闭浏览器安全策略,仅用于本地调试,禁止生产使用。
8. document.domain 主域名跨域
仅限同主域名、不同子域名场景,设置统一主域名实现跨域,适配企业多子站点。
3.4 WebSocket 双向长连接(实时通信核心)
HTTP 是单次短连接(请求-响应即断开),WebSocket 是双向持久长连接,一次握手、永久通信,服务端可主动推送数据,适配聊天、直播、实时日志、大屏数据更新场景。
3.4.1 核心特性
1. 基于 TCP 协议,可靠性高、低延迟
2. 不受同源策略限制,天然支持跨域
3. 连接状态可控、支持心跳保活、断线重连
4. 数据体积小、开销低,优于轮询方案
3.4.2 原生 WebSocket 完整实战(心跳+重连)
class WSClient {
constructor(url) {
this.url = url;
this.ws = null;
this.heartTimer = null; // 心跳定时器
this.reconnectTimer = null; // 重连定时器
this.heartTime = 30000; // 30s心跳
this.reconnectDelay = 5000; // 5s重连间隔
this.init();
}
// 初始化连接
init() {
this.ws = new WebSocket(this.url);
this.bindEvent();
}
// 绑定事件
bindEvent() {
// 连接成功
this.ws.onopen = () => {
console.log("WebSocket连接成功");
this.startHeart(); // 开启心跳
};
// 接收服务端消息
this.ws.onmessage = (e) => {
console.log("接收消息:", e.data);
// 重置心跳
this.startHeart();
};
// 连接关闭
this.ws.onclose = () => {
console.log("连接关闭,准备重连");
this.clearTimer();
this.reconnect();
};
// 连接报错
this.ws.onerror = (err) => {
console.error("连接异常:", err);
this.ws.close();
};
}
// 开启心跳保活
startHeart() {
this.clearTimer();
this.heartTimer = setInterval(() => {
this.ws.readyState === WebSocket.OPEN && this.ws.send("ping");
}, this.heartTime);
}
// 断线重连
reconnect() {
this.reconnectTimer = setTimeout(() => {
this.init();
}, this.reconnectDelay);
}
// 清空定时器
clearTimer() {
clearInterval(this.heartTimer);
clearTimeout(this.reconnectTimer);
}
// 发送消息
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
}
// 使用
// const ws = new WSClient("ws://xxx.com/ws");
3.4.3 Socket.io 封装方案
原生 WebSocket 需手动处理心跳、重连、断线、房间通信,Socket.io 是成熟封装库,内置重连、心跳、房间、广播、降级轮询,适配所有浏览器,是业务开发首选。
3.5 前端二进制完整体系(ArrayBuffer/Blob/File)
二进制体系是文件上传、下载、预览、分片传输、图片压缩、音视频处理的底层基础,六大核心对象层层关联,是前端高阶必备知识。
3.5.1 六大核心二进制对象层级关系
-
ArrayBuffer:原始二进制缓冲区,存储原始字节数据,不可直接读写,是所有二进制的底层载体
-
TypedArray:定型数组(Uint8Array/Int32Array等),视图层,用于读写 ArrayBuffer 数据
-
DataView:灵活视图,支持任意字节、任意类型读取二进制,适配复杂二进制解析
-
Blob:二进制大对象,封装 ArrayBuffer,支持文件类型、切片、生成URL,前端文件核心对象
-
File:继承自 Blob,专门代表本地文件,携带文件名、大小、修改时间等文件信息
-
FileReader:文件读取器,解析 Blob/File,转为 base64/文本/ArrayBuffer
3.5.2 核心转换实战(全覆盖)
// 1. ArrayBuffer 与 TypedArray 互转
const buffer = new ArrayBuffer(8); // 8字节缓冲区
const uint8 = new Uint8Array(buffer);
uint8[0] = 10;
console.log(buffer);
// 2. ArrayBuffer 转 Blob
const blob = new Blob([buffer], { type: "application/octet-stream" });
// 3. Blob 转 File
const file = new File([blob], "test.bin", { type: blob.type });
// 4. Blob 转 Base64(图片预览常用)
function blobToBase64(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = () => resolve(reader.result);
});
}
// 5. Base64 转 Blob
function base64ToBlob(base64) {
const arr = base64.split(",");
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) u8arr[n] = bstr.charCodeAt(n);
return new Blob([u8arr], { type: mime });
}
3.6 本地文件读写与下载实战
3.6.1 FileReader 四大读取方式
1. readAsText:读取为文本(txt/json文件)
2. readAsDataURL:读取为 Base64(图片预览首选)
3. readAsArrayBuffer:读取为原始二进制
4. readAsBinaryString:读取为二进制字符串(废弃,不推荐)
3.6.2 前端文件下载两种方案
// 1. Blob 文件下载(后端二进制流下载首选)
function downloadByBlob(blob, fileName) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
// 释放内存
URL.revokeObjectURL(url);
document.body.removeChild(a);
}
// 2. 链接直接下载(静态资源)
// <a href="xxx.pdf" download="文件名.pdf">下载</a>
3.6.3 大文件分片上传核心原理
利用 Blob.slice() 切割大文件,分批上传,服务端合并,解决大文件超时、失败重传问题。
3.7 网络&二进制高频面试总结(必背)
-
XHR核心:readyState4种有效状态,status为HTTP状态码,支持进度监听、超时、取消请求。
-
Fetch坑点:默认无Cookie、404/500不报错、需手动处理状态、基于Promise。
-
跨域选型:生产CORS,开发Node代理,实时通信WebSocket,页面通信postMessage,老旧兼容JSONP。
-
WebSocket优势:双向长连接、无轮询开销、跨域自由,需手动实现心跳保活与断线重连。
-
二进制层级:ArrayBuffer(底层存储)→TypedArray/DataView(读写)→Blob(文件封装)→File(本地文件)。
-
文件操作规范:图片预览用Base64、文件下载用Blob、大文件分片用slice切割、本地解析用FileReader。
-
性能优化:大文件禁止一次性读取,采用分片上传/流式解析,避免主线程阻塞、内存溢出。
第四部分 原型、面向对象、元编程、正则
模块 1 原型与原型链(底层核心+面试必考+完整实战)
原型与原型链是 JavaScript 面向对象的底层基石,是理解 JS 继承、this 指向、instanceof、类本质的核心,也是中高级面试、手写源码的高频重难点。JS 是基于原型的面向对象语言,不同于 Java/C++ 的类式继承,JS 所有对象的属性、方法复用、继承均依托原型链实现。
1.1 三大核心概念(彻底理清原型关系)
1.1.1 prototype 构造函数显式原型
每一个构造函数(普通函数、class 类)天生自带 prototype 属性,值为一个普通对象,称为显式原型。
核心作用:存储实例公共方法/属性,所有实例共享原型上的资源,实现代码复用、节省内存。
特性:
-
只有函数有
prototype,普通对象、数组、字符串无该属性 -
prototype 对象默认自带
constructor属性,指向当前构造函数 -
原型上定义的方法,所有实例可直接访问,不会重复创建
1.1.2 __proto__ 实例隐式原型
每一个对象实例(包含普通对象、数组、函数、正则等)天生自带 __proto__ 属性,称为隐式原型。
核心作用:指向所属构造函数的 prototype,是原型链查找的核心链路。
核心等式(永恒成立):实例.__proto__ === 构造函数.prototype
规范说明:__proto__ 是非标准废弃属性,开发中优先使用标准 API Object.getPrototypeOf() / Object.setPrototypeOf() 读写原型。
1.1.3 constructor 构造器属性
每个原型对象 prototype 自带 constructor,默认指向创建该原型的构造函数。
作用:通过实例查找自身构造函数,可用于类型溯源、实例判断。
高频坑点:手动重写 prototype 会覆盖 constructor,需要手动修复指向。
1.2 核心关系全景图(必背)
以自定义构造函数为例,梳理所有原型关联关系:
// 1. 自定义构造函数
function Person(name) {
this.name = name;
}
// 实例化对象
const p1 = new Person("张三");
// 核心等式 1:实例隐式原型 === 构造函数显式原型
console.log(p1.__proto__ === Person.prototype); // true
// 核心等式 2:原型的 constructor 指向构造函数
console.log(Person.prototype.constructor === Person); // true
// 核心等式 3:所有普通原型最终指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true
// 核心等式 4:Object 原型终点,顶层原型为 null
console.log(Object.prototype.__proto__ === null); // true
// 核心等式 5:函数也是对象,所有函数继承自 Function.prototype
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
1.3 原型链查找规则(底层核心)
当访问一个对象的属性/方法时,JS 会自动开启原型链逐层查找,规则固定且单向:
-
优先查找自身:先在当前实例对象自身属性中查找,找到直接返回
-
逐层向上查找原型:自身无则通过
__proto__找上级原型,依次向上 -
终点终止:查找到
Object.prototype.__proto__ === null终止,找不到返回undefined
核心特性:单向向上、不可向下回溯,原型链上的属性所有实例共享。
// 原型链查找实战
Person.prototype.sayHi = function() {
console.log("你好,我是" + this.name);
};
// 实例自身无 sayHi 方法,向上查找原型
p1.sayHi(); // 你好,我是张三
// 原型链顶层属性
console.log(p1.toString()); // 继承自 Object.prototype
console.log(p1.hasOwnProperty("name")); // true 自身属性
console.log(p1.hasOwnProperty("sayHi")); // false 原型属性
1.4 new 关键字底层四步原理(面试满分)
new 是创建实例、绑定原型的核心,底层固定四步执行,是手写 new 的理论基础:
-
创建空对象:在内存中开辟空间,创建一个全新的空普通对象
-
绑定原型:将空对象的
__proto__指向构造函数的prototype -
绑定 this 并执行:将构造函数内部 this 指向新对象,执行构造函数代码,挂载实例属性
-
返回实例:若构造函数无返回值、或返回基本类型,自动返回新对象;若返回引用类型(对象/数组),直接返回该引用值
1.5 五大 JS 继承方案(全覆盖+优缺点+选型)
1.5.1 原型继承(基础版)
原理:将子类原型指向父类实例,子类通过原型链继承父类属性和方法。
// 父类
function Parent(name) {
this.name = name;
this.arr = [1,2,3];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 子类
function Child(age) {
this.age = age;
}
// 原型继承核心
Child.prototype = new Parent();
// 修复 constructor 指向
Child.prototype.constructor = Child;
// 测试
const c1 = new Child(18);
c1.sayName(); // undefined(无父类属性)
缺陷:
-
无法给父类构造函数传参
-
所有子类实例共享父类引用属性,一个修改全部影响
1.5.2 构造函数继承(借用继承)
原理:子类内部通过 父类.call(this),借用父类构造函数,绑定子类 this。
function Child(name, age) {
// 借用父类构造函数
Parent.call(this, name);
this.age = age;
}
const c2 = new Child("李四", 20);
console.log(c2.name); // 李四
// c2.sayName(); // 报错:无法继承原型方法
优点:可传参、解决引用属性共享问题
缺陷:只能继承实例属性,无法继承原型方法,方法无法复用
1.5.3 组合继承(最常用经典继承)
原理:原型继承 + 构造函数继承结合,取长补短,解决前两种方案的所有问题。
function Child(name, age) {
Parent.call(this, name); // 继承实例属性、可传参、独立属性
this.age = age;
}
Child.prototype = new Parent(); // 继承原型方法
Child.prototype.constructor = Child; // 修复构造器
const c3 = new Child("王五", 22);
const c4 = new Child("赵六", 25);
c3.arr.push(4);
console.log(c3.arr); // [1,2,3,4]
console.log(c4.arr); // [1,2,3] 互不影响
c3.sayName(); // 王五 成功继承原型方法
优点:可传参、属性独立、可继承原型方法、复用性强
缺陷:父类构造函数执行两次(call 一次、new 一次),存在冗余属性
1.5.4 寄生组合继承(最优原生继承方案)
原理:通过 Object.create 创建纯净原型,避免父类构造函数重复执行,是原生 JS 最优继承方案,Class 继承底层基于此实现。
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 核心:创建纯净原型,指向父类原型,不执行父类构造
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// 完美继承属性+方法,无冗余执行
const c5 = new Child("小七", 21);
c5.sayName(); // 小七
优点:无冗余执行、属性独立、原型方法复用、无副作用
1.5.5 ES6 Class 继承(语法糖)
ES6 class 是原型继承的语法糖,底层为寄生组合继承,语法简洁、开发首选。
class Parent {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
// extends 实现继承,super 调用父类构造
class Child extends Parent {
constructor(name, age) {
super(name); // 必须优先调用 super,绑定父类属性
this.age = age;
}
}
const c6 = new Child("小八", 19);
c6.sayName(); // 小八
1.6 instanceof 底层原理与手写实现
1.6.1 核心规则
A instanceof B:判断B 的 prototype 是否出现在 A 的原型链上,返回布尔值。
特点:顺着实例 __proto__ 逐层向上查找,找到则为 true,查到 null 为 false。
1.6.2 手写 instanceof(面试必写)
function myInstanceof(instance, constructor) {
// 基础类型直接返回 false
if (typeof instance !== "object" || instance === null) return false;
// 获取实例隐式原型
let proto = Object.getPrototypeOf(instance);
// 逐层向上查找原型链
while(proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 测试
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof({}, Array)); // false
1.7 原型高频易错点与避坑总结
-
属性遮蔽:实例自身属性与原型属性同名时,优先读取自身属性,原型属性被遮蔽
-
引用类型原型属性坑:原型上禁止定义引用类型属性(数组/对象),会被所有实例共享修改,污染数据
-
constructor 丢失:手动重写 prototype 会覆盖 constructor,必须手动修复指向
-
原型链终点:所有普通对象原型链最终指向 Object.prototype,顶层为 null
-
函数与对象原型差异:函数有 prototype+__proto__,普通对象只有 __proto__
-
继承选型规范:原生开发用寄生组合继承,业务开发统一用 ES6 Class extends
1.8 原型链完整实战案例
// 1. 原型属性共享与遮蔽
function Student() {}
Student.prototype.school = "北京大学";
const s1 = new Student();
const s2 = new Student();
console.log(s1.school, s2.school); // 共享原型属性
// 实例同名属性遮蔽原型属性
s1.school = "清华大学";
console.log(s1.school); // 自身属性:清华大学
console.log(s2.school); // 原型属性:北京大学
// 2. 判断属性来源
console.log(s1.hasOwnProperty("school")); // true 自身属性
console.log(s2.hasOwnProperty("school")); // false 原型属性
// 3. 遍历原型链所有属性
for(let key in s1) {
console.log(key); // 遍历自身+原型可枚举属性
}
模块 2 元编程(底层劫持)
元编程核心定义:对语言底层本身的行为进行劫持、改写、拓展,区别于普通业务逻辑编程。普通编程是操作数据,元编程是劫持对象、函数、属性的底层默认规则,是Vue响应式、状态管理、数据校验、框架底层的核心基石。
JS元编程体系核心包含四大模块:Object.defineProperty、Proxy拦截器、Reflect标准化操作、内置Symbol元符号。
2.1 Object.defineProperty(Vue2 响应式底层)
核心作用:精准劫持对象单个属性的读取、赋值行为,可配置属性的权限、特性,是ES5原生属性描述符方案。
2.1.1 两大属性描述符分类
对象属性分为数据描述符和存取描述符,二者互斥,不能同时混用:
-
数据描述符:控制属性值本身,包含4个配置项 value:属性默认值 writable:是否可修改(默认false,只读) enumerable:是否可枚举(默认false,遍历不生效) configurable:是否可删除、可重新配置(默认false,永久锁定)
-
存取描述符:劫持属性读写行为,核心用于响应式 get():读取属性时自动触发,返回值为最终读取结果 set(val):赋值属性时自动触发,接收新值,可做拦截校验 enumerable/configurable:同数据描述符
2.1.2 完整实战代码(响应式极简实现)
// 原生对象
const obj = { name: "JS", age: 20 };
// 劫持单个属性读写
Object.defineProperty(obj, "name", {
enumerable: true, // 允许遍历
configurable: true, // 允许修改/删除
// 读取劫持
get() {
console.log("读取name属性");
return this._name || "默认JS";
},
// 赋值劫持
set(val) {
console.log("修改name属性:", val);
// 可做数据校验、拦截、更新视图(响应式核心)
if (!val) return console.error("名称不能为空");
this._name = val;
}
});
// 测试读写劫持
console.log(obj.name); // 读取name属性 → 默认JS
obj.name = "JavaScript"; // 修改name属性: JavaScript
console.log(obj.name); // JavaScript
// 配置只读属性
Object.defineProperty(obj, "id", {
value: 1001,
writable: false,
enumerable: true
});
obj.id = 2002; // 静默失效(严格模式报错)
console.log(obj.id); // 1001
// 配置不可枚举属性
Object.defineProperty(obj, "secret", {
value: "私密数据",
enumerable: false
});
console.log(Object.keys(obj)); // ['name', 'age', 'id'] 无法遍历secret
2.1.3 核心缺陷(Vue2 硬伤)
-
仅劫持单个属性,无法监听对象新增、删除属性,需手动重写$set/$delete
-
无法监听数组下标修改、数组长度变更,Vue2需重写7个数组变异方法
-
仅支持对象属性,不支持集合(Map/Set)、数组整体劫持
-
层级嵌套对象需递归遍历劫持,性能开销大
2.2 Proxy 代理拦截(ES6 元编程核心、Vue3 底层)
Proxy 是对象整体代理,无需遍历属性,可劫持对象、数组、函数、集合的13种底层行为,完美弥补defineProperty缺陷,是现代元编程首选方案。
2.2.1 13种完整拦截器(全覆盖)
|
拦截器方法 |
劫持行为 |
适用场景 |
|---|---|---|
|
get |
读取属性(含深层、不存在属性) |
数据监听、默认值兜底 |
|
set |
响应式更新、数据校验 | |
|
deleteProperty |
删除属性(delete) |
禁止删除核心属性 |
|
has |
in 运算符判断属性存在 |
隐藏私有属性 |
|
getOwnPropertyDescriptor |
获取属性描述符 |
自定义属性权限 |
|
defineProperty |
新增/修改属性描述符 |
统一属性配置规则 |
|
ownKeys |
遍历对象(Object.keys/for...in) |
过滤私有属性 |
|
getPrototypeOf |
获取原型 |
原型劫持 |
|
setPrototypeOf |
修改原型 |
禁止篡改原型 |
|
isExtensible |
判断对象可拓展性 |
对象权限控制 |
|
preventExtensions |
锁定对象不可拓展 |
数据只读锁定 |
|
apply |
函数调用行为 |
函数参数拦截、防抖节流 |
|
construct |
new 实例化行为 |
构造函数参数校验 |
2.2.2 Proxy 全能实战(对象+数组+函数劫持)
// 原始数据(对象+数组)
const target = { name: "元编程", age: 18, list: [1,2,3] };
// 创建代理实例
const proxyTarget = new Proxy(target, {
// 1. 读取拦截
get(target, key) {
console.log("读取属性:", key);
// 深层属性递归代理(简易响应式)
const res = Reflect.get(target, key);
return typeof res === "object" && res !== null ? new Proxy(res, this) : res;
},
// 2. 赋值/新增拦截
set(target, key, val) {
console.log("修改属性:", key, val);
// 数据校验:年龄不能为负数
if (key === "age" && val < 0) {
console.error("年龄不能为负数");
return false;
}
// 允许新增属性、修改数组
return Reflect.set(target, key, val);
},
// 3. 删除属性拦截
deleteProperty(target, key) {
if (key === "name") {
console.error("禁止删除核心属性name");
return false;
}
return Reflect.deleteProperty(target, key);
},
// 4. 属性存在判断拦截
has(target, key) {
// 隐藏私有属性
if (key === "secret") return false;
return Reflect.has(target, key);
}
});
// 测试对象劫持
proxyTarget.age = 20; // 修改属性: age 20
proxyTarget.age = -5; // 年龄不能为负数(拦截失败)
console.log(proxyTarget.name); // 读取属性: name → 元编程
// 测试数组劫持(完美支持数组变更)
proxyTarget.list.push(4); // 读取属性:list、修改属性:list
proxyTarget.list[0] = 99;
// 测试删除拦截
delete proxyTarget.name; // 禁止删除核心属性name
// 5. 函数劫持 apply/construct
function testFn(a, b) {
return a + b;
}
const proxyFn = new Proxy(testFn, {
// 函数调用拦截
apply(target, ctx, args) {
console.log("函数调用,参数:", args);
// 参数预处理
args = args.map(Number);
return Reflect.apply(target, ctx, args);
},
// new实例化拦截
construct(target, args) {
console.log("实例化函数,参数:", args);
return Reflect.construct(target, args);
}
});
console.log(proxyFn("10", "20")); // 函数调用,参数: ['10','20'] → 30
2.2.3 Proxy 核心优势与局限
优势:
-
无需遍历属性,一键代理整个对象/数组/集合
-
支持监听属性新增、删除、数组变更、下标修改,弥补Vue2缺陷
-
支持函数、构造函数劫持,覆盖全场景元编程
-
分层拦截、逻辑解耦,性能优于defineProperty递归劫持
局限:
-
Proxy 无法被解构、扩展运算符完全穿透,存在少量边界场景
-
异步场景、原始值劫持需要特殊处理
-
ES6+ 特性,不兼容IE(现代项目无影响)
2.3 Reflect 标准化操作(Proxy 最佳搭档)
Reflect 是ES6推出的语言底层操作API,统一替代传统的对象操作方式,与Proxy拦截器一一对应,专门配合元编程使用。
2.3.1 核心优势
-
返回值标准化:所有方法返回布尔值,直接判断操作是否成功,无静默失败
-
函数式调用:摒弃传统命令式写法,统一API风格
-
与Proxy一一对应:13个方法完全匹配Proxy拦截器,完美闭环
-
修复传统语法的异常抛出问题,可优雅捕获操作失败
2.3.2 常用方法实战对比
const obj = { name: "Reflect", age: 20 };
// 1. 读取属性
console.log(Reflect.get(obj, "name")); // Reflect
// 读取不存在属性,默认返回undefined,不报错
console.log(Reflect.get(obj, "sex")); // undefined
// 2. 设置属性(返回布尔值,判断是否成功)
const setRes = Reflect.set(obj, "age", 21);
console.log("修改是否成功:", setRes); // true
// 3. 删除属性
const delRes = Reflect.deleteProperty(obj, "age");
console.log("删除是否成功:", delRes); // true
// 4. 判断属性是否存在
console.log(Reflect.has(obj, "name")); // true
// 5. 获取所有自身属性key
console.log(Reflect.ownKeys(obj)); // ['name']
// 6. 调用函数、绑定this
function sum(a, b) {
return a + b;
}
console.log(Reflect.apply(sum, null, [10, 20])); // 30
2.4 内置 Symbol 元符号(底层行为自定义)
JS内置十余个Symbol静态属性,可自定义对象、数组、迭代器的底层默认行为,属于高阶元编程,常用于改写语言原生规则。
2.4.1 高频核心元符号实战
// 1. Symbol.toPrimitive:自定义对象转原始值规则(优先级最高)
const user = {
name: "元编程",
// 劫持对象隐式转换
[Symbol.toPrimitive](hint) {
if (hint === "number") return 100;
if (hint === "string") return "自定义字符串";
return "默认值";
}
};
console.log(+user); // 100(数字转换)
console.log(`${user}`); // 自定义字符串(字符串转换)
// 2. Symbol.iterator:自定义迭代器(for...of遍历规则)
const customObj = {
list: [10,20,30],
// 让普通对象支持for...of遍历
[Symbol.iterator]() {
let index = 0;
const arr = this.list;
return {
next() {
return index < arr.length ? { value: arr[index++], done: false } : { done: true };
}
};
}
};
for (const item of customObj) {
console.log(item); // 10 20 30
}
// 3. Symbol.hasInstance:自定义instanceof判断规则
class CustomClass {
// 劫持instanceof运算
static [Symbol.hasInstance](instance) {
// 自定义规则:只要是数组就判定为实例
return Array.isArray(instance);
}
}
console.log([] instanceof CustomClass); // true
// 4. Symbol.toStringTag:自定义对象toString标签
const tagObj = {
[Symbol.toStringTag]: "CustomObject"
};
console.log(Object.prototype.toString.call(tagObj)); // [object CustomObject]
2.5 元编程高频面试总结(必背)
-
defineProperty核心:劫持单个属性读写,Vue2底层,无法监听新增/删除属性、数组变更,需递归劫持
-
Proxy核心优势:全局代理、支持13种行为拦截、监听新增/删除/数组、无需递归,Vue3核心原理
-
Reflect作用:标准化对象操作、返回布尔值、与Proxy一一配对,规避原生操作报错问题
-
元符号价值:改写JS底层默认转换、迭代、类型判断规则,实现语言层面自定义
-
响应式选型:现代项目优先Proxy+Reflect,废弃defineProperty递归方案,性能更强、场景全覆盖
-
边界避坑:Proxy代理后,原始对象与代理对象不相等,所有操作优先使用代理实例
模块 3 正则表达式 RegExp(完整版+底层语法+实战模板+面试题)
正则表达式(RegExp)是用于匹配、检索、替换、校验字符串的规则语法,可高效解决表单校验、文本处理、数据清洗、格式截取等业务场景,是前端开发、面试高频核心知识点。本章全覆盖正则修饰符、元字符、分组、断言、匹配模式、实战案例与避坑规则。
3.1 正则创建方式
3.1.1 字面量创建(开发首选)
语法:/匹配规则/修饰符,编译时机早、性能高,适合固定正则规则
// 匹配数字,全局匹配
const reg1 = /\d/g;
3.1.2 构造函数创建(动态正则)
语法:new RegExp(匹配字符串, 修饰符),支持动态拼接规则,适合规则可变场景,字符串内反斜杠需双重转义 \\
// 动态拼接正则规则
const rule = "\\d";
const reg2 = new RegExp(rule, "g");
console.log(reg1.test("123")); // true
3.2 六大核心修饰符(全局作用)
修饰符用于修改正则默认匹配模式,可组合使用,是正则基础核心
-
g(global)全局匹配:默认匹配到第一个结果即终止,加 g 后匹配所有结果,全局检索替换
-
i(ignoreCase)忽略大小写:匹配时不区分英文字母大小写
-
m(multiline)多行匹配:修改
^ $边界符规则,匹配每一行的行首、行尾 -
s(dotAll)单行匹配:让
.元字符匹配换行符 \n \r,默认 . 无法匹配换行 -
u(unicode)Unicode匹配:正确匹配 Unicode 字符、emoji、生僻汉字,解决特殊字符匹配异常
-
y(sticky)粘性匹配:强制从
lastIndex位置开始匹配,不回溯,精准定点匹配
// 修饰符组合实战
const str = "Abc123aBC456";
// 全局+忽略大小写匹配字母
const reg = /[a-z]/gi;
console.log(str.match(reg)); // ['A','b','c','a','B','C']
// s修饰符:.匹配换行
const newStr = "hello\nworld";
console.log(/.+/.test(newStr)); // false 默认不匹配换行
console.log(/.+/s.test(newStr)); // true 开启单行匹配
3.3 基础元字符(核心匹配符号)
3.3.1 边界元字符(定位匹配)
-
^:匹配行首,多行模式下匹配每一行开头
-
$:匹配行尾,多行模式下匹配每一行结尾
-
\b:单词边界,字母/数字/下划线与其他字符的间隔位置
-
\B:非单词边界,与 \b 相反
核心:^$ 配合使用可精准匹配完整字符串,表单校验必用(杜绝部分匹配)
3.3.2 预设类元字符(快捷匹配)
-
\d:匹配任意数字 [0-9]
-
\D:匹配任意非数字
-
\w:匹配单词字符(字母、数字、下划线)[A-Za-z0-9_]
-
\W:匹配非单词字符
-
\s:匹配空白字符(空格、制表符\t、换行\n\r)
-
\S:匹配非空白字符
-
.:匹配除换行外任意单个字符,s修饰符下可匹配换行
3.3.3 自定义字符集
-
[]:匹配括号内任意单个字符,
[abc]匹配a/b/c -
[a-z]:区间匹配,小写字母;
[A-Z]大写字母;[0-9]数字 -
[^]:取反匹配,
[^0-9]匹配非数字字符
// 边界精准匹配(表单校验核心)
const phone = "13812345678";
const wrongPhone = "13812345678999";
const phoneReg = /^1\d{10}$/;
console.log(phoneReg.test(phone)); // true
console.log(phoneReg.test(wrongPhone)); // false
// 字符集匹配
const str = "a1b2c3";
console.log(str.match(/[a-z]/g)); // ['a','b','c']
console.log(str.match(/[^0-9]/g)); // ['a','b','c']
3.4 量词(重复匹配规则)
量词用于限制前面一个规则的匹配次数,默认贪婪匹配
-
n+:匹配至少 1 次(>=1)
-
n*:匹配 0 次或多次(>=0)
-
n?:匹配 0 次或 1 次,亦可终止贪婪模式
-
n{x}:精准匹配 x 次
-
n{x,}:至少匹配 x 次
-
n{x,y}:匹配 x~y 次(闭区间)
// 量词实战
const numStr = "12345";
console.log(numStr.match(/\d{3}/)); // ['123'] 精准3次
console.log(numStr.match(/\d{2,4}/)); // ['1234'] 2-4次
console.log(numStr.match(/\d+/)); // ['12345'] 至少1次
3.5 分组与捕获(高阶核心)
3.5.1 普通捕获组 ()
作用:将多个规则整合为整体、限制量词作用范围、捕获匹配内容,可通过 $1-$99 反向引用
// 分组限定量词:匹配连续3位数字
const reg1 = /(\d{3})+/;
console.log(reg1.test("123456")); // true
// 捕获组取值
const nameStr = "姓名:张三";
const nameReg = /姓名:(.+)/;
const res = nameStr.match(nameReg);
console.log(res[1]); // 张三 获取分组捕获内容
3.5.2 非捕获组 (?:)
仅整合规则、不捕获内容、不占用分组索引,无需取值时优先使用,性能更高
const reg = /(?:\d{3})-\d{4}/;
console.log("123-4567".match(reg)); // 无额外捕获分组
3.5.3 命名捕获组(ES2018)
给分组命名,通过 groups 获取结果,语义清晰、避免索引错乱,复杂正则首选
const timeStr = "2026-06-17";
const timeReg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const res = timeStr.match(timeReg);
console.log(res.groups.year); // 2026
console.log(res.groups.month); // 06
3.5.4 反向引用 \1 $1
\1:正则内部引用第1个分组匹配内容;$1:replace 替换回调中引用分组内容
// 匹配重复字符
const repeatReg = /([a-z])\1/;
console.log(repeatReg.test("aa")); // true
console.log(repeatReg.test("ab")); // false
// replace $1 引用
const str = "hello-world";
console.log(str.replace(/(\w+)-(\w+)/, "$1$2")); // helloworld
3.6 贪婪与非贪婪匹配(高频坑点)
3.6.1 贪婪匹配(默认)
量词默认尽可能多匹配,从最大符合条件长度匹配
3.6.2 非贪婪匹配(? 修饰)
在量词后加 ?,尽可能少匹配,精准截取片段,解决过度匹配问题
const html = "<div>内容1</div><div>内容2</div>";
// 贪婪匹配:一次性匹配全部(错误)
console.log(html.match(/<div>.+<\/div>/)); // 匹配整段字符串
// 非贪婪匹配:分段精准匹配(正确)
console.log(html.match(/<div>.+?<\/div>/g)); // ['<div>内容1</div>', '<div>内容2</div>']
3.7 零宽断言(高阶核心、无消耗匹配)
断言用于条件预判,匹配符合前后规则的内容,不占用匹配字符、不消耗字符串,是精准筛选的核心方案
3.7.1 正向预查 (?=):后面必须是什么
3.7.2 负向预查 (?!):后面不能是什么
3.7.3 后行断言 (?<=):前面必须是什么
3.7.4 负向后行断言 (?<!):前面不能是什么
// 1. 正向预查:匹配后面带@的用户名
const emailStr = "test@163.com";
console.log(emailStr.match(/\w+(?=@)/)); // test
// 2. 负向预查:匹配不是png结尾的图片名
const imgStr = "test.jpg test.png";
console.log(imgStr.match(/\w+\.(?!png)\w+/g)); // ['test.jpg']
// 3. 后行断言:匹配@后面的域名
console.log(emailStr.match(/(?<=@).+/)); // 163.com
3.8 正则常用方法与字符串匹配API
3.8.1 正则实例方法
-
test():返回布尔值,校验字符串是否匹配规则(校验首选,性能高)
-
exec():返回匹配结果数组,可循环获取所有全局匹配内容
3.8.2 字符串正则方法
-
match():返回匹配结果,全局匹配返回数组,非全局返回单次结果
-
matchAll():返回所有全局匹配迭代器,支持分组取值,适合复杂批量匹配
-
replace():匹配替换,支持 $ 分组引用、回调函数精准替换
-
split():根据正则规则分割字符串
// replace 高阶回调替换(精准处理每一项)
const str = "a1b2c3";
const newStr = str.replace(/\d/g, (val) => Number(val) * 2);
console.log(newStr); // a2b4c6
// matchAll 批量获取分组内容
const listStr = "姓名:张三,姓名:李四";
const reg = /姓名:(?<name>[\u4e00-\u9fa5]+)/g;
const res = [...listStr.matchAll(reg)];
console.log(res.map(item => item.groups.name)); // ['张三','李四']
3.9 高频业务正则模板(直接复用)
// 1. 手机号校验(国内11位)
const phoneReg = /^1[3-9]\d{9}$/;
// 2. 邮箱校验
const emailReg = /^[\w.-]+@[\w.-]+\.\w+$/;
// 3. 6-16位密码(字母+数字组合)
const pwdReg = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,16}$/;
// 4. 纯中文校验
const chineseReg = /^[\u4e00-\u9fa5]+$/;
// 5. 身份证号(18位/15位兼容)
const idReg = /(^\d{15}$)|(^\d{17}[\dXx]$)/;
// 6. 去除首尾空格
const trimReg = /^\s+|\s+$/g;
// 7. 匹配URL链接
const urlReg = /^(http|https):\/\/.+$/;
// 8. 匹配整数/小数
const numberReg = /^-?\d+(\.\d+)?$/;
3.10 正则高频面试易错总结(必背)
-
默认匹配特性:正则默认贪婪匹配、单次匹配,需 g 修饰符才能全局匹配,m 修饰符才会开启多行边界匹配。
-
分组核心区别:普通分组可捕获+反向引用,非捕获组仅分组不取值,复杂正则优先用非捕获组优化性能。
-
断言特性:零宽断言只校验规则、不消耗字符,适合精准截取、条件筛选,不会影响原字符串结构。
-
转义规则:字面量正则特殊符号直接转义 \,构造函数正则需双重转义 \\。
-
match 坑点:非全局匹配返回完整匹配+分组,全局匹配仅返回匹配内容,无分组信息,批量分组匹配优先用 matchAll。
-
表单校验规范:必须用 ^$ 完整匹配,杜绝部分匹配通过(如手机号局部数字匹配成功的bug)。
-
贪婪避坑:标签匹配、片段截取必须用非贪婪匹配,避免过度匹配导致结果错误。
第五部分 Node.js 服务端 JavaScript
模块 1 模块化 CommonJS(Node.js 默认规范·完整底层详解)
CommonJS 是Node.js 默认的模块化规范(CJS),适用于服务端 Node 环境,同步加载模块、运行时解析,是 Node 项目底层模块化的核心基础。区别于浏览器端的 ESM,CommonJS 所有模块均为运行时加载、动态解析、同步执行,无编译时静态优化。
1.1 核心成员与基础语法
CommonJS 模块化体系包含三个核心全局成员:require、module、exports,每个 JS 文件都是一个独立模块作用域,模块内变量、函数默认私有,不会污染全局。
1.1.1 导出语法(module.exports / exports)
核心本质:exports 是 module.exports 的浅拷贝引用,初始指向同一个空对象,最终导出的永远是 module.exports,exports 仅为简写工具。
开发规范:优先使用 module.exports,避免单独赋值 exports 导致导出失效。
// 1. 基础导出方式1:exports 逐个挂载(推荐零散导出)
exports.name = "CommonJS";
exports.version = "1.0.0";
exports.getInfo = function () {
return "Node 模块化";
};
// 2. 基础导出方式2:module.exports 整体重写(推荐统一导出)
module.exports = {
name: "CommonJS",
version: "1.0.0",
getInfo() {
return "Node 模块化";
}
};
// 【高频坑点】exports 单独赋值失效
// 错误写法:切断 exports 与 module.exports 的引用关联
// exports = { a: 1 };
// 正确写法:整体赋值必须用 module.exports
1.1.2 导入语法(require)
require(路径) 为同步导入方法,运行时执行,支持相对路径、绝对路径、第三方模块名导入,返回值为模块导出的 module.exports 对象。
// 1. 导入自定义模块(相对路径,必须加 ./ /../)
const moduleDemo = require("./demo.js");
// 2. 导入核心内置模块(直接写模块名,无需路径)
const fs = require("fs");
const path = require("path");
// 3. 导入第三方 npm 模块(自动读取 node_modules)
const axios = require("axios");
// 4. 解构导入(常用)
const { name, getInfo } = require("./demo.js");
1.2 模块加载规则(优先级·面试必考)
require 导入模块时,会按照固定优先级查找模块,顺序如下:缓存模块 > 内置核心模块 > node_modules 第三方模块 > 自定义相对/绝对路径模块
-
模块缓存(最高优先级):模块第一次加载后会被存入内存缓存,后续重复 require 直接读取缓存,不会重复执行模块代码
-
内置核心模块:Node 原生模块(fs/path/http 等),优先级高于第三方模块,无需下载直接使用
-
node_modules 第三方模块:逐层向上查找当前目录、上级目录的 node_modules,直至根目录
-
自定义路径模块:识别 ./ ../ / 路径,默认优先加载 .js/.json/.node 文件,可省略后缀
1.3 模块缓存机制(核心底层)
CommonJS 核心特性:模块单例缓存,所有导入共享同一个模块实例,全局唯一,大幅提升加载性能。
1.3.1 缓存特性实战
// demo.js(被导入模块)
console.log("模块执行");
let count = 0;
count++;
module.exports = { count };
// index.js(主模块)
// 多次导入,模块代码仅执行一次
const m1 = require("./demo");
const m2 = require("./demo");
console.log(m1.count); // 1
console.log(m2.count); // 1
console.log(m1 === m2); // true 同一缓存实例
1.3.2 清除缓存(极少用·了解即可)
// 删除指定模块缓存
delete require.cache[require.resolve("./demo.js")];
// 重新加载模块
const m3 = require("./demo");
1.4 循环依赖处理机制(高频面试题)
CommonJS 支持模块循环依赖,不会报错,核心原理:模块未加载完成时,会先导出已初始化的部分内容,后续继续加载剩余代码。
1.4.1 循环依赖实战案例
// a.js
console.log("a 开始执行");
const b = require("./b");
console.log("a 获取 b 数据:", b.num);
exports.num = 100;
console.log("a 执行完毕");
// b.js
console.log("b 开始执行");
const a = require("./a");
console.log("b 获取 a 数据:", a.num); // undefined(a未导出完成)
exports.num = 200;
console.log("b 执行完毕");
// 执行结果:
// a 开始执行
// b 开始执行
// b 获取 a 数据: undefined
// b 执行完毕
// a 获取 b 数据: 200
// a 执行完毕
1.4.2 核心原理总结
-
模块加载采用深度优先、边加载边导出策略
-
循环依赖时,未加载完的模块会返回空的导出对象,不会阻塞程序
-
仅能获取模块加载完成前的导出内容,未执行的导出逻辑无法获取
-
业务开发需尽量规避循环依赖,避免数据获取异常
1.5 __dirname / __filename 路径变量
CommonJS 模块内置两个全局路径变量,用于处理文件绝对路径,ESM 模块无此变量(需手动实现)。
// __dirname:当前文件所在目录的绝对路径
console.log(__dirname);
// __filename:当前文件的完整绝对路径(含文件名)
console.log(__filename);
// 路径拼接(跨平台兼容)
const filePath = require("path").join(__dirname, "test.txt");
1.6 CommonJS 与 ESM 核心区别(面试必背)
|
对比维度 |
CommonJS(CJS) |
ESM(ES6 模块化) |
|---|---|---|
|
运行时机 |
运行时解析,代码执行阶段加载 |
编译时静态解析,提前扫描模块 |
|
加载方式 |
同步加载,阻塞代码执行(适配服务端) |
异步加载,非阻塞(适配浏览器/Node) |
|
导出特性 |
值拷贝,导出后值不随原变量变化 |
实时引用,绑定原变量,支持动态更新 |
|
缓存机制 |
全局单例缓存,重复加载不重复执行 |
同样支持模块缓存,规则基本一致 |
|
循环依赖 |
支持,返回未完成导出内容 |
支持,依赖预解析,无空值问题 |
|
文件后缀 |
默认 .cjs,Node 默认 .js 均为 CJS |
默认 .mjs,package.json 配置 type:module 生效 |
|
路径变量 |
支持 __dirname、__filename |
无内置,需通过 import.meta.url 模拟 |
|
语法特性 |
动态导入,require 可写在任意代码位置 |
静态导入需写在顶层,动态 import() 支持运行时导入 |
模块 2 核心内置模块(完整版·底层原理+实战代码+面试考点)
Node.js 内置模块为原生开箱即用能力,无需第三方安装,是服务端开发、文件操作、服务搭建、进程管理的核心基础。所有内置模块加载优先级高于第三方模块,同步加载、性能极高,本章全覆盖高频8大内置模块,包含基础用法、底层特性、避坑点、实战案例。
2.1 path 路径处理模块(跨平台核心)
path 模块用于规范化、拼接、解析文件路径,解决 Windows(\)与 Linux/Mac(/)路径分隔符跨平台兼容问题,是文件操作必备模块。
2.1.1 核心高频API
-
path.join():路径拼接,自动适配系统分隔符、自动解析 ../ ./,规避拼接错误
-
path.resolve():解析绝对路径,始终返回完整绝对路径,以当前执行目录为基准
-
path.dirname():获取文件所在目录路径
-
path.basename():获取文件名(可去除后缀)
-
path.extname():获取文件后缀名
-
path.parse() / path.format():路径结构化解析/拼接
2.1.2 实战代码(可直接运行)
const path = require("path");
// 1. 路径拼接(推荐,跨平台兼容)
const filePath = path.join(__dirname, "static", "index.html");
console.log("拼接路径:", filePath);
// 2. 解析绝对路径
const absPath = path.resolve("./demo.txt");
console.log("绝对路径:", absPath);
// 3. 路径拆解
console.log("目录:", path.dirname(filePath));
console.log("文件名:", path.basename(filePath));
console.log("纯文件名:", path.basename(filePath, ".html"));
console.log("后缀:", path.extname(filePath));
// 4. 结构化解析与重构
const pathObj = path.parse(filePath);
console.log("路径结构化:", pathObj);
const rebuildPath = path.format(pathObj);
console.log("重构路径:", rebuildPath);
2.1.3 面试避坑点
join vs resolve 核心区别:join 仅拼接路径,相对路径基于当前文件;resolve 会逐层解析为绝对路径,基准为终端执行目录;项目路径统一使用 path.join(__dirname, 相对路径),杜绝路径找不到问题。
2.2 fs 文件操作系统模块(最常用核心)
fs 模块用于本地文件/文件夹的读写、创建、删除、权限操作,分为同步API、异步回调API、Promise异步API(fs.promises)三类,现代开发优先使用 fs.promises 避免回调地狱。
2.2.1 核心API分类
-
文件读写:readFile / writeFile / appendFile(读取/覆盖写入/追加写入)
-
文件夹操作:mkdir / rmdir / readdir(创建/删除/读取目录)
-
文件操作:rename / unlink / stat(重命名/删除文件/获取文件信息)
-
高性能流式读写:createReadStream / createWriteStream(大文件专属)
-
递归操作:递归创建目录、递归删除文件夹(Node14+ 原生支持 recursive)
2.2.2 全套实战代码
const fs = require("fs");
const fsPromises = fs.promises;
const path = require("path");
// 1. 异步读取文件(Promise 语法,推荐)
async function readFileDemo() {
try {
const res = await fsPromises.readFile(path.join(__dirname, "test.txt"), "utf8");
console.log("文件内容:", res);
} catch (err) {
console.log("读取失败:", err);
}
}
// 2. 覆盖写入文件(不存在则创建,存在则覆盖)
async function writeFileDemo() {
await fsPromises.writeFile("./test.txt", "Node fs 写入内容");
console.log("写入成功");
}
// 3. 追加写入(不覆盖原有内容)
async function appendFileDemo() {
await fsPromises.appendFile("./test.txt", "\n追加的新内容");
console.log("追加成功");
}
// 4. 文件夹操作(递归创建/删除)
async function dirDemo() {
// 递归创建多级目录
await fsPromises.mkdir("./a/b/c", { recursive: true });
// 读取目录文件列表
const list = await fsPromises.readdir("./a");
console.log("目录文件:", list);
// 递归删除空目录
await fsPromises.rmdir("./a/b/c", { recursive: true });
}
// 5. 删除文件
async function delFileDemo() {
await fsPromises.unlink("./test.txt");
console.log("文件删除成功");
}
// 6. 大文件流式拷贝(避免内存溢出)
function streamCopyDemo() {
const readStream = fs.createReadStream("./source.mp4");
const writeStream = fs.createWriteStream("./target.mp4");
readStream.pipe(writeStream);
readStream.on("end", () => console.log("拷贝完成"));
}
2.2.3 开发&面试规范
1. 小文件读写用 fs.promises 异步语法,简洁无回调地狱;
2. 大文件(图片/视频/压缩包)必须用流式读写,防止一次性加载占用内存溢出;
3. 创建多级目录必须加 recursive: true;
4. 同步API仅用于初始化配置场景,业务代码禁止使用阻塞同步方法。
2.3 stream 流模块(大文件高性能核心)
Stream 是 Node 高性能数据处理核心,采用分段读取、边读边写机制,无需将全部数据载入内存,专门解决大文件操作内存溢出问题,是 http 请求、文件拷贝、数据传输的底层原理。
2.3.1 四大流类型
-
可读流 Readable:读取数据(文件读取、接口响应)
-
可写流 Writable:写入数据(文件写入、接口请求)
-
双工流 Duplex:既可读又可写(TCP 通信)
-
转换流 Transform:读写过程中加工数据(压缩、加密、转码)
2.3.2 核心事件与实战
const fs = require("fs");
// 手动监听流事件(底层原理)
const readStream = fs.createReadStream("./test.txt", "utf8");
// 数据读取事件(分段触发)
readStream.on("data", (chunk) => {
console.log("分段数据:", chunk);
});
// 读取完成
readStream.on("end", () => {
console.log("文件读取完毕");
});
// 错误监听(必写,防止程序崩溃)
readStream.on("error", (err) => {
console.log("流读取错误:", err);
});
// 管道传输(核心API:自动读、自动写、自动关闭)
const writeStream = fs.createWriteStream("./copy.txt");
readStream.pipe(writeStream);
2.3.3 核心特性
流默认存在背压机制:自动匹配读写速度,写入速度过快时暂停读取,防止数据积压内存;pipe 方法会自动处理事件、异常、流关闭,是最稳定的流传输方式。
2.4 EventEmitter 事件发布订阅(Node 事件核心)
EventEmitter 是 Node 所有事件的底层基类,采用发布-订阅模式,所有内置模块(fs/http/stream)均继承此类,是异步事件驱动的核心。
2.4.1 核心API
-
on():持续订阅事件,触发多次执行
-
once():单次订阅,触发一次后自动销毁
-
emit():触发事件,可传递参数
-
off():取消事件订阅
-
error 事件:必须手动监听,否则程序直接崩溃
2.4.2 实战代码
const EventEmitter = require("events");
// 实例化事件对象
const eventBus = new EventEmitter();
// 1. 持续订阅
eventBus.on("login", (name, age) => {
console.log(`用户登录:${name},${age}岁`);
});
// 2. 单次订阅
eventBus.once("register", () => {
console.log("用户注册(仅触发一次)");
});
// 3. 触发事件
eventBus.emit("login", "张三", 20);
eventBus.emit("login", "李四", 22);
eventBus.emit("register");
eventBus.emit("register"); // 第二次不触发
// 4. 异常监听(必备)
eventBus.on("error", (err) => {
console.log("事件异常:", err);
});
2.4.3 面试考点
1. 事件订阅顺序执行,先订阅先触发;
2. error 为特殊事件,无监听直接退出进程;
3. once 底层会自动移除订阅,避免内存泄漏;
4. 可实现全局事件总线,解耦模块通信。
2.5 buffer 二进制缓冲区
Buffer 是 Node 处理二进制数据的核心类,无需 require 直接使用,专门处理文件、网络、二进制数据流,所有流传输的数据本质都是 Buffer。
2.5.1 核心特性与实战
// 1. 字符串转 Buffer(默认 utf8)
const buf1 = Buffer.from("Node 二进制");
console.log("Buffer 数据:", buf1);
console.log("转为字符串:", buf1.toString("utf8"));
// 2. 分配固定长度缓冲区
const buf2 = Buffer.alloc(10, 1); // 初始化填充1
// 3. 编码转换(utf8/base64/hex)
const base64Str = buf1.toString("base64");
console.log("base64编码:", base64Str);
const originBuf = Buffer.from(base64Str, "base64");
console.log("解码还原:", originBuf.toString());
2.5.2 核心考点
1. Buffer 为堆内存分配,固定长度,不可动态扩容;
2. 支持 utf8、base64、hex、ascii 编码互转;
3. 文件读写、网络请求的原始数据均为 Buffer,需手动转字符串。
2.6 http 网络服务模块
http 是 Node 原生搭建 Web 服务、处理 HTTP 请求的核心模块,无需第三方框架即可实现接口服务、静态资源服务器、请求转发。
2.6.1 极简服务实战
const http = require("http");
// 创建服务
const server = http.createServer((req, res) => {
// req:请求对象(参数、请求头、路径)
// res:响应对象(返回数据、设置响应头)
res.setHeader("Content-Type", "application/json;charset=utf-8");
// 返回 json 数据
res.end(JSON.stringify({ code: 200, msg: "服务响应成功" }));
});
// 监听端口启动服务
server.listen(3000, () => {
console.log("服务启动成功:http://localhost:3000");
});
2.6.2 核心考点
1. req.url 获取请求路径,req.method 获取请求方式;2. res.end() 必须执行,否则请求一直挂起;3. 原生 http 无路由、参数解析能力,Express/Koa 均基于 http 模块二次封装。
2.7 process 进程全局对象
process 是 Node 全局进程对象,无需引入,可获取进程信息、环境变量、命令行参数、监听进程事件,是工程化配置、跨环境适配核心。
2.7.1 核心实战
// 1. 环境变量(区分开发/生产环境)
console.log("环境变量:", process.env.NODE_ENV);
process.env.NODE_ENV = "development";
// 2. 命令行参数
console.log("命令行参数:", process.argv);
// 3. 进程退出
// process.exit(0); // 正常退出
// 4. 监听进程异常(全局兜底)
process.on("uncaughtException", (err) => {
console.log("全局异常捕获:", err);
});
2.7.2 核心用途
用于区分开发/生产环境、读取命令行启动参数、全局异常兜底、配置跨平台路径,是所有工程化工具的底层依赖。
2.8 child_process 子进程模块
Node 单线程无法处理 CPU 密集型任务,child_process 用于创建子进程,执行系统命令、脚本、解压计算任务,实现多核利用、任务分流。
2.8.1 四大核心方法区别
-
exec:缓存输出、适合小命令、回调获取结果
-
execFile:更安全、不解析 shell、性能更高、推荐使用
-
spawn:流式输出、适合大日志、长耗时任务
-
fork:专属启动 Node 子进程、支持进程通信
2.8.2 简单实战
const { exec, execFile, spawn } = require("child_process");
// 执行系统命令
exec("node -v", (err, stdout) => {
console.log("Node版本:", stdout);
});
// 流式执行命令(长耗时任务)
const child = spawn("npm", ["install"]);
child.stdout.on("data", (data) => {
console.log("安装日志:", data.toString());
});
2.9 内置模块高频面试总结
-
path模块:优先使用 join 拼接路径,resolve 用于获取绝对路径,解决跨平台路径兼容问题;
-
fs模块:小文件用 Promise 异步API,大文件必须流式读写,杜绝内存溢出;
-
stream核心:分段传输、背压机制、pipe 自动管理流状态,是大文件处理最优方案;
-
EventEmitter:事件驱动底层,必须监听 error 事件,once 单次订阅自动销毁;
-
Buffer:二进制数据载体,支持多编码转换,是所有流数据的基础;
-
子进程选型:小命令用 execFile、长耗时用 spawn、启动Node脚本用 fork;
-
开发规范:优先使用原生内置模块,性能优于第三方封装,减少项目依赖体积。
第六部分 内存、垃圾回收、性能优化
模块 1 内存存储模型(栈内存 + 堆内存·底层完整版)
JS 内存存储分为栈内存(Stack)和堆内存(Heap)两大核心区域,所有数据存储、变量赋值、拷贝差异、闭包内存、GC 回收均基于该模型实现,是理解 JS 数据机制、内存泄漏、性能优化的底层根基。JS 执行上下文、原始数据、引用地址全部存于栈,复杂引用数据实体存于堆,二者分工明确、配合工作。
1.1 栈内存(Stack)—— 线性高速内存
1.1.1 核心特性
-
存储内容:七大原始数据类型(String/Number/Boolean/Null/Undefined/Symbol/BigInt)、函数执行上下文、变量引用地址、基本指针
-
结构特点:后进先出(LIFO)线性结构,内存空间固定、连续,自动分配自动释放
-
访问速度:极高,无寻址开销,直接读取栈内存地址数据
-
生命周期:随执行上下文创建,上下文销毁(函数执行完毕)后,栈内存立即自动释放,无内存残留
-
大小限制:固定有限,无法存储大数据,过量会触发栈溢出(Stack Overflow)(如无限递归)
1.1.2 核心规则
原始类型变量在栈中直接存储真实值,变量赋值为值拷贝,新变量与原变量完全独立,修改互不影响。
1.2 堆内存(Heap)—— 动态无序内存
1.2.1 核心特性
-
存储内容:所有引用类型数据(Object/Array/Function/Date/RegExp 等)的真实数据实体
-
结构特点:无序、动态内存结构,空间不固定,可动态扩容,不会自动分配固定大小
-
访问速度:较慢,需要通过栈中存储的内存地址指针寻址访问
-
生命周期:不会随函数执行完毕自动释放,仅当无任何栈指针引用时,才会被 GC 垃圾回收机制回收
-
大小限制:容量大,可存储海量、复杂、嵌套大数据,无栈溢出风险
1.2.2 核心规则
引用类型数据实体存在堆内存,栈中仅存储「堆内存的唯一地址指针」。变量赋值为地址拷贝,多个变量共享同一个堆内存数据,修改任一变量会影响所有关联变量。
1.3 栈堆内存完整存储原理图解(文字版)
// 1. 原始类型 - 栈存值
let num = 100;
// 栈内存:num = 100(直接存真实数值)
// 2. 引用类型 - 栈存地址,堆存数据
let obj = { name: "JS" };
// 栈内存:obj = 0x0001(堆内存地址指针)
// 堆内存:0x0001 = { name: "JS" }(真实数据实体)
1.4 赋值拷贝底层差异(核心重难点)
1.4.1 原始类型:值拷贝(完全隔离)
新变量会在栈中开辟全新内存空间,复制原变量的值,二者无任何关联,修改互不影响。
let a = 10;
let b = a; // 栈内存值拷贝,b 单独开辟空间
b = 20;
console.log(a); // 10(原值不变,完全隔离)
1.4.2 引用类型:地址拷贝(数据共享)
新变量仅拷贝栈中的堆内存地址,不会创建新堆内存,多个变量指向同一个堆数据,相互影响。
let obj1 = { age: 18 };
let obj2 = obj1; // 栈内存地址拷贝,共享堆数据
obj2.age = 20;
console.log(obj1.age); // 20(原值被修改,数据共享)
1.5 深浅拷贝底层本质(衔接手写源码)
-
浅拷贝:仅拷贝栈内存的地址指针,堆内存数据共享,只解决一层引用嵌套问题,多层嵌套依然互相影响
-
深拷贝:递归开辟全新堆内存,完整拷贝所有层级数据,新旧变量栈、堆内存完全独立,彻底隔离
1.6 经典高频面试坑点
-
栈溢出场景:无限递归函数、死循环创建局部变量,导致栈内存占满报错;堆内存无溢出,只会触发内存占用过高
-
undefined 与 null 存储差异:undefined 是栈原始值;null 是空对象指针,栈中存储空地址,指向堆空内存
-
闭包内存滞留:闭包会让栈执行上下文不销毁,持续持有堆数据地址,导致堆内存无法被 GC 回收,引发内存泄漏
-
const 变量存储逻辑:const 锁定栈内存的指针/原始值,引用类型可修改堆内数据,无法修改栈中存储的堆地址
1.7 栈堆内存核心对比总结(必背)
|
对比维度 |
栈内存(Stack) |
堆内存(Heap) |
|---|---|---|
|
存储内容 |
原始值、执行上下文、地址指针 |
引用类型真实数据实体 |
|
内存结构 |
连续线性、固定大小 |
无序动态、可动态扩容 |
|
访问速度 |
快 |
慢(需指针寻址) |
|
赋值方式 |
值拷贝,完全隔离 |
地址拷贝,数据共享 |
|
释放机制 |
上下文销毁自动释放 |
无引用后 GC 主动回收 |
|
溢出风险 |
易栈溢出(无限递归) |
无溢出,易内存泄漏 |
模块 2 GC 垃圾回收机制(JS 底层核心·面试必考完整版)
GC(Garbage Collection,垃圾回收)是 JS 自动内存管理的核心机制,JS 开发者无需手动申请/释放内存,引擎会自动识别无效内存、回收空间。GC 仅针对堆内存生效(栈内存随执行上下文自动销毁),所有前端内存优化、内存泄漏排查均基于 GC 底层规则,是面试高频重难点。
2.1 垃圾回收核心判定标准
引擎判定「内存垃圾」的唯一标准:堆内存中的数据,无任何栈指针、全局变量、闭包、DOM 引用指向,无法被代码访问使用,即为垃圾数据,等待 GC 回收。
核心逻辑:只要存在有效引用,数据就会常驻堆内存,不会被回收,这也是内存泄漏的根本原因(无效数据持续被引用,无法释放)。
2.2 三大经典 GC 回收算法(迭代升级)
2.2.1 引用计数算法(初代·已淘汰)
核心原理:为每个堆内存数据维护一个引用计数器,记录当前被引用次数:
-
数据被新变量引用,计数器 +1
-
变量解除引用(赋值 null/销毁),计数器 -1
-
计数器为 0 时,立即判定为垃圾,同步回收内存
优势:回收及时、逻辑简单、内存利用率高
致命缺陷(被淘汰核心原因):无法解决循环引用
经典场景:两个对象互相引用,无外部引用,计数器永远不为0,内存永久无法回收,造成内存泄漏。
// 循环引用场景(引用计数算法无法回收)
let obj1 = {};
let obj2 = {};
obj1.target = obj2;
obj2.target = obj1;
// 解除外部引用
obj1 = null;
obj2 = null;
// obj1、obj2 互相引用,计数器均为1,永远无法被回收
2.2.2 标记清除算法(现代浏览器主流基础算法)
为解决引用计数的循环引用问题,引擎推出标记-清除(Mark-Sweep)算法,分为两个阶段,异步间断执行,不阻塞主线程:
-
标记阶段:从根对象(window/global)开始遍历,逐层扫描所有可访问的有效数据,对存活数据做标记
-
清除阶段:遍历堆内存,将无标记的未引用数据统一回收释放内存
核心优势:彻底解决循环引用问题,只要数据无法从根对象访问,无论是否内部循环引用,都会被回收。
核心缺陷:内存碎片化,回收后剩余内存零散不连续,大对象无法分配连续空间,造成内存浪费。
2.2.3 标记整理算法(优化版)
基于标记清除优化,解决内存碎片化问题,执行流程分为三步:标记、整理、清除:
-
标记:同标记清除,标记所有存活有效数据
-
整理:将所有存活数据向堆内存一端移动,集中存放,压缩零散空间
-
清除:清空末端整片未使用的垃圾内存,得到连续可用空间
优势:无内存碎片化,内存空间规整
缺陷:整理数据位置需要移动内存地址,执行速度慢、开销大,不会单独频繁使用
2.3 V8 引擎分代回收机制(实际生产使用·核心)
浏览器 V8 引擎不单独使用单一算法,而是采用分代回收策略,根据对象生命周期、存活时长,将堆内存分为新生代、老生代,针对性使用不同回收算法,兼顾回收效率和性能。
2.3.1 内存分代划分
-
新生代(年轻代):内存空间小(约 8M),存放短期存活、新建、临时对象(函数局部变量、临时渲染数据等),回收频率极高
-
老生代(老年代):内存空间大,存放长期存活、常驻对象(全局变量、闭包数据、DOM 对象、缓存数据等),回收频率低
2.3.2 新生代回收机制(Minor GC)
新生代采用 复制算法,将内存平分为两个等大空间:From(使用区)、To(空闲区),流程极简高效:
-
新建对象全部存入 From 空间
-
From 空间即将占满时,触发 Minor GC
-
标记 From 中所有存活对象,复制到 To 空闲区
-
清空 From 全部内存,互换 From/To 身份,完成回收
优势:速度极快、无内存碎片化,适配短期临时对象高频创建销毁场景
缺陷:内存利用率仅 50%,牺牲空间换时间
2.3.3 对象晋升机制(核心规则)
新生代对象满足以下任意条件,会晋升至老生代,不再参与新生代高频回收:
-
对象经过 2 次 Minor GC 依然存活(长期有效对象)
-
单个对象体积过大,新生代空间无法存放,直接晋升老生代
2.3.4 老生代回收机制(Full GC)
老生代对象存活久、数量少、体积大,采用 标记清除 + 标记整理 结合策略:
-
常规场景:使用标记清除,快速回收垃圾,性能开销低
-
内存碎片化严重、空间不足时:触发标记整理,压缩内存空间
Full GC 特点:执行速度慢、阻塞主线程,尽量减少触发频率,是前端性能优化的重点
2.4 GC 执行特性与性能特点
-
异步间断执行:GC 不会持续占用主线程,采用「分片执行」,空闲时回收,减少页面卡顿
-
分优先级执行:新生代 Minor GC 高频低耗时,老生代 Full GC 低频高耗时
-
Stop-The-World(STW):GC 执行期间会暂停 JS 代码执行,防止内存数据错乱,Full GC 的 STW 是页面卡顿的核心原因
2.5 高频面试必背总结
-
GC 只回收堆内存,栈内存随执行上下文自动释放,无需 GC 处理。
-
引用计数淘汰核心原因:无法解决循环引用内存泄漏。
-
标记清除解决循环引用,但存在内存碎片化问题;标记整理优化碎片但速度慢。
-
V8 分代回收:新生代复制算法(快、占空间),老生代标记清除/整理(稳、耗时)。
-
对象晋升规则:存活2次新生代GC、大体积对象直接进入老生代。
-
性能核心:尽量避免 Full GC,减少全局常驻变量、及时释放无效引用,降低内存占用。
-
内存泄漏本质:本该回收的垃圾数据,被无效引用持有,GC 无法识别回收。
模块 3 内存泄漏全部场景(最全实战版·成因+坑代码+解决方案)
内存泄漏核心定义:代码逻辑已不再使用的堆内存数据,因被无效引用持续持有,GC 无法回收,长期累积导致页面卡顿、内存飙升、页面崩溃。前端所有内存泄漏均来自「未主动释放的无效引用」,以下为生产环境 100% 全覆盖场景,包含经典坑点、错误代码、根治方案。
3.1 全局变量滥用(最基础、最高频泄漏)
泄漏成因
浏览器全局作用域(window)、Node 全局作用域变量生命周期与页面/进程一致,挂载在全局的对象、数组、变量,页面销毁前永远不会被 GC 回收。未声明直接赋值的变量会自动挂载全局,造成隐性内存泄漏。
错误代码(泄漏案例)
// 1. 未声明直接赋值,自动挂载 window 全局
function createGlobalVar() {
leakData = new Array(100000).fill("全局泄漏数据");
}
createGlobalVar();
// 2. 无意挂载大量全局缓存,页面卸载仍存在
window.cacheList = window.cacheList || [];
window.cacheList.push(...new Array(50000).fill("缓存数据"));
解决方案
-
开启严格模式 use strict,禁止未声明变量赋值,杜绝隐性全局变量;
-
临时数据、局部数据禁止挂载 window,仅模块、全局配置可少量挂载;
-
页面销毁、逻辑结束后,手动清空全局缓存:
window.cacheList = null。
3.2 定时器/延时器未清除(经典高频泄漏)
泄漏成因
setInterval 定时器为宏任务,会常驻主线程,若组件销毁、页面跳转、逻辑终止时未清除定时器,定时器会持续执行,且持续持有回调函数内部的变量、DOM、数据引用,导致内存无法回收。
错误代码(泄漏案例)
// 组件挂载开启定时器,销毁未清除
export default {
mounted() {
// 定时器持有大量数据和DOM引用
setInterval(() => {
const bigData = new Array(100000).fill("定时数据");
document.getElementById("box").innerText = bigData.length;
}, 1000);
}
}
解决方案
-
定时器必须存储标识,逻辑结束/组件销毁时
clearInterval清除; -
单次延时任务优先用
setTimeout,执行后自动销毁,无需手动清除; -
封装全局定时器工具,统一管理、自动清除,避免遗漏。
// 正确写法
export default {
data() {
return { timer: null }
},
mounted() {
this.timer = setInterval(() => {
// 业务逻辑
}, 1000);
},
beforeDestroy() {
// 组件销毁强制清除
clearInterval(this.timer);
this.timer = null;
}
}
3.3 DOM 事件未解绑 / 事件重复绑定
泄漏成因
DOM 元素绑定的点击、滚动、监听事件,会形成「JS 引用 DOM、事件函数引用数据」的闭环。若 DOM 元素被移除(remove),但事件未解绑,事件监听依然持有元素和数据引用,GC 无法回收 DOM 和相关内存。频繁重复绑定事件不解绑,会叠加泄漏。
错误代码(泄漏案例)
// 频繁渲染重复绑定事件,未解绑
function bindDomEvent() {
const box = document.getElementById("box");
box.addEventListener("click", () => {
console.log("点击事件");
});
}
// 每次渲染都会新增事件监听,不会覆盖
bindDomEvent();
bindDomEvent();
// DOM移除但事件未解绑
box.remove();
解决方案
-
事件绑定使用具名函数,便于销毁时解绑;
-
组件销毁、DOM 移除前,执行
removeEventListener解绑事件; -
优先使用事件委托,统一绑定父元素,减少监听数量、避免重复绑定。
3.4 闭包长期持有无效引用(隐蔽性最强)
泄漏成因
闭包会保留外层函数的作用域,若闭包函数长期常驻(定时器回调、事件回调、全局函数),会持续持有外层变量、DOM、大数据引用,即使外层函数执行完毕,内存也无法释放,是最难排查的隐性内存泄漏。
错误代码(泄漏案例)
function createClosure() {
// 超大内存数据
const bigData = new Array(200000).fill("闭包泄漏数据");
// 闭包常驻,持有bigData引用
return () => {
console.log(bigData.length);
};
}
// 全局持有闭包,bigData永久无法回收
window.closureFn = createClosure();
解决方案
-
避免全局常驻闭包函数,减少闭包嵌套层级;
-
无需使用的闭包函数,手动置空:
window.closureFn = null; -
闭包内不存储超大临时数据,数据使用后及时清空。
3.5 未清理的监听与订阅(Vue/React 高频)
泄漏成因
路由监听、全局事件总线、Vue watch 监听、Redux 订阅、WebSocket 监听,属于全局常驻订阅。组件销毁时未取消订阅,监听函数持续持有组件实例、数据引用,造成页面内存累积泄漏。
错误代码(泄漏案例)
// Vue 监听未销毁
watch(() => route.path, (newVal) => {
console.log("路由变化", newVal);
});
// 全局事件总线订阅未取消
eventBus.on("refresh", () => {
// 业务逻辑
});
解决方案
-
所有自定义监听、订阅必须在组件销毁生命周期中手动取消;
-
Vue 优先使用组件内 watch,自动随组件销毁,避免全局监听;
-
WebSocket 连接页面卸载时主动 close,清空监听事件。
3.6 缓存无上限累积(长期项目泄漏主力)
泄漏成因
自定义全局缓存、本地缓存、请求结果缓存、图片资源缓存,无过期清理、无最大数量限制,页面长期运行后,缓存数据无限累积,占用堆内存无法释放。
错误代码(泄漏案例)
// 无上限全局缓存,无限累加
const requestCache = {};
function getRequestData(url) {
if (!requestCache[url]) {
requestCache[url] = fetch(url).then(res => res.json());
}
return requestCache[url];
}
解决方案
-
所有自定义缓存设置最大容量 + 过期时间,自动淘汰旧缓存;
-
页面刷新、路由切换时,清空临时业务缓存;
-
使用 LRU 缓存策略,限制内存缓存上限。
3.7 iframe 嵌套残留内存
泄漏成因
动态创建的 iframe,执行移除操作时仅删除 DOM 节点,未销毁 iframe 内部的 window、全局变量、定时器、监听事件,iframe 上下文依然常驻内存,造成隐性泄漏。
解决方案
-
移除 iframe 前,手动清空内部上下文、停止所有任务;
-
清空 iframe src、置空引用,再删除 DOM 节点;
-
禁止频繁动态创建销毁 iframe,复用实例减少内存开销。
3.8 控制台日志残留与第三方插件泄漏
泄漏成因
开发环境 console 打印大量复杂对象、DOM 节点,浏览器控制台会缓存日志引用,导致数据无法被 GC 回收;埋点统计、弹窗、富文本、图表等第三方插件,初始化后未随组件销毁释放实例,持续占用内存。
解决方案
-
生产环境移除所有 console 打印,避免日志缓存;
-
第三方组件、图表实例,页面销毁时调用内置 destroy 方法;
-
定期清理无用第三方实例引用,手动置空。
3.9 内存泄漏通用排查 & 修复规范(必背)
-
排查工具:Chrome DevTools Memory 面板快照分析、Performance 实时内存监控、Lighthouse 内存检测;
-
核心修复原则:谁创建、谁管理、谁销毁,所有常驻引用必须有手动释放逻辑;
-
开发强制规范:禁止滥用全局变量、定时器/监听必销毁、闭包不存大数据、缓存必设上限、第三方实例必销毁;
-
终极判断标准:页面反复刷新、路由切换,内存平稳无持续上涨,即为无泄漏。
模块 4 前端性能优化(完整版·项目+面试全攻略)
前端性能优化核心目标:缩短首屏加载时间、减少页面卡顿、提升交互响应速度、降低内存占用、优化用户体验。优化贯穿「网络传输、资源加载、页面渲染、代码执行、内存管理、工程构建」全流程,以下为生产落地全覆盖方案,包含原理、实战方案、避坑规范。
4.1 核心性能指标(面试必背+检测标准)
主流浏览器核心性能指标,是衡量页面性能的唯一标准,也是项目优化的靶向目标:
-
FP(首次绘制):浏览器首次向屏幕绘制像素,标志页面开始渲染
-
FCP(首次内容绘制):首次绘制文本、图片、DOM节点,用户首次看到页面内容
-
LCP(最大内容绘制):页面最大可视元素渲染完成,核心优化指标,标准:<2.5s优秀、2.5~4s合格、>4s较差
-
FID(首次输入延迟):用户首次交互到浏览器响应的延迟,衡量交互流畅度
-
CLS(累积布局偏移):页面加载过程中元素偏移累积值,避免页面抖动,标准:<0.1优秀
-
TTI(可交互时间):页面完全加载、无长任务、可正常交互的时间
4.2 网络加载优化(首屏提速核心)
4.2.1 资源压缩与精简
-
代码压缩:JS/CSS/HTML 去除空格、注释、冗余代码,开启生产环境压缩打包;Webpack/Vite 内置压缩插件,杜绝线上源码暴露与冗余体积
-
静态资源压缩:图片无损压缩(TinyPNG)、视频压缩转码、字体文件精简,舍弃无用图片通道、冗余字体字形
-
开启Gzip/Brotli压缩:服务端配置压缩,文本资源(JS/CSS/HTML/JSON)压缩率可达60%~80%,大幅减少传输体积
4.2.2 资源加载策略优化
-
懒加载(延迟加载):图片、视频、iframe 开启原生懒加载
loading="lazy",视口外资源滚动到可视区域再加载,减少首屏资源请求量 -
预加载/预连接:关键资源使用
link preload预加载、preconnect预连接域名、prefetch预取后续页面资源,提升后续访问速度 -
资源按需加载:路由懒加载、组件异步加载、工具函数动态导入
import(),实现代码分割,首屏仅加载核心代码
4.2.3 请求优化
-
请求合并:合并高频小接口请求、合并雪碧图减少图片请求次数,规避浏览器同域名请求并发限制
-
防抖节流:输入框搜索、窗口resize、滚动监听、鼠标移动事件添加防抖节流,减少高频请求与函数执行次数
-
接口缓存:静态接口数据本地缓存、接口结果内存缓存,避免重复请求相同数据
-
禁用无用请求:开发环境禁用冗余埋点、日志请求,生产环境剔除无效接口调用
4.3 页面渲染优化(解决卡顿、抖动)
4.3.1 回流与重绘核心原理
-
重绘(Repaint):元素样式(颜色、透明度)改变,不影响布局,仅重新绘制像素,开销较小
-
回流(Reflow):元素宽高、位置、字体大小改变,触发页面布局重新计算,开销极大,前端卡顿主要元凶
-
层级叠加:频繁回流重绘会阻塞主线程,导致页面卡顿、交互延迟
4.3.2 回流重绘优化方案
-
批量修改DOM:避免逐行修改DOM样式,统一修改class类名,替代逐一样式修改
-
文档碎片优化:大量DOM节点创建时,使用
DocumentFragment批量插入,减少DOM渲染次数 -
脱离文档流修改:对频繁修改的元素,设置绝对定位脱离文档流,修改样式不会触发全局回流
-
开启硬件加速:通过
transform、opacity实现动画,二者仅触发重绘,不触发回流,且可开启GPU硬件加速 -
避免强制同步布局:杜绝「读取布局属性+修改样式」交替执行,统一先读所有布局属性、再统一修改样式
4.3.3 渲染层级优化
-
减少嵌套层级:DOM结构扁平化,避免过深嵌套,减少浏览器解析渲染开销
-
精简CSS样式:删除冗余CSS、避免通配符选择器、深层嵌套选择器,提升样式匹配效率
-
虚拟列表:长列表渲染(千条以上数据)使用虚拟列表,仅渲染可视区域DOM节点,杜绝一次性渲染大量DOM导致的白屏、卡顿
4.4 代码执行优化(减少主线程阻塞)
4.4.1 长任务优化(核心)
浏览器主线程单次任务执行超过 50ms 即为长任务,会阻塞渲染、交互,造成卡顿
-
任务分片:使用
requestIdleCallback、setTimeout拆分耗时循环、大数据遍历任务,利用浏览器空闲时间分片执行 -
Web Worker:CPU密集型任务(大数据计算、排序、解析)移入 Web Worker,脱离主线程执行,不阻塞页面渲染
4.4.2 JS代码优化
-
变量缓存:缓存频繁访问的DOM节点、对象属性、数组长度,避免重复查询、重复取值
-
杜绝无效循环:循环内不做变量声明、不做DOM查询、不做复杂计算,提前预处理数据
-
优化递归:尾递归优化、设置递归最大深度,避免栈溢出与内存占用过高
-
销毁无效逻辑:页面卸载、组件销毁时,清空定时器、监听事件、无效引用,避免内存累积
4.5 工程化构建优化(打包提速+线上性能)
4.5.1 Webpack/Vite打包优化
-
Tree-Shaking:开启ES6模块Tree-Shaking,剔除未使用的冗余代码、无效依赖,精简打包体积(仅支持ESM模块)
-
代码分割splitChunks:拆分业务代码、第三方依赖、runtime代码,实现按需打包、浏览器长效缓存
-
externals外部化:Vue/React/axios等大型依赖通过CDN引入,不打包进业务代码,减少包体积
-
资源预构建:Vite开启esbuild预构建,缓存依赖模块,提升本地启动与热更新速度
4.5.2 分包与懒加载
-
路由级别分包:不同路由页面单独打包,实现页面按需加载
-
组件级别懒加载:弹窗、抽屉、非首屏组件异步引入,减少首屏打包体积
-
工具函数拆分:公共工具按需引入,杜绝全量导入整个工具库
4.6 浏览器缓存优化(长效提速)
4.6.1 强缓存(无需请求服务器)
通过服务端响应头 Cache-Control、Expires 实现,资源未过期直接读取本地缓存,速度最快
-
静态资源(图片、字体、打包后的JS/CSS)设置长期强缓存
-
入口HTML设置无缓存,保证页面实时更新
4.6.2 协商缓存(请求校验更新)
通过 ETag/If-None-Match、Last-Modified/If-Modified-Since 校验资源是否更新,未更新返回304,无需重新下载资源
4.6.3 缓存策略规范
-
打包静态资源添加hash后缀,文件更新hash自动变化,触发资源更新;无更新则复用缓存
-
区分开发/生产缓存策略,开发环境禁用强缓存,保证实时调试
4.7 移动端专属优化
-
适配优化:使用rem/vw适配,避免固定像素,减少移动端布局重排
-
触摸优化:设置
touch-action: manipulation,禁用浏览器双击缩放,提升点击响应速度 -
图片适配:使用webp/avif高效图片格式,适配移动端分辨率,降低图片体积
-
减少层级:移动端DOM层级精简,避免过度渲染导致的滑动卡顿
4.8 性能排查工具与实战规范
4.8.1 核心排查工具
-
Chrome Performance:录制页面运行全程,定位长任务、回流重绘、卡顿节点
-
Chrome Memory:内存快照分析,排查内存泄漏、内存飙升问题
-
Lighthouse:一键检测页面性能、可访问性、最佳实践,生成优化报告
-
Network面板:分析资源加载耗时、请求阻塞、资源体积问题
4.8.2 开发强制优化规范
-
优先优化首屏核心指标LCP、TTI,保证用户首次访问体验
-
杜绝主线程长任务,所有耗时逻辑异步、分片、子线程处理
-
静态资源必做压缩、缓存、懒加载,从源头减少性能消耗
-
上线前必做性能检测,保证LCP、CLS、FID三大核心指标达标
第七部分 前端安全体系(完整版·原理+攻击+防御+实战)
前端安全是Web安全的核心一环,绝大多数用户侧漏洞、接口攻击、数据泄露均源于前端防护缺失。本模块全覆盖前端高频安全漏洞,包含漏洞原理、真实攻击场景、落地防御方案、避坑规范、实战代码,适配项目开发、面试答辩、线上漏洞排查全场景。前端安全核心目标:防止恶意脚本执行、防止身份伪造、防止数据窃取与篡改、规避权限越权风险。
模块 1 XSS 跨站脚本攻击(最经典高频漏洞)
1.1 漏洞核心原理
XSS(Cross-Site Scripting,跨站脚本攻击):攻击者向网页注入恶意 JS 代码,当用户访问页面时,恶意代码自动执行,窃取用户Cookie、本地存储、页面数据、伪造用户操作、劫持页面跳转。核心成因:网页未对用户输入、外部渲染内容做过滤转义,直接解析执行HTML/JS代码。
1.2 三大攻击类型(全覆盖)
1.2.1 存储型 XSS(持久型·危害最高)
攻击场景:恶意代码提交至服务端数据库,所有访问该页面的用户都会触发恶意脚本执行,属于全站扩散型漏洞。常见于评论区、留言板、用户简介、动态发布等可存储用户内容的场景。
攻击示例:用户提交评论 <script>窃取Cookie并发送至攻击者服务器</script>,服务端未过滤直接存储,所有查看该评论的用户均被攻击。
1.2.2 反射型 XSS(非持久型·临时攻击)
攻击场景:恶意代码拼接在URL参数中,用户点击特制链接后,参数内容被页面直接渲染执行,仅单次生效,不会存储到服务端。常见于搜索回显、参数跳转、页面传值场景。
攻击示例:构造链接 xxx.com/search?key=<script>恶意代码</script>,页面直接展示搜索关键词,触发脚本执行。
1.2.3 DOM 型 XSS(前端专属·隐蔽性最强)
攻击场景:完全由前端JS操作DOM引发,参数不经过服务端,服务端无法拦截。前端直接将URL参数、本地变量、用户输入通过 innerHTML、document.write、eval 等API渲染为可执行代码。
高危API:innerHTML、outerHTML、document.write、eval()、new Function()、setTimeout(字符串)。
1.3 完整落地防御方案(必做)
-
输入输出转义(基础防御):所有用户输入、外部渲染内容,前端手动转义HTML特殊字符(<、>、&、'、"),杜绝代码解析。
-
CSP 内容安全策略(核心防御):通过响应头限制脚本加载、执行来源,禁止内联脚本、未知域名脚本执行,从根源拦截XSS。
-
Cookie 安全配置:开启 HttpOnly,禁止JS读取Cookie,彻底杜绝Cookie窃取;开启Secure仅HTTPS传输。
-
禁用高危API:项目中禁止使用innerHTML、eval等危险方法,优先使用textContent纯文本渲染。
-
输入长度与格式校验:对用户输入做正则校验、长度限制,拦截特殊恶意字符。
1.4 XSS 防御实战代码
// 1. HTML特殊字符转义工具(核心防御)
function htmlEscape(str) {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, '');
}
// 错误写法:直接渲染,触发XSS漏洞
// document.getElementById('content').innerHTML = userInput;
// 正确写法:转义后渲染
const userInput = '<script>alert("恶意攻击")</script>';
document.getElementById('content').textContent = htmlEscape(userInput);
// 2. CSP响应头配置(服务端配置)
// 限制仅本站脚本、样式、图片可加载,禁止内联脚本、eval
// Content-Security-Policy: default-src 'self'; img-src *; style-src 'self'; script-src 'self'
1.5 高频面试总结
1. 存储型XSS危害最大、持久生效;DOM型XSS仅前端触发、服务端无日志、最难排查;
2. XSS核心防御:输入转义+CSP+HttpOnly Cookie三位一体;
3. 绝对禁止eval、innerHTML解析未知用户内容;
4. HttpOnly Cookie可防Cookie窃取,但无法防御页面篡改类XSS。
模块 2 CSRF 跨站请求伪造
2.1 漏洞核心原理
CSRF(跨站请求伪造):攻击者诱导用户在已登录状态下,在第三方页面发起本站请求,利用浏览器自动携带Cookie、登录态的特性,伪造用户身份执行操作(改密码、转账、提交数据、修改权限)。核心成因:浏览器同源策略不拦截跨站GET/POST请求,自动携带域名Cookie,服务端未校验请求合法性。
2.2 经典攻击场景
用户登录A网站(保留登录Cookie),访问攻击者搭建的B恶意网站,B网站内嵌A网站的提交表单/接口请求,用户无感知触发请求,以合法用户身份执行非法操作。
2.3 全套防御方案(生产必配)
-
Token 校验(主流方案):登录后服务端下发随机CSRF Token,前端请求接口时手动携带Token,服务端校验Token合法性,伪造请求无Token直接拦截。
-
SameSite Cookie 限制:设置Cookie的SameSite属性(Strict/Lax),禁止跨站请求携带Cookie,从浏览器层面拦截攻击。
-
请求来源校验:服务端校验 Referer/Origin 请求头,拦截非本站域名的跨站请求。
-
关键操作二次校验:转账、改密、权限修改等高危操作,增加短信验证码、人机校验,杜绝伪造操作。
2.4 CSRF 实战规范
1. 所有POST/PUT/DELETE写接口必须携带CSRF Token,GET查询接口可按需放行;
2. SameSite=Strict:完全禁止跨站带Cookie,安全性最高;SameSite=Lax:允许安全跨站GET请求,兼容性最优;
3. 禁止前端固定Token,必须每次登录动态刷新。
模块 3 点击劫持漏洞
3.1 漏洞原理
攻击者通过iframe嵌套目标网站,设置透明遮罩,诱导用户点击页面按钮,实际点击的是嵌套的目标网站高危操作按钮,实现恶意操作劫持。核心成因:网站允许被第三方iframe嵌套。
3.2 防御方案
-
X-Frame-Options 响应头(核心):服务端配置响应头,禁止页面被嵌套。取值:DENY(全部禁止嵌套)、SAMEORIGIN(仅本站可嵌套)。
-
CSP frame-ancestors 限制:配合CSP策略,精准限制可嵌套域名,彻底杜绝劫持。
-
前端JS防御(兜底):判断当前页面是否为顶层窗口,非顶层则自动跳出iframe。
3.3 前端兜底实战代码
// 页面强制跳出iframe嵌套
if (window.top !== window.self) {
window.top.location.href = window.location.href;
}
模块 4 接口与请求安全
4.1 接口防刷、防重放攻击
漏洞原理:攻击者抓取合法接口请求,重复发送请求,造成数据重复提交、服务器压力过载。
防御方案:
1. 接口添加时间戳+随机串+签名校验,过期请求直接拦截;
2. 服务端做幂等性设计,唯一请求标识,重复请求不生效;
3. 高频接口添加限流、防抖机制。
4.2 越权攻击防御(水平/垂直越权)
-
垂直越权:普通用户伪造管理员接口,执行高危操作;防御:服务端严格校验接口权限,前端仅做展示拦截,核心权限后端校验。
-
水平越权:用户篡改参数,查看/操作其他用户数据;防御:服务端校验数据归属权,禁止跨用户操作。
核心规范:所有权限校验必须放在服务端,前端权限控制仅为体验优化,不可作为安全屏障。
模块 5 前端数据安全与加密体系
5.1 敏感数据存储规范(强制)
-
禁止本地存储敏感数据:Token、账号密码、手机号、身份证、密钥等禁止存入 localStorage、sessionStorage、Cookie(非HttpOnly),极易被XSS窃取。
-
Token 存储最优方案:优先存储在内存变量中,配合HttpOnly+Secure Cookie存储刷新令牌,杜绝本地窃取。
-
临时数据用完即清:敏感接口返回数据、表单敏感信息,操作完成后立即清空变量。
5.2 常用加密算法与实战场景
-
MD5(单向哈希):密码加密、数据校验,不可逆,仅用于摘要校验,不单独用于密码加密。
-
Base64(编码非加密):二进制转文本,可解码,仅用于数据传输格式化,禁止用于敏感数据加密。
-
对称加密(AES):前端高频,加密解密同一密钥,适用于传输敏感参数、表单数据。
-
非对称加密(RSA):公钥加密、私钥解密,安全性极高,适用于密钥传输、核心机密数据加密。
-
Web Crypto API:浏览器原生加密接口,替代第三方加密库,安全性更高、无第三方后门。
模块 6 常见安全漏洞与规避规范
6.1 弱口令与参数篡改漏洞
规避:前端做密码强度校验,禁止弱口令;禁止前端固定参数、硬编码密钥、接口地址,所有核心配置由服务端下发。
6.2 文件上传漏洞
漏洞成因:未校验文件后缀、MIME类型,允许上传js、php、exe等恶意脚本文件。
防御:前端+后端双重校验文件类型、大小、后缀;上传文件重命名、独立域名存储,禁止执行权限。
6.3 敏感信息泄露
规避:1. 打包剔除console日志、源码注释、敏感配置;
2. 禁止前端返回用户完整手机号、身份证,做脱敏展示;
3. 隐藏接口错误堆栈信息,避免泄露服务端架构。
模块 7 前端安全开发强制规范(必守)
-
所有用户输入、外部渲染内容必须转义,杜绝XSS注入;
-
所有写接口必须校验CSRF Token,开启Cookie安全策略;
-
敏感数据不落地本地存储,核心权限、数据校验全部后置;
-
页面开启CSP、X-Frame-Options安全响应头;
-
禁用eval、动态脚本执行、未知DOM渲染等高风险操作;
-
文件上传、接口请求、用户操作全链路安全校验;
-
生产环境关闭调试模式、隐藏源码、剔除敏感日志。
第八部分 工程化 & TypeScript
模块 1 模块化四大规范
前端模块化四大核心规范:CommonJS、AMD、UMD、ESM,是 JS 工程化的基石,贯穿 Node 开发、浏览器适配、打包编译、库兼容适配全场景。四大规范迭代递进,分别适配服务端、早期浏览器、跨环境兼容、现代标准开发,以下为全覆盖底层原理、语法实战、优缺点与面试考点。
1. CommonJS(服务端同步规范·Node.js 默认)
1.1 核心定位
最早的 JS 模块化社区规范,Node.js 原生默认模块方案,主打服务端同步加载、运行时解析,每个 JS 文件就是独立模块,天然隔离作用域、杜绝全局污染。前端需借助 Webpack/Rollup 打包适配浏览器环境。
1.2 核心语法
导出:module.exports / exports,导入:require()
// 导出 module.js
const name = "CommonJS";
const getName = () => name;
// 核心导出(优先,覆盖exports)
module.exports = { name, getName };
// 次要导出(仅追加属性,不可直接赋值覆盖)
// exports.age = 18;
// 导入 use.js
const mod = require("./module.js");
console.log(mod.name); // CommonJS
1.3 底层运行机制
1. 同步加载:服务端本地文件读取速度快,同步阻塞无性能问题,浏览器端直接使用会卡顿;
2. 运行时解析:模块导入导出在代码执行阶段生效,不支持编译时静态分析;
3. 值拷贝特性:模块导出为浅拷贝,导出值修改后,导入方不会同步更新;
4. 模块缓存:模块首次加载后缓存,二次 require 直接读取缓存,不会重复执行模块代码。
1.4 优缺点与适用场景
✅ 优点:语法简单、服务端适配完美、天然缓存、作用域隔离;
❌ 缺点:同步加载、不支持异步、无静态分析、无法 Tree-Shaking;
🎯 适用:Node.js 服务端开发、前端打包工具适配、传统工程化项目。
2. AMD(浏览器异步规范·RequireJS)
2.1 核心定位
AMD(Asynchronous Module Definition,异步模块定义),专为早期浏览器设计的异步模块化规范,解决浏览器同步加载阻塞页面渲染问题,代表工具 RequireJS。
2.2 核心语法(依赖前置、异步加载)
通过 define(依赖数组, 工厂函数) 定义模块,异步并行加载依赖,加载完成后执行工厂函数。
// 定义无依赖模块
define([], function() {
const add = (a, b) => a + b;
return { add };
});
// 定义有依赖模块(依赖前置声明)
define(["./add.js"], function(addMod) {
const sum = addMod.add(1, 2);
return { sum };
});
// 引入模块
require(["./sum.js"], function(sumMod) {
console.log(sumMod.sum); // 3
});
2.3 底层特性
1. 异步并行加载:不阻塞页面渲染,适配浏览器网络异步特性;
2. 依赖前置:模块顶部提前声明所有依赖,提前加载、预执行;
3. 运行时执行:同样不支持静态分析,无 Tree-Shaking 能力。
2.4 优缺点与现状
✅ 优点:浏览器原生适配、异步无阻塞、多模块并行加载;
❌ 缺点:语法冗余、依赖前置不够灵活、开发成本高、无静态优化;
📌 现状:现代项目已淘汰,仅老旧传统前端项目遗留使用。
3. UMD(通用跨环境规范·兼容层)
3.1 核心定位
UMD(Universal Module Definition,通用模块定义),兼容 CommonJS、AMD、全局变量的跨环境适配规范,专门用于第三方工具库、插件,实现一套代码适配 Node 服务端、浏览器、模块化/非模块化所有环境。
3.2 核心适配逻辑
优先级:AMD 环境 > CommonJS 环境 > 全局挂载环境,自动识别当前运行环境适配导出。
// 标准 UMD 通用模板
(function(root, factory) {
// 1. 识别 AMD 环境(浏览器模块化)
if (typeof define === "function" && define.amd) {
define([], factory);
}
// 2. 识别 CommonJS 环境(Node 服务端)
else if (typeof module === "object" && module.exports) {
module.exports = factory();
}
// 3. 非模块化环境,挂载全局 window
else {
root.tool = factory();
}
})(this, function() {
// 模块核心逻辑
return {
version: "1.0.0",
log: (str) => console.log(str)
};
});
3.3 核心特点与场景
1. 无独立运行机制,仅为兼容适配层,整合另外三大规范;
2. 不支持静态分析、Tree-Shaking,仅用于库兼容;
🎯 适用:第三方开源工具库、兼容老旧项目的通用插件。
4. ESM(ES6 官方标准规范·现代首选)
4.1 核心定位
ESM(ES Modules),ES6 官方原生模块化标准,统一浏览器与 Node.js 模块化方案,是现代前端、Node 新项目唯一首选规范,支持编译时静态分析,工程化优化能力最强。
4.2 核心语法(静态导入导出)
导出:export / export default,导入:import / import()
// 1. 常规导出(命名导出)
export const name = "ESM";
export const getName = () => name;
// 2. 默认导出(单个默认值)
const version = "2.0.0";
export default version;
// 3. 统一导入
import version, { name, getName } from "./esm.js";
// 4. 动态导入(异步按需加载,返回Promise)
import("./esm.js").then(mod => console.log(mod.name));
4.3 核心底层特性(面试高频)
1. 编译时静态解析:导入导出在编译阶段确定,支持静态分析、类型校验、Tree-Shaking,剔除冗余代码;
2. 动态绑定特性:导出值为动态引用绑定,原模块值更新,导入方同步更新(区别于 CommonJS 值拷贝);
3. 严格模式默认开启:ESM 模块默认启用 use strict,杜绝全局污染、变量泄露;
4. 顶层作用域隔离:模块顶层变量不挂载 window,完全隔离;
5. 支持按需异步导入:import() 实现代码分割、懒加载,适配首屏优化。
4.4 优缺点与适用场景
✅ 优点:官方标准、静态优化、支持 Tree-Shaking、动态绑定、跨环境统一、按需加载;
❌ 缺点:老旧浏览器兼容差、早期 Node 版本需配置开启;
🎯 适用:所有现代前端项目、Node.js 新项目、工程化完整项目。
四大规范核心对比(面试必背)
-
CommonJS:服务端同步、运行时、值拷贝、缓存机制、无 Tree-Shaking;
-
AMD:浏览器异步、依赖前置、运行时、语法繁琐、已淘汰;
-
UMD:跨环境兼容适配、无独立机制、仅用于第三方库兼容;
-
ESM:官方标准、编译时静态解析、动态绑定、支持 Tree-Shaking、现代项目首选。
模块 2 构建工具(Webpack + Vite + Babel 完整版·原理+配置+面试)
前端构建工具是工程化核心,解决「模块化浏览器不兼容、代码冗余、兼容性差、打包效率低、生产环境性能差」等问题,核心能力包含:模块解析、代码转译、资源打包、压缩优化、热更新、环境适配。主流构建工具分为传统 Webpack、现代 Vite,搭配 Babel 实现 JS 语法兼容,以下为全维度落地知识点。
2.1 Webpack 全能打包工具(工程化基石)
Webpack 是静态模块打包器,可处理 JS/TS/CSS/图片/字体等所有前端资源,适配复杂中大型项目,是传统企业级项目主流打包工具,核心核心逻辑:入口编译 → 模块解析 → Loader 转译 → Plugin 优化 → 出口输出。
2.1.1 五大核心概念(必背)
-
Entry 入口:打包起始文件,支持单入口/多入口,Webpack 从入口递归解析所有依赖模块,默认值
./src/index.js -
Output 出口:打包后文件输出路径、文件名配置,可配置哈希后缀、输出目录,区分开发/生产环境输出规则
-
Loader 模块转换器:Webpack 仅能识别 JS/JSON 文件,Loader 负责将其他资源(CSS、图片、TS)转译为 Webpack 可识别的模块代码,执行顺序:从右到左、从下到上
-
Plugin 插件:贯穿打包全生命周期,拓展 Webpack 能力(压缩、Html 生成、环境变量注入、清理目录),可监听打包钩子自定义逻辑
-
Mode 模式:区分开发/生产环境,内置默认优化规则:
development(开启热更新、不压缩)、production(默认代码压缩、Tree-Shaking、作用域提升)、none(无默认配置)
2.1.2 核心高频配置与实战方案
(1)基础打包配置
支持多入口打包、输出文件哈希化,解决缓存更新问题,适配生产环境长效缓存策略。
(2)核心 Loader 适配
-
js/ts:
babel-loader语法转译、ts-loaderTS 类型编译 -
css/less/scss:
css-loader解析样式依赖、style-loader注入页面、less-loader/sass-loader预编译、mini-css-extract-plugin抽离独立 CSS 文件 -
静态资源:
asset-module内置资源模块,替代 file-loader/url-loader,自动处理图片、字体、媒体文件,支持按需 base64 转换
(3)核心 Plugin 实战场景
-
HtmlWebpackPlugin:自动生成 HTML、注入打包后的 JS/CSS 资源,自动清理冗余引入 -
CleanWebpackPlugin:打包前清空旧输出目录,避免残留冗余文件 -
MiniCssExtractPlugin:抽离 CSS 为独立文件,避免样式内联在 JS 中,支持 CSS 压缩 -
TerserPlugin:生产环境 JS 压缩、去除注释、压缩变量名,支持多线程打包提速 -
DefinePlugin:注入全局环境变量,区分开发/生产环境业务逻辑
2.1.3 高级优化核心(面试+生产高频)
(1)Tree-Shaking 树摇
原理:基于 ESM 静态语法,编译时扫描未被引入、未执行的冗余代码,自动剔除,减小包体积。限制:仅支持 ESM 模块,CommonJS 动态模块不生效;生产模式默认开启,开发模式需手动配置。
(2)splitChunks 代码分割
解决单文件打包体积过大问题,自动拆分第三方依赖、公共模块、业务代码,实现按需打包、浏览器长效缓存。核心拆分规则:拆分重复引用模块、拆分超大模块、区分vendor公共依赖。
(3)runtimeChunk 运行时抽离
抽离 Webpack 运行时代码(模块解析、加载逻辑),避免业务代码变更导致 runtime 哈希变化,保证第三方依赖缓存永久生效。
(4)externals 外部化依赖
将 Vue/React/axios 等大型依赖排除在打包范围外,通过 CDN 引入,大幅减小打包体积,提升打包速度与首屏加载速度。
(5)打包提速优化
开启多线程打包、缓存编译结果、缩小文件解析范围、排除 node_modules 扫描,解决大型项目打包慢的问题。
2.2 Vite 现代构建工具(新一代主流)
Vite 是基于原生 ESM 的下一代前端构建工具,解决 Webpack 大型项目「启动慢、热更新慢」的核心痛点,开发环境极速启动、秒级热更新,生产环境基于 Rollup 打包,兼顾开发体验与生产性能,是现代 Vue/React/TS 项目首选工具。
2.2.1 核心底层原理(面试必考)
(1)开发环境:原生 ESM + esbuild 预构建
Webpack 启动需一次性打包所有模块,Vite 摒弃全量打包逻辑:利用浏览器原生 ESM 能力,按需加载模块;同时通过 esbuild(Go 编写,极速编译)预构建第三方 CommonJS 依赖,转换为 ESM 格式,解决浏览器不兼容 CommonJS 问题。
(2)生产环境:Rollup 打包
生产环境无原生 ESM 按需加载优势,复用 Rollup 成熟打包能力,支持 Tree-Shaking、代码分割、资源压缩,保证生产包体积最优。
2.2.2 核心优势(对比 Webpack)
-
启动极速:无需全量打包,项目越大,对比 Webpack 优势越明显
-
HMR 热更新秒级生效:精准更新单个模块,无需重新编译整体模块,热更新速度与项目体积无关
-
原生 ESM 支持:天然适配现代语法,无需复杂转换,配置极简
-
esbuild 预构建:极速转换依赖、合并模块,解决模块重复请求问题
2.2.3 核心能力与配置
-
预构建缓存:缓存第三方依赖转换结果,二次启动无需重新构建,大幅提速
-
插件体系:兼容 Rollup 插件,生态完善,支持自定义构建逻辑
-
按需编译:仅编译当前页面所需模块,闲置模块不参与编译
-
内置优化:默认支持 TS、CSS、静态资源处理,零配置开箱即用
2.2.4 局限性
对老旧 CommonJS 复杂依赖兼容性略差、大型传统项目迁移成本高、部分小众 Webpack 插件无替代方案,适合新项目、现代轻量化/中大型项目。
2.3 Babel 语法转译核心(兼容性核心)
Babel 是JS 编译器,核心作用:将 ES6+、TS、JSX 等高阶语法,转译为低版本浏览器兼容的 ES5 语法,解决前端语法兼容问题,仅做语法转换,不处理模块打包、资源解析。
2.3.1 核心三大模块
-
@babel/core:Babel 核心编译引擎,提供语法解析、转换、生成能力
-
@babel/preset-env:智能预设,根据目标浏览器版本,自动按需转换 ES6+ 语法,无需手动配置单个插件
-
@babel/plugin-xxx:单个语法转换插件,处理特殊高阶语法(装饰器、可选链等)
2.3.2 语法转译 vs 补丁兼容(核心重难点)
-
语法转译:Babel 仅转换「语法层面」(箭头函数、解构、class),将高阶语法转为 ES5 语法
-
API 补丁:Babel 无法转换「新增 API」(Promise、Array.includes、Map),需要 core-js实现 polyfill 补丁填充
2.4 core-js 垫片兼容方案
core-js 是前端标准 API 垫片库,专门修复低版本浏览器缺失的 ES 新 API,是前端兼容低端浏览器的核心方案。
2.4.1 核心配置规则
-
core-js@2:仅支持 ES5 及以下补丁,功能有限,已淘汰
-
core-js@3:全覆盖 ES6+、Web API 补丁,支持按需引入、自动补全
2.4.2 两种兼容模式
-
按需 polyfill(推荐):根据项目代码、目标浏览器,只引入缺失的 API 补丁,体积最小
-
全量 polyfill:一次性引入所有补丁,兼容性最强,但体积冗余,仅适配极致兼容老旧浏览器场景
2.5 Webpack 与 Vite 核心对比(面试必背)
|
对比维度 |
Webpack |
Vite |
|---|---|---|
|
打包原理 |
全量递归打包所有模块 |
开发按需ESM+esbuild、生产Rollup |
|
启动速度 |
项目越大越慢,全量编译 |
极速启动,无全量打包过程 |
|
热更新 |
模块级重新编译,速度较慢 |
精准单模块更新,秒级响应 |
|
构建性能 |
中大型项目打包耗时久 |
开发极速,生产打包高效 |
|
生态适配 |
生态最全,兼容所有老旧项目 |
现代生态完善,老旧依赖兼容一般 |
|
适用场景 |
大型复杂项目、老旧项目迭代 |
新项目、Vue/React/TS现代项目 |
2.6 构建工程化开发规范(强制落地)
-
开发环境优先保证启动速度、热更新效率,生产环境优先保证包体积最小、性能最优
-
统一开启 Tree-Shaking、代码分割、资源压缩,杜绝冗余代码与资源
-
语法转译+polyfill 分层处理,语法靠 Babel、API 靠 core-js,兼顾兼容与体积
-
静态资源统一哈希命名,配合浏览器缓存策略,实现长效缓存更新
-
大型项目优先拆分模块、开启打包缓存、多线程编译,优化构建效率
模块 3 代码规范与测试(工程化标准化落地·完整体系)
代码规范与测试是前端工程化的核心兜底能力,解决团队代码风格混乱、语法错误隐性bug、代码质量参差不齐、线上故障无兜底等问题。本模块覆盖代码格式化、语法校验、Git提交规范、代码提交拦截、单元测试、集成测试、覆盖率检测全流程落地方案,适配团队协作、项目上线、代码提效、质量管控全场景,为企业级项目标准化必备配置。
3.1 代码格式化规范(Prettier)
Prettier 是前端统一代码格式化工具,核心作用:统一团队代码风格,杜绝缩进、换行、引号、空格等格式争议,零成本统一编码样式,支持 JS/TS/CSS/LESS/Vue/React/JSON 全文件格式化。
3.1.1 核心配置规则(企业通用规范)
创建根目录.prettierrc 配置文件,通用标准化配置如下:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf"
}
3.1.2 配置释义与规范说明
-
semi:语句末尾自动添加分号,规避ASI自动分号插入bug
-
singleQuote:统一使用单引号,区别于JSON双引号,前端代码统一规范
-
tabWidth:缩进2空格,适配所有前端项目通用排版标准
-
trailingComma:ES5语法允许尾部逗号,简化多人协作合并代码冲突
-
printWidth:单行代码最大长度100,避免代码过长难以阅读
-
arrowParens:箭头函数单参数省略括号,精简代码
3.1.3 落地使用方式
-
IDE安装Prettier插件,开启保存自动格式化
-
配置脚本:
"prettier": "prettier --write .",全局格式化所有文件 -
配置忽略文件
.prettierignore,过滤node_modules、打包产物、配置文件
3.2 代码语法质量校验(ESLint)
ESLint 是前端代码质量检测工具,区别于Prettier(格式美化),ESLint 专注语法错误检测、代码质量管控、不良语法拦截、最佳实践约束,可规避90%的隐性语法bug、不规范编码、安全隐患代码。
3.2.1 核心能力
-
校验JS/TS语法错误,拦截未定义变量、无效语法、死代码
-
约束代码最佳实践,禁止eval、var、未使用变量、无效逻辑
-
支持TS、Vue、React专属语法校验,适配各框架项目
-
可配置错误/警告级别,支持自动修复可规范代码
3.2.2 标准化配置(TS通用模板)
依托 .eslintrc.js 配置,整合官方规范+TS规范+禁用冗余规则:
module.exports = {
// 适配环境
env: {
browser: true,
es2021: true,
node: true
},
// 继承通用规范
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
// 解析器适配TS
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
// 自定义规则
rules: {
// 禁用var,强制let/const
'no-var': 'error',
// 允许未使用参数(适配框架场景)
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
// 允许空函数,屏蔽无效警告
'@typescript-eslint/no-empty-function': 'off',
// 禁止eval高危语法
'no-eval': 'error',
// 禁止debugger上线
'no-debugger': 'error',
// 禁止冗余console
'no-console': ['warn', { allow: ['warn', 'error'] }]
}
}
3.2.3 ESLint与Prettier冲突解决
二者默认存在格式规则冲突,通过 eslint-config-prettier 关闭ESLint格式规则、eslint-plugin-prettier 将Prettier格式问题纳入ESLint警告,实现质量校验+格式美化统一。
3.2.4 常用执行脚本
-
"lint": "eslint src/**/*.{js,ts,vue,jsx,tsx}":检测所有代码规范问题 -
"lint:fix": "eslint src/**/*.{js,ts,vue,jsx,tsx} --fix":自动修复可修复问题
3.5 前端单元测试(Jest 完整版)
单元测试是前端代码质量兜底核心,针对工具函数、核心逻辑、算法、公共方法进行最小粒度测试,保证代码修改后原有功能不失效,杜绝迭代改崩历史逻辑,适配复杂业务、公共组件、底层工具库开发。
3.5.1 Jest 核心优势
-
零配置开箱即用,天然支持JS/TS,无需复杂适配
-
内置断言库、模拟函数、定时器模拟、异步测试能力
-
支持测试覆盖率统计,精准定位未测试代码
-
运行速度快,支持并行测试、文件监听增量测试
3.5.2 基础语法与实战用例
通用测试模板,包含同步/异步函数测试、断言匹配:
// 待测试工具函数
export const sum = (a, b) => a + b;
export const asyncGetData = () => Promise.resolve('success');
// 单元测试套件
describe('工具函数测试', () => {
// 同步测试
test('sum函数正确求和', () => {
expect(sum(1, 2)).toBe(3);
expect(sum(-1, 1)).toBe(0);
});
// 异步Promise测试
test('异步请求返回正确结果', async () => {
const res = await asyncGetData();
expect(res).toBe('success');
});
});
3.5.3 常用核心断言(面试+实战)
-
toBe:精准匹配基本类型值、引用地址 -
toEqual:深度匹配对象/数组内容(常用) -
toBeTruthy/toBeFalsy:匹配真假值 -
toContain:判断数组/字符串包含指定内容 -
toThrow:校验函数是否抛出指定错误 -
resolves/rejects:快捷校验Promise结果
3.5.4 测试覆盖率
执行脚本 jest --coverage,自动生成覆盖率报告,包含四项核心指标:
-
语句覆盖率:执行到的代码语句占比
-
分支覆盖率:if/else、三元等分支覆盖比例
-
函数覆盖率:被调用测试的函数占比
-
行覆盖率:被执行的代码行占比
企业规范:核心工具、底层逻辑覆盖率需达到90%以上。
3.6 组件测试与集成测试
3.6.1 组件测试(Testing-Library)
搭配 @testing-library/vue/react 实现UI组件测试,模拟用户点击、输入、切换等操作,校验组件渲染、交互、状态更新是否符合预期,避免组件迭代出现交互bug。
3.6.2 集成测试核心思想
区别于单单元测试,集成测试聚焦多个模块联动逻辑,校验页面完整流程(表单提交、接口联动、状态流转、路由跳转),保证整体业务流程通畅。
3.7 工程化质量流水线(CI/CD 集成)
将代码规范、测试能力接入CI流水线,代码合并、版本构建时自动执行:ESLint校验、Prettier格式检测、单元测试、覆盖率校验,任意环节不达标直接阻断构建合并,彻底保障线上代码质量。
3.8 高频面试与开发规范总结
-
Prettier vs ESLint:Prettier只管代码格式排版,ESLint只管代码质量语法,二者互补,需统一配置避免冲突;
-
lint-staged核心价值:仅校验提交文件,提升校验效率,规避全量代码校验冗余开销;
-
Git规范意义:统一提交日志,便于版本迭代追溯、自动化生成更新日志、团队协作复盘;
-
单元测试落地场景:核心工具函数、通用算法、公共组件、复杂业务逻辑必须写测试用例,简单页面业务可按需省略;
-
质量管控核心逻辑:IDE实时校验+提交强制拦截+CI流水线兜底,三层防护杜绝劣质代码上线。
模块 4 TypeScript(JS 超集·完整版实战+面试全解)
TypeScript 是 JavaScript 的静态类型超集,由微软开发,最终编译为纯 JS 运行。核心解决 JS 弱类型带来的隐性 bug、代码可读性差、大型项目维护困难、团队协作不规范等问题。TS 支持所有 JS 原生语法,新增静态类型校验、接口、泛型、枚举、装饰器、类型推导等高级能力,是现代 Vue3/React/小程序/企业级项目的标配技术。本模块全覆盖入门语法、核心重难点、实战代码、面试高频考点、工程化配置。
4.1 TS 核心优势(为什么必学)
-
静态类型校验:编译阶段检测类型错误,提前规避线上隐性 bug,替代部分运行时校验;
-
代码可读性极强:类型定义即文档,清晰标注参数、返回值、对象结构,降低团队协作成本;
-
适配大型项目:约束代码规范、限制随意赋值,解决大型项目迭代混乱、维护成本高的问题;
-
智能提示拉满:IDE 精准推导类型、属性、方法,大幅提升开发效率;
-
完全兼容 JS:所有 JS 代码可直接运行,渐进式接入,无侵入改造;
-
生态完善:主流框架、第三方库均提供完整 TS 类型支持,企业开发刚需。
4.2 基础类型系统(TS 基石)
TS 在 JS 七大原始类型、引用类型基础上,拓展专属类型,构建完整静态类型体系,所有变量、函数参数、返回值均可精准约束类型。
4.2.1 基础原生类型(精准约束)
支持 JS 所有基础类型,新增类型严格校验,禁止类型随意赋值:
// 1. 基础类型定义
let str: string = "TS字符串";
let num: number = 996;
let bool: boolean = true;
let n: null = null;
let un: undefined = undefined;
let sym: symbol = Symbol("ts");
let big: bigint = 100n;
// 报错演示:类型不匹配(TS编译拦截)
// str = 123;
// 2. 任意类型(慎用,放弃TS类型校验能力)
let anyVal: any = "任意值";
anyVal = 123;
anyVal = true;
// 3. 未知类型(安全的任意类型)
let unknownVal: unknown = "未知值";
// unknownVal.toUpperCase(); // 直接调用报错,需类型收敛后使用
if (typeof unknownVal === "string") {
unknownVal.toUpperCase();
}
// 4. 空值/无返回值
function voidFn(): void {
// 无返回值,或返回undefined
console.log("无返回值函数");
}
// 5. 永不返回类型(死循环、报错终止程序)
function errorFn(): never {
throw new Error("程序终止");
}
4.2.2 复合核心类型(面试高频)
(1)字面量类型
约束变量只能是指定固定值,精准限制取值范围,常用于枚举状态、固定参数:
// 字符串字面量
type Status = "success" | "error" | "loading";
let resStatus: Status = "success";
// 数字字面量
type ScoreLevel = 1 | 2 | 3 | 4 | 5;
let level: ScoreLevel = 5;
// 布尔字面量
let flag: true = true;
(2)联合类型
变量支持多种类型任选其一,用 | 分隔,适配多类型参数场景:
let mixVal: string | number;
mixVal = "联合类型";
mixVal = 666;
// 联合类型+字面量 精准约束
type Gender = "男" | "女" | "未知";
(3)交叉类型
用 & 合并多个类型,变量必须同时满足所有类型规则,常用于对象类型合并:
interface UserInfo { name: string }
interface UserAuth { token: string }
// 合并两个类型,必须包含所有属性
type User = UserInfo & UserAuth;
let user: User = { name: "张三", token: "xxx-xxx" };
(4)类型别名 type
自定义复合类型别名,简化重复类型定义,支持所有类型封装:
// 基础类型别名
type NumStr = string | number;
let val: NumStr = "123";
// 函数类型别名
type Fn = (a: number, b: number) => number;
const add: Fn = (a, b) => a + b;
// 复杂对象类型别名
type Person = {
name: string;
age?: number; // 可选属性
readonly id: string; // 只读属性
}
4.3 接口 interface(TS 核心核心)
接口用于定义对象、函数、类的结构规范,约束属性类型、可选性、只读性,支持接口合并、继承拓展,是团队规范、数据结构约束的核心手段。与 type 相比,interface 更适合定义对象结构、支持合并继承,type 适合定义联合/字面量/交叉类型。
4.3.1 基础接口定义
// 基础对象接口
interface IUser {
// 必选属性
username: string;
// 可选属性
age?: number;
// 只读属性(初始化后不可修改)
readonly userId: string;
// 任意属性(兼容未知拓展字段)
[prop: string]: any;
}
let user1: IUser = {
username: "李四",
userId: "u1001",
gender: "男" // 任意属性兼容
};
4.3.2 接口继承与合并
接口天然支持多继承、自动合并,可层层拓展规范,适配层级化数据结构:
// 基础接口
interface IBase {
id: string;
createTime: string;
}
// 单继承
interface IAdmin extends IBase {
role: string;
permission: string[];
}
// 多继承
interface ISuperAdmin extends IAdmin, IBase {
superFlag: boolean;
}
// 接口自动合并(同名接口自动合并属性)
interface IUser {
email?: string;
}
interface IUser {
phone?: string;
}
// 最终IUser包含:username/age/userId/email/phone/任意属性
4.3.3 接口与type核心区别(面试必背)
-
语法差异:interface 专注对象结构,type 可定义任意类型(联合、字面量、基础类型);
-
合并特性:interface 同名自动合并,type 同名直接报错;
-
继承方式:interface 用 extends 继承,type 用 & 交叉合并;
-
使用规范:对外暴露的对象结构、类规范优先用 interface;内部复合类型、工具类型优先用 type。
4.4 枚举 Enum(固定状态最优解)
枚举用于定义固定有限的常量集合,替代零散字面量,统一状态管理,适配状态码、权限、类型、状态标识等场景,TS 专属高级特性。
4.4.1 基础枚举
// 1. 数字枚举(默认自增,初始值0)
enum Status {
PENDING, // 0
SUCCESS, // 1
ERROR // 2
}
// 手动设置初始值
enum Score {
LOW = 60,
MID = 80,
HIGH = 90
}
// 2. 字符串枚举(推荐,语义清晰,无歧义)
enum Gender {
MAN = "male",
WOMAN = "female",
UNKNOWN = "unknown"
}
// 3. 常量枚举(编译后直接替换值,减少冗余代码)
const enum Week {
MONDAY = 1,
TUESDAY = 2
}
let today: Week = Week.MONDAY;
4.4.2 枚举核心特性
-
数字枚举支持双向映射(值查名、名查值),字符串枚举仅单向映射;
-
常量枚举编译后无枚举对象,直接内联值,性能更优;
-
统一项目状态常量,杜绝魔法数字、魔法字符串。
4.5 函数类型约束(精准管控函数)
TS 可精准约束函数的参数类型、参数个数、返回值类型、剩余参数、可选参数,杜绝函数传参、返回值类型混乱问题。
// 1. 基础函数约束
function calc(a: number, b: number): number {
return a + b;
}
// 2. 可选参数、默认参数
function sayHi(name: string, age?: number, msg: string = "你好"): string {
return `${msg}${name},年龄${age ?? "未知"}`;
}
// 3. 剩余参数
function total(...nums: number[]): number {
return nums.reduce((sum, item) => sum + item, 0);
}
// 4. 无返回值函数
function logInfo(info: string): void {
console.log(info);
}
// 5. 函数类型别名复用
type CalcFn = (x: number, y: number) => number;
const sub: CalcFn = (x, y) => x - y;
4.6 泛型(TS 灵魂·高阶核心)
泛型实现类型复用、动态类型约束,解决「any 丢失类型校验」和「重复定义相似类型」的痛点,实现类型参数化,是封装通用工具函数、组件、接口的核心。
4.6.1 基础泛型
// 通用返回自身的函数,T为动态类型占位符
function getVal<T>(val: T): T {
return val;
}
// 自动类型推导
getVal(123);
getVal("泛型");
// 手动指定类型
getVal<boolean>(true);
4.6.2 多泛型参数、泛型约束
// 1. 多泛型参数
function swap<T, K>(a: T, b: K): [T, K] {
return [a, b];
}
swap(123, "ts");
// 2. 泛型约束(限制泛型必须包含指定属性)
interface ILength {
length: number;
}
// 约束T必须拥有length属性
function getLength<T extends ILength>(val: T): number {
return val.length;
}
getLength("字符串");
getLength([1,2,3]);
4.6.3 泛型接口、泛型类
// 泛型接口
interface IRes<T> {
code: number;
data: T;
message: string;
}
// 接口复用,适配不同返回数据
let userRes: IRes<{name: string}> = {
code: 200,
data: { name: "TS" },
message: "成功"
};
// 泛型类
class Queue<T> {
private list: T[] = [];
push(val: T) {
this.list.push(val);
}
pop(): T | undefined {
return this.list.shift();
}
}
const numQueue = new Queue<number>();
numQueue.push(1);
4.6.4 内置工具泛型(必背高频)
TS 内置大量通用工具类型,基于泛型封装,无需重复定义,开发高频复用:
interface IPerson {
name: string;
age: number;
readonly id: string;
}
// 1. Partial:所有属性变为可选
type PartialPerson = Partial<IPerson>
// 2. Required:所有属性变为必选
type RequiredPerson = Required<PartialPerson>
// 3. Pick:挑选指定属性生成新类型
type PickPerson = Pick<IPerson, "name" | "age">
// 4. Omit:剔除指定属性生成新类型
type OmitPerson = Omit<IPerson, "id">
// 5. Record:快速定义键值对类型
type ObjMap = Record<string, number>
// 6. Exclude:排除联合类型指定值
type T1 = Exclude<"a" | "b" | "c", "a"> // "b" | "c"
// 7. Extract:提取联合类型指定值
type T2 = Extract<"a" | "b", "a"> // "a"
4.7 类与面向对象(TS 强化版)
TS 对 ES6 类做了全面强化,新增访问修饰符、抽象类、抽象方法、只读属性、参数属性,规范面向对象开发。
4.7.1 三大访问修饰符
-
public(默认):公有属性/方法,任意位置可访问;
-
private:私有属性/方法,仅当前类内部可访问;
-
protected:受保护属性/方法,当前类、子类可访问,外部无法访问。
class Person {
// 公有
public name: string;
// 私有
private password: string;
// 受保护
protected age: number;
constructor(name: string, password: string, age: number) {
this.name = name;
this.password = password;
this.age = age;
}
}
4.7.2 抽象类 abstract
抽象类用于定义父类规范,无法实例化,强制子类实现抽象方法,统一子类结构:
abstract class Animal {
// 抽象方法:无实现,强制子类重写
abstract bark(): void;
// 普通方法:子类可直接继承
sleep() {
console.log("睡觉");
}
}
// 子类必须实现所有抽象方法
class Dog extends Animal {
bark() {
console.log("汪汪汪");
}
}
4.7.3 参数属性(语法糖)
简化类属性定义,直接在 constructor 参数中声明属性,无需重复赋值:
class User {
// 一行代码完成属性定义+赋值
constructor(public name: string, private id: string) {}
}
4.8 高级类型特性(面试重难点)
4.8.1 类型守卫
精准收敛联合类型,细化类型判断,解决联合类型属性/方法调用报错问题:
// 自定义类型守卫
function isString(val: unknown): val is string {
return typeof val === "string";
}
function getValText(val: string | number) {
if (isString(val)) {
return val.toUpperCase();
} else {
return val.toFixed(2);
}
}
4.8.2 类型断言
手动告知 TS 变量类型,弥补 TS 自动推导的不足,谨慎使用避免类型错误:
// 1. 尖括号断言
const dom = <HTMLDivElement>document.getElementById("box");
// 2. as 断言(推荐,兼容JSX)
const dom2 = document.getElementById("box") as HTMLDivElement;
// 非空断言!:强制告知变量非空
let str?: string;
str!.length;
4.8.3 infer 类型推导(高阶面试考点)
infer 用于在泛型中动态推导未知类型,常用于封装工具类型、提取函数返回值、参数类型:
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type FnReturn = ReturnType<() => string> // string
// 提取数组元素类型
type ArrayItem<T> = T extends (infer I)[] ? I : never;
type Item = ArrayItem<number[]> // number
4.9 声明文件 .d.ts
.d.ts 是 TS 类型声明文件,仅用于定义类型,无业务逻辑,主要为无 TS 类型的第三方 JS 库、全局变量、模块补充类型说明,编译后不打包。
4.9.1 核心作用
-
为原生 JS 库、全局变量、浏览器 API 补充类型;
-
自定义模块类型声明,解决模块找不到类型问题;
-
统一项目全局类型,实现全项目类型复用。
4.9.2 基础声明语法
// 声明全局变量
declare var globalName: string;
// 声明全局函数
declare function globalFn(num: number): boolean;
// 声明模块类型
declare module "xxx-utils" {
export function formatTime(time: number): string;
}
// 声明全局接口(全局拓展)
interface Window {
customGlobal: string;
}
4.10 tsconfig.json 核心配置(工程化必备)
tsconfig.json 是 TS 项目核心配置文件,控制编译规则、类型校验、文件包含、兼容性,以下为企业通用标准配置:
{
"compilerOptions": {
"target": "ES6", // 编译目标JS版本
"module": "ESNext", // 模块规范
"moduleResolution": "NodeNext", // 模块解析规则
"strict": true, // 开启所有严格类型校验(核心,强制规范)
"esModuleInterop": true, // 兼容CommonJS模块
"skipLibCheck": true, // 跳过库文件类型校验
"forceConsistentCasingInFileNames": true, // 禁止文件名大小写不一致
"resolveJsonModule": true, // 支持导入json文件
"isolatedModules": true, // 模块化隔离
"jsx": "preserve", // 保留JSX语法
"outDir": "./dist", // 编译输出目录
"rootDir": "./src" // 源码根目录
},
"include": ["src/**/*", "typings/**/*"], // 包含文件
"exclude": ["node_modules", "dist"] // 排除文件
}
4.11 TS 高频面试总结 + 开发规范
-
核心定位:TS 是编译期静态类型校验,不改变 JS 运行逻辑,仅在编译阶段报错拦截问题;
-
type vs interface:对象结构优先 interface,复合类型(联合/字面量)优先 type;
-
泛型核心意义:告别 any,实现类型复用与类型安全,是通用组件、工具封装的核心;
-
严格模式必开:strict:true 开启所有严格校验,杜绝隐式 any、空值隐患,企业项目强制开启;
-
枚举使用场景:固定状态、常量配置优先用字符串枚举,语义清晰、无双向映射歧义;
-
类型断言规范:禁止滥用断言,仅在 TS 推导失效时使用,避免掩盖真实类型错误;
-
工程规范:全局类型统一放 .d.ts,通用工具类型抽离独立文件,类型复用、统一规范。
第九部分 Web 高级拓展能力(企业级高阶实战 + 面试全解)
Web 高级拓展 API 是现代前端进阶核心,区别于基础 DOM/BOM 语法,主要用于实现离线应用、音视频实时通信、图形可视化、硬件设备调用、高性能渲染、跨端能力拓展,是中高级前端、可视化、音视频、PWA 项目、跨端项目的必备技能,也是大厂高频面试与工程实战考点。本模块全覆盖主流高级 API,包含原理、场景、实战代码、坑点总结。
9.1 PWA 渐进式网页应用(离线应用核心)
PWA(Progressive Web App)渐进式网页应用,是谷歌推出的现代网页增强方案,让网页拥有离线访问、桌面图标、消息推送、秒开加载、无浏览器栏沉浸式体验,实现媲美原生 APP 的使用体验,适配官网、后台系统、移动端 H5、资讯类项目。
9.1.1 PWA 三大核心组件
(1)Manifest 应用清单
Web 应用配置文件,用于定义网页桌面图标、启动页、主题色、展示模式,实现网页桌面化、沉浸式打开。
核心能力:添加桌面快捷方式、隐藏浏览器地址栏、自定义启动动画、适配移动端状态栏。
# manifest.json 标准配置
{
"name": "JS全栈手册",
"short_name": "JS手册",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#409eff",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
}
],
"orientation": "portrait-primary"
}
配置引入:HTML 头部引入 <link rel="manifest" href="/manifest.json">
(2)Service Worker 离线服务线程
Service Worker 是独立于主线程的后台脚本,脱离网页 DOM,后台常驻运行,核心实现资源缓存、离线访问、请求拦截、版本更新、后台同步,是 PWA 离线能力的核心。
核心特性:异步执行、无 DOM 权限、独立线程、生命周期可控、需要 HTTPS 环境(localhost 本地调试豁免)。
生命周期(面试必考)
注册 → 安装(install) → 激活(activate) → 后台运行 → 销毁
-
install:首次加载触发,缓存静态资源
-
activate:激活新版本,清理旧缓存资源
-
fetch:拦截所有网络请求,匹配缓存优先访问
// 主线程注册 Service Worker
async function registerSW() {
if ('serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW注册成功', registration.scope);
}
}
registerSW();
(3)Cache 缓存体系 & 离线策略
搭配 Service Worker 使用,实现资源持久化缓存,提供两种核心缓存策略:
-
缓存优先(静态资源):优先读取本地缓存,无缓存再请求网络,适配 JS/CSS/图片、静态页面,实现秒开
-
网络优先(动态接口):优先请求最新网络数据,失败后读取缓存兜底,适配动态接口、资讯内容
9.1.2 消息推送 & 后台同步Web
Push 消息推送:网页关闭后,依然可接收服务端推送消息,依赖浏览器推送服务Background Sync 后台同步:断网时保存用户操作,联网后自动同步数据,适配表单提交、离线点赞等场景
9.1.3 PWA 优缺点与落地场景
优势:离线可用、加载极速、节省流量、沉浸式体验、无需应用商店上架、跨平台适配;
劣势:部分硬件能力受限、老旧浏览器不兼容、推送依赖浏览器、复杂业务适配性弱。
落地场景:文档手册、资讯博客、工具类网站、移动端 H5、轻量后台系统。
9.2 WebRTC 实时音视频通信
WebRTC 是浏览器原生实时通信 API,无需依赖第三方插件,实现浏览器之间点对点(P2P)音视频传输、数据传输,是网页直播、视频通话、在线会议、屏幕共享的核心底层技术。
9.2.1 核心能力
-
媒体设备调用:摄像头、麦克风、屏幕录制
-
P2P 点对点音视频实时传输,低延迟
-
任意二进制数据实时传输(文件、文本)
-
支持音视频编码、降噪、画质自适应
9.2.2 核心 API 实战
(1)媒体设备获取
// 获取摄像头+麦克风媒体流
async function getMediaStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 绑定到video标签播放
const video = document.querySelector('video');
video.srcObject = stream;
} catch (err) {
console.error('设备调用失败:', err);
}
}
(2)屏幕共享
// 屏幕录制共享
async function getScreenStream() {
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
document.querySelector('video').srcObject = stream;
}
9.2.3 通信核心流程(面试必背)
设备授权 → 获取媒体流 → SDP 信令交换(描述媒体信息) → ICE 服务器穿透(解决内网互通) → 建立 P2P 连接 → 实时传输数据
核心概念:SDP(媒体描述协议)、ICE(内网穿透)、STUN/TURN(中继服务器)
9.3 Web Audio 音频处理高级 API
Web Audio 是浏览器专业音频处理框架,区别于简单的 audio 标签,支持音频解析、混音、音效处理、音量均衡、频谱可视化、音频降噪、定时播放,适配音乐播放器、音频可视化、在线K歌、音效游戏场景。
9.3.1 核心核心节点
-
AudioContext:音频上下文(全局音频管理器)
-
AudioBufferSource:音频数据源
-
GainNode:音量控制节点
-
AnalyserNode:频谱分析节点(可视化核心)
-
BiquadFilterNode:音效滤镜(高低音调节)
9.3.2 基础音频播放实战
// 初始化音频上下文
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// 加载音频文件
async function playAudio(url) {
const res = await fetch(url);
const arrayBuffer = await res.arrayBuffer();
// 解码音频
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
// 创建音源
const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
// 音量控制
const gainNode = audioCtx.createGain();
gainNode.value = 0.8;
// 链路连接
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
// 播放音频
source.start();
}
9.4 Canvas 2D 图形绘制体系
Canvas 是像素级 2D 绘图 API,通过 JS 动态绘制图形、文字、图片、动画,像素渲染高性能,适配数据可视化、画板、小游戏、海报生成、水印、图表底层。
9.4.1 核心特性
-
基于像素渲染,绘制后无 DOM 节点,性能开销低
-
支持矩形、圆形、路径、渐变、图片、文字绘制
-
支持逐帧动画、图形变换、裁剪、合成模式
9.4.2 基础绘图实战
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 300;
// 绘制矩形
ctx.fillStyle = '#409eff';
ctx.fillRect(50, 50, 100, 80);
// 绘制圆形
ctx.beginPath();
ctx.arc(200, 90, 40, 0, Math.PI * 2);
ctx.fillStyle = '#67c23a';
ctx.fill();
// 绘制文字
ctx.font = '20px 微软雅黑';
ctx.fillStyle = '#333';
ctx.fillText('Canvas绘图', 50, 200);
9.4.3 高频场景与坑点
画布宽高必须通过 width/height 属性设置,禁止 CSS 宽高拉伸(像素失真)
动画需手动清空画布 clearRect 逐帧重绘适合高频动态绘制,静态图形优先 SVG
9.5 WebGL 3D 图形高性能渲染
WebGL 是基于 OpenGL ES 的浏览器 3D 绘图标准,依托GPU 硬件加速,实现高性能 2D/3D 图形渲染,是 3D 可视化、数字孪生、3D 游戏、模型展示、全景图的底层核心。
9.5.1 核心优势
-
GPU 并行渲染,脱离 CPU 单线程,超高帧率
-
支持 3D 模型、光影、纹理、矩阵变换、粒子效果
-
适配复杂可视化、3D 大屏、三维交互场景
9.5.2 主流封装方案
原生 WebGL 语法繁琐,企业开发均使用成熟封装库:
-
Three.js:最主流 3D 图形库,简化 WebGL 语法,快速实现 3D 场景、模型、动画
-
EGret/Laya:小游戏、H5 动画专用
-
Mapbox/DeckGL:地理可视化、大数据 3D 渲染
9.6 WebAssembly 高性能底层交互
WebAssembly(Wasm)是浏览器低级二进制指令格式,突破 JS 性能瓶颈,支持 C/C++/Rust/Go 等语言编译为浏览器可执行代码,实现接近原生的运行性能。
9.6.1 核心价值
-
解决 JS 弱类型、单线程、运算性能差的问题
-
高性能运算场景:音视频编解码、图像算法、大数据计算、游戏引擎
-
支持 JS 与 Wasm 双向相互调用,无缝衔接前端业务
9.6.2 落地场景
视频剪辑网页端、AI 模型推理、图像识别、加密解密运算、3D 游戏引擎、大数据实时计算。
9.7 浏览器硬件设备 API
现代浏览器开放大量硬件设备调用能力,无需原生开发,网页即可调用设备硬件功能,是 H5 能力拓展的核心。
9.7.1 媒体设备 API
摄像头、麦克风、扬声器调用,适配扫码、人脸采集、音视频录制场景(上文 WebRTC 已覆盖)。
9.7.2 文件系统访问 API(File System Access)
突破传统 FileReader 只读限制,支持直接读写本地文件、创建文件夹、保存编辑内容,实现网页端在线编辑器、PS 网页版、文档编辑工具。
9.7.3 其他高频硬件 API
-
地理位置 API:获取用户经纬度、定位适配门店、地图场景
-
陀螺仪/加速度传感器 API:适配摇一摇、全景图、游戏体感交互
-
振动 API:手机设备振动反馈,增强交互体验
-
剪贴板 API:原生复制粘贴、读写剪贴板内容,替代老旧 document.execCommand
-
全屏 API:网页/元素全屏展示,适配大屏可视化、视频播放
// 全屏API实战
async function fullScreen(el) {
if (!document.fullscreenElement) {
await el.requestFullscreen();
} else {
document.exitFullscreen();
}
}
// 剪贴板复制
async function copyText(text) {
await navigator.clipboard.writeText(text);
alert('复制成功');
}
9.8 高性能 Web 新增能力(工程高频)
9.8.1 虚拟滚动 & 视口监听
IntersectionObserver 视口交叉监听,替代传统 scroll 滚动监听,异步执行、性能极高,用于图片懒加载、组件按需渲染、曝光埋点、无限滚动。
9.8.2 Web Worker 多线程
突破 JS 单线程限制,开启后台子线程,复杂计算、大数据解析、文件解析不阻塞主线程,解决页面卡顿问题,支持主线程与子线程消息通信。
9.8.3 requestAnimationFrame 帧动画
浏览器原生动画 API,跟随屏幕刷新率执行(60帧/秒),比 setInterval 更精准、无延迟、无卡顿,是网页动画、逐帧渲染最优方案。
9.9 高级拓展能力面试&开发总结(必背)
-
PWA 核心价值:Manifest 负责外观配置、Service Worker 负责离线缓存、实现 APP 级网页体验;
-
Canvas vs SVG:Canvas 像素绘图适合动态高频渲染,SVG 矢量图适合静态高清图形、图标;
-
WebRTC 核心:基于 P2P 点对点通信,无需服务端转发,低延迟实现音视频实时通话;
-
Wasm 定位:弥补 JS 运算性能短板,高性能计算场景刚需,与 JS 互补协作;
-
硬件 API 规范:所有设备、权限 API 必须在 HTTPS 环境生效,localhost 为调试特例;
-
性能优化核心:复杂计算丢 Web Worker、动画用 requestAnimationFrame、监听用 IntersectionObserver,彻底规避主线程阻塞。
第十部分 高频手写源码(实战检验·面试完整版)
本模块汇总前端面试100%高频手写源码,覆盖JS底层、函数高阶、异步核心、网络DOM、工程框架五大模块,每道题包含「核心原理+完整可运行代码+面试考点解析」,适配校招、社招面试手撕环节,所有代码无冗余、可直接复制运行、兼容边界场景,补齐市面手写源码短板(循环引用、特殊类型、边界容错)。
1. JS 底层核心手写(基础必考)
1.1 手写 new 操作符
核心原理
1. 创建一个空的原生对象;
2. 将空对象的原型指向构造函数的 prototype;
3. 改变构造函数 this 指向为空对象;
4. 执行构造函数逻辑;
5. 若构造函数返回引用类型则返回该值,否则返回新建对象。
function myNew(Fn, ...args) {
// 1. 创建空对象,绑定构造函数原型
const obj = Object.create(Fn.prototype);
// 2. 改变this指向并执行构造函数
const res = Fn.apply(obj, args);
// 3. 优先返回构造函数返回的引用类型,否则返回新建对象
return res instanceof Object ? res : obj;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log(`你好,${this.name}`);
};
const p = myNew(Person, "张三", 20);
console.log(p.name, p.age); // 张三 20
p.sayHi(); // 你好,张三
面试考点
new 四步核心、构造函数返回值优先级、原型绑定逻辑,是JS面向对象底层核心。
1.2 手写 instanceof 运算符
核心原理
遍历实例的原型链,逐层向上查找,判断构造函数的 prototype 是否存在于实例原型链上,基础类型直接返回 false。
function myInstanceof(left, right) {
// 基础类型直接返回false
if (typeof left !== "object" || left === null) return false;
// 获取实例原型
let proto = Object.getPrototypeOf(left);
// 逐层遍历原型链
while (proto) {
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 测试
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof({}, Object)); // true
console.log(myInstanceof(123, Number)); // false
1.3 手写 Object.create
核心原理
创建一个空对象,将指定对象作为新对象的原型,返回空对象,实现原型继承。
function myCreate(proto) {
// 校验原型必须是对象/ null
if (!["object", "function"].includes(typeof proto) || proto === undefined) {
throw new TypeError("原型必须是对象或null");
}
// 创建空对象并绑定原型
function Fn() {}
Fn.prototype = proto;
return new Fn();
}
// 测试
const obj = { name: "JS" };
const newObj = myCreate(obj);
console.log(newObj.name); // JS
console.log(Object.getPrototypeOf(newObj) === obj); // true
1.4 手写深拷贝(完整版·支持循环引用/特殊类型)
核心原理
区分原始值/引用类型,单独处理 Date、RegExp、null、undefined 等特殊类型,通过 WeakMap 缓存已拷贝对象,解决循环引用爆栈问题。
function deepClone(target, cache = new WeakMap()) {
// 1. 原始值/函数直接返回
if (typeof target !== "object" || target === null) return target;
// 2. 解决循环引用
if (cache.has(target)) return cache.get(target);
// 3. 特殊引用类型单独处理
// 日期
if (target instanceof Date) return new Date(target);
// 正则
if (target instanceof RegExp) return new RegExp(target.source, target.flags);
// 4. 创建对应类型实例(数组/对象)
const cloneTarget = Array.isArray(target) ? [] : {};
// 缓存当前对象
cache.set(target, cloneTarget);
// 5. 递归遍历拷贝
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = deepClone(target[key], cache);
});
return cloneTarget;
}
// 测试(含循环引用)
const obj = { a: 1, b: [2, 3], c: new Date() };
obj.self = obj; // 循环引用
const cloneObj = deepClone(obj);
console.log(cloneObj);
console.log(cloneObj.self === cloneObj); // true(循环引用修复)
面试考点
普通深拷贝无法解决循环引用、特殊类型丢失问题,完整版必须兼容 Date/RegExp/循环引用,是中高级面试高频题。
2. 高阶函数手写(工具必考)
2.1 手写 call / apply / bind
2.1.1 手写 call
Function.prototype.myCall = function (context, ...args) {
// 默认绑定window,null/undefined指向全局
context = context || window;
// 唯一key,避免属性覆盖
const fnKey = Symbol("fn");
// 将函数挂载到上下文
context[fnKey] = this;
// 执行函数并传参
const res = context[fnKey](...args);
// 删除临时属性
delete context[fnKey];
return res;
};
// 测试
function sum(a, b) {
return this.num + a + b;
}
const obj = { num: 10 };
console.log(sum.myCall(obj, 20, 30)); // 60
2.1.2 手写 apply
Function.prototype.myApply = function (context, args = []) {
context = context || window;
const fnKey = Symbol("fn");
context[fnKey] = this;
const res = context[fnKey](...args);
delete context[fnKey];
return res;
};
// 测试
console.log(sum.myApply(obj, [20, 30])); // 60
2.1.3 手写 bind(完整版·支持柯里化、new 调用)
Function.prototype.myBind = function (context, ...args1) {
const fn = this;
// 返回新函数
return function Fn(...args2) {
// 区分普通调用和new调用
const ctx = this instanceof Fn ? this : context;
// 合并两次参数
return fn.apply(ctx, [...args1, ...args2]);
};
};
// 测试
const bindSum = sum.myBind(obj, 20);
console.log(bindSum(30)); // 60
2.2 手写防抖(debounce)
核心原理
触发事件后延迟执行,若期间再次触发,重置延迟时间,只执行最后一次,适配搜索输入、窗口resize。
// 完整版:支持立即执行、取消防抖
function debounce(fn, delay, immediate = false) {
let timer = null;
const debounceFn = function (...args) {
// 清空定时器,重置延迟
if (timer) clearTimeout(timer);
// 立即执行场景
if (immediate && !timer) {
fn.apply(this, args);
}
// 延迟执行
timer = setTimeout(() => {
// 非立即执行执行函数
if (!immediate) fn.apply(this, args);
timer = null;
}, delay);
};
// 取消防抖
debounceFn.cancel = () => clearTimeout(timer);
return debounceFn;
}
// 测试
const inputDebounce = debounce((val) => console.log("输入值:", val), 500);
2.3 手写节流(throttle)
核心原理
固定时间内只执行一次函数,限制执行频率,适配滚动监听、按钮频繁点击、拖拽事件。
// 时间戳版 + 定时器版完整版
function throttle(fn, interval) {
let startTime = 0;
let timer = null;
const throttleFn = function (...args) {
const now = Date.now();
// 清空未执行的定时器
if (timer) {
clearTimeout(timer);
timer = null;
}
// 超过间隔时间,立即执行
if (now - startTime >= interval) {
fn.apply(this, args);
startTime = now;
} else {
// 兜底执行,保证最后一次触发生效
timer = setTimeout(() => {
fn.apply(this, args);
startTime = Date.now();
}, interval - (now - startTime));
}
};
// 取消节流
throttleFn.cancel = () => clearTimeout(timer);
return throttleFn;
}
// 测试
const scrollThrottle = throttle(() => console.log("滚动执行"), 300);
2.4 手写函数 compose(函数组合)
核心原理
将多个单参数函数组合成一个函数,从右至左依次执行,前一个函数返回值作为后一个函数入参,适配中间件、状态处理。
function compose(...fns) {
// 无函数返回原值
if (fns.length === 0) return (val) => val;
// 单个函数直接返回
if (fns.length === 1) return fns[0];
// 从右往左递归执行
return fns.reduce((prev, curr) => (val) => prev(curr(val)));
}
// 测试
const add = (x) => x + 1;
const mul = (x) => x * 2;
const fn = compose(add, mul);
console.log(fn(10)); // 21 (10*2+1)
2.5 手写记忆函数(缓存函数结果)
核心原理
缓存函数入参对应的执行结果,重复相同入参直接读取缓存,减少重复计算,适配递归、大数据计算场景。
function memoize(fn) {
const cache = new Map();
return function (...args) {
// 参数序列化作为key
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const res = fn.apply(this, args);
cache.set(key, res);
return res;
};
}
// 测试:斐波那契数列优化
const fib = memoize((n) => n <= 2 ? 1 : fib(n - 1) + fib(n - 2));
console.log(fib(30)); // 快速输出结果,无重复计算
3. 异步核心手写(面试重难点)
3.1 手写完整 Promise(符合 A+ 规范)
核心原理
实现 Promise 三种状态、状态不可逆、then 异步回调、链式调用、值穿透、异常捕获、微任务执行。
class MyPromise {
// 三种状态
static PENDING = "pending";
static FULFILLED = "fulfilled";
static REJECTED = "rejected";
constructor(executor) {
this.status = MyPromise.PENDING; // 初始状态
this.value = null; // 成功值
this.reason = null; // 失败原因
this.resolveCallbacks = []; // 成功回调队列
this.rejectCallbacks = []; // 失败回调队列
// 成功函数
const resolve = (value) => {
if (this.status !== MyPromise.PENDING) return;
this.status = MyPromise.FULFILLED;
this.value = value;
// 异步执行所有成功回调
queueMicrotask(() => {
this.resolveCallbacks.forEach(fn => fn());
});
};
// 失败函数
const reject = (reason) => {
if (this.status !== MyPromise.PENDING) return;
this.status = MyPromise.REJECTED;
this.reason = reason;
queueMicrotask(() => {
this.rejectCallbacks.forEach(fn => fn());
});
};
// 执行器异常捕获
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// 链式then
then(onFulfilled, onRejected) {
// 值穿透处理
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : val => val;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
// 返回新Promise实现链式调用
return new MyPromise((resolve, reject) => {
// 成功状态
if (this.status === MyPromise.FULFILLED) {
queueMicrotask(() => {
try {
const res = onFulfilled(this.value);
resolve(res);
} catch (err) {
reject(err);
}
});
}
// 失败状态
if (this.status === MyPromise.REJECTED) {
queueMicrotask(() => {
try {
const res = onRejected(this.reason);
resolve(res);
} catch (err) {
reject(err);
}
});
}
// 等待状态,存入回调队列
if (this.status === MyPromise.PENDING) {
this.resolveCallbacks.push(() => {
try {
const res = onFulfilled(this.value);
resolve(res);
} catch (err) {
reject(err);
}
});
this.rejectCallbacks.push(() => {
try {
const res = onRejected(this.reason);
resolve(res);
} catch (err) {
reject(err);
}
});
}
});
}
// 失败捕获
catch(onRejected) {
return this.then(null, onRejected);
}
// 最终执行
finally(fn) {
return this.then(
val => MyPromise.resolve(fn()).then(() => val),
err => MyPromise.resolve(fn()).then(() => { throw err })
);
}
// 静态resolve
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
// 静态reject
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
}
// 基础测试
new MyPromise((resolve) => resolve("Promise测试")).then(res => console.log(res));
3.2 手写 Promise 四大静态方法
3.2.1 Promise.all
MyPromise.all = function (promiseList) {
return new MyPromise((resolve, reject) => {
const result = [];
let count = 0;
const len = promiseList.length;
// 空数组直接resolve
if (len === 0) resolve([]);
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
res => {
result[i] = res;
count++;
// 所有请求成功,返回结果数组
if (count === len) resolve(result);
},
err => {
// 任意失败直接reject
reject(err);
}
);
}
});
};
3.2.2 Promise.race
MyPromise.race = function (promiseList) {
return new MyPromise((resolve, reject) => {
for (let p of promiseList) {
MyPromise.resolve(p).then(resolve, reject);
}
});
};
3.2.3 Promise.allSettled
MyPromise.allSettled = function (promiseList) {
return new MyPromise((resolve) => {
const result = [];
let count = 0;
const len = promiseList.length;
if (len === 0) resolve([]);
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
res => {
result[i] = { status: "fulfilled", value: res };
count++;
if (count === len) resolve(result);
},
err => {
result[i] = { status: "rejected", reason: err };
count++;
if (count === len) resolve(result);
}
);
}
});
};
3.2.4 Promise.any
MyPromise.any = function (promiseList) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;
const len = promiseList.length;
if (len === 0) reject(new AggregateError([], "所有Promise失败"));
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
res => resolve(res), // 任意成功直接返回
err => {
errors[i] = err;
count++;
if (count === len) reject(new AggregateError(errors, "所有Promise失败"));
}
);
}
});
};
3.3 手写异步并发限制器(高阶面试)
核心原理
控制同时执行的异步任务数量,避免并发过多导致请求拥堵,适配批量接口请求、文件上传场景。
class ConcurrentLimit {
constructor(limit) {
this.limit = limit; // 最大并发数
this.running = 0; // 当前运行数
this.queue = []; // 等待队列
}
// 添加任务
add(task) {
return new Promise(resolve => {
this.queue.push({ task, resolve });
this.run();
});
}
// 执行任务
run() {
// 超出并发限制/无等待任务,终止执行
if (this.running >= this.limit || this.queue.length === 0) return;
// 取出队首任务
const { task, resolve } = this.queue.shift();
this.running++;
// 执行异步任务
task().then(res => {
resolve(res);
}).finally(() => {
this.running--;
this.run(); // 递归执行下一个任务
});
}
}
// 测试:限制最大并发2
const limit = new ConcurrentLimit(2);
// 模拟异步任务
function createTask(val) {
return () => new Promise(resolve => setTimeout(() => resolve(val), 1000));
}
// 批量添加5个任务
for (let i = 1; i <= 5; i++) {
limit.add(createTask(i)).then(res => console.log("任务完成:", res));
}
4. DOM/网络 手写源码
4.1 手写原生 AJAX
function myAjax(options) {
const { url, method = "GET", data = {}, success, error } = options;
// 初始化XMLHttpRequest
const xhr = new XMLHttpRequest();
// 拼接GET参数
let params = "";
Object.keys(data).forEach(key => {
params += `${key}=${data[key]}&`;
});
const reqUrl = params ? `${url}?${params.slice(0, -1)}` : url;
// 打开请求
xhr.open(method.toUpperCase(), reqUrl, true);
// 监听状态变化
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
success && success(JSON.parse(xhr.responseText));
} else {
error && error(xhr.statusText);
}
}
};
// 发送请求(POST携带请求体)
if (method.toUpperCase() === "POST") {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
}
4.2 手写简易 Fetch 封装
async function myFetch(url, options = {}) {
const { method = "GET", data = {} } = options;
const config = { method, headers: { "Content-Type": "application/json" } };
// 处理GET参数
if (method.toUpperCase() === "GET") {
const params = new URLSearchParams(data);
url += `?${params.toString()}`;
} else {
config.body = JSON.stringify(data);
}
// 发起请求
const res = await fetch(url, config);
if (!res.ok) throw new Error("请求失败");
return await res.json();
}
4.3 手写事件总线(发布订阅模式)
class EventBus {
constructor() {
this.eventMap = new Map(); // 存储事件-回调队列
}
// 订阅事件
on(eventName, fn) {
if (!this.eventMap.has(eventName)) {
this.eventMap.set(eventName, []);
}
this.eventMap.get(eventName).push(fn);
}
// 发布事件
emit(eventName, ...args) {
if (!this.eventMap.has(eventName)) return;
this.eventMap.get(eventName).forEach(fn => fn(...args));
}
// 取消订阅
off(eventName, fn) {
if (!this.eventMap.has(eventName)) return;
const fns = this.eventMap.get(eventName).filter(item => item !== fn);
this.eventMap.set(eventName, fns);
}
// 单次订阅
once(eventName, fn) {
const onceFn = (...args) => {
fn(...args);
this.off(eventName, onceFn);
};
this.on(eventName, onceFn);
}
}
// 测试
const bus = new EventBus();
bus.on("msg", (res) => console.log("接收消息:", res));
bus.emit("msg", "Hello EventBus");
5. 工程框架手写源码(进阶面试)
5.1 手写 Hash / History 路由
5.1.1 简易 Hash 路由
class HashRouter {
constructor() {
this.routes = new Map();
// 监听hash变化
window.addEventListener("hashchange", () => this.matchRoute());
// 初始化匹配
window.addEventListener("load", () => this.matchRoute());
}
// 注册路由
register(path, fn) {
this.routes.set(path, fn);
}
// 匹配路由
matchRoute() {
const hash = location.hash.slice(1) || "/";
this.routes.get(hash)?.();
}
}
// 测试
const router = new HashRouter();
router.register("/", () => console.log("首页"));
router.register("/about", () => console.log("关于页面"));
5.1.2 简易 History 路由
class HistoryRouter {
constructor() {
this.routes = new Map();
// 监听浏览器前进后退
window.addEventListener("popstate", () => this.matchRoute());
window.addEventListener("load", () => this.matchRoute());
}
register(path, fn) {
this.routes.set(path, fn);
}
// 跳转路由
push(path) {
history.pushState({}, "", path);
this.matchRoute();
}
// 匹配路由
matchRoute() {
this.routes.get(location.pathname)?.();
}
}
5.2 手写简易虚拟 DOM + Diff 算法
核心原理
虚拟DOM用JS对象描述DOM结构,Diff算法通过层级对比、节点key对比,最小化更新真实DOM,实现高效渲染。
// 1. 创建虚拟DOM
function createVNode(tag, props = {}, children = []) {
return { tag, props, children };
}
// 2. 虚拟DOM转真实DOM
function render(vNode) {
// 文本节点
if (typeof vNode === "string") return document.createTextNode(vNode);
// 元素节点
const dom = document.createElement(vNode.tag);
// 挂载属性
Object.keys(vNode.props).forEach(key => {
dom.setAttribute(key, vNode.props[key]);
});
// 递归挂载子节点
vNode.children.forEach(child => dom.appendChild(render(child)));
return dom;
}
// 3. 简易Diff算法(同层级对比)
function diff(oldVNode, newVNode) {
const patches = [];
// 节点类型不同:直接替换
if (oldVNode.tag !== newVNode.tag) {
patches.push({ type: "REPLACE", newVNode });
return patches;
}
// 文本节点对比
if (typeof oldVNode === "string" && typeof newVNode === "string") {
if (oldVNode !== newVNode) patches.push({ type: "TEXT", content: newVNode });
return patches;
}
// 子节点简易对比
const maxLen = Math.max(oldVNode.children.length, newVNode.children.length);
for (let i = 0; i < maxLen; i++) {
const oldChild = oldVNode.children[i];
const newChild = newVNode.children[i];
if (!oldChild) patches.push({ type: "ADD", newChild });
else if (!newChild) patches.push({ type: "REMOVE", index: i });
else patches.push(...diff(oldChild, newChild));
}
return patches;
}
// 测试
const oldVDom = createVNode("div", { id: "box" }, ["旧内容"]);
const newVDom = createVNode("div", { id: "box" }, ["新内容"]);
console.log(diff(oldVDom, newVDom));
高频手写源码面试总结
-
基础底层必背:new、instanceof、深拷贝(完整版)、call/apply/bind 是入门必考,重点关注边界场景和特殊类型处理;
-
高阶函数核心:防抖节流必须掌握立即执行/取消功能,compose、记忆函数侧重原理和应用场景;
-
异步重中之重:完整Promise A+规范、四大静态方法、异步并发限制器是中高级面试手撕核心;
-
工程进阶考点:事件总线、路由原理、虚拟DOM+Diff 框架底层原理,进阶面试高频考察;
-
得分关键:手写代码必须兼容边界场景、支持异常捕获、可直接运行,杜绝残缺版代码。
第十一部分 调试与开发工具(工程实战·线上排错完整版)
前端开发调试是排查Bug、优化性能、解决线上疑难问题的核心能力,告别单纯console.log打印,本章节整合浏览器高阶调试、Console全套API、断点调试、SourceMap、异常监控、性能调试、抓包实战、线上报错上报全体系知识,覆盖日常开发、本地调试、线上排错全场景,补齐工程化调试短板。
11.1 Console 高阶调试 API(告别简陋 log)
普通 console.log 仅满足基础打印,浏览器内置全套高阶 Console API,可实现格式化打印、统计、计时、堆栈追踪、分组管理,大幅提升调试效率。
11.1.1 核心高阶 API 实战
// 1. console.table 结构化打印数组/对象(清晰展示表格数据)
const userList = [{name: "张三", age: 20}, {name: "李四", age: 22}];
console.table(userList);
// 2. console.time / timeEnd 精准统计代码执行耗时
console.time("循环耗时");
for(let i = 0; i < 10000; i++){}
console.timeEnd("循环耗时"); // 打印对应标签代码执行毫秒数
// 3. console.trace 打印函数调用堆栈(定位函数调用来源)
function fnA() { fnB() }
function fnB() { fnC() }
function fnC() { console.trace("函数调用堆栈"); }
fnA();
// 4. console.group / groupEnd 日志分组(层级管理日志)
console.group("用户登录流程");
console.log("校验参数");
console.log("请求登录接口");
console.log("存储登录态");
console.groupEnd();
// 5. console.count 统计代码执行次数(高频触发场景统计)
for(let i = 0; i < 3; i++) {
console.count("循环执行次数");
}
// 6. console.assert 断言调试(条件不成立则报错,精准校验参数)
const num = 10;
console.assert(num > 20, "数值必须大于20"); // 断言失败,抛出自定义错误
// 7. console.dir 深度打印DOM/对象详情(展示完整属性方法)
const dom = document.querySelector("body");
console.dir(dom); // 打印DOM对象全部原生属性,区别于log的DOM结构
// 8. 自定义样式打印
console.log("%c自定义彩色日志", "color: #409eff; font-size: 14px; font-weight: bold;");
11.1.2 Console 调试规范
1. 开发环境优先使用 console.table/trace/assert 替代原生 log,日志更直观、定位问题更精准;
2. 禁止代码留存无效打印日志,上线前统一清理;
3. 核心逻辑、异常分支使用 console.error/console.warn 区分日志级别,便于快速筛选问题;
4. 复杂循环、异步逻辑用 console.time 监控耗时,提前发现性能瓶颈。
11.2 浏览器断点调试(核心排错能力)
断点调试是前端精准排错的核心,支持逐行执行、变量监听、作用域查看、断点条件筛选,彻底解决打印日志的局限性,适配复杂逻辑、异步代码、循环Bug排查。
11.2.1 六大断点类型及使用场景
(1)普通行断点
点击代码行号添加断点,代码执行到当前行自动暂停,可查看当前作用域变量、this指向、调用堆栈,适用于固定逻辑代码排错。
(2)条件断点(高频高效)
右键行断点设置触发条件,仅满足条件时暂停代码,规避无效暂停,适配大循环、特定参数报错场景。
示例:循环100次,仅当 i=99 时暂停,精准排查末尾迭代Bug。
(3)DOM 断点
监听指定DOM节点,触发节点修改、属性变更、节点移除时自动断点,快速定位是谁修改了DOM样式/属性。
(4)XHR/Fetch 网络断点
匹配指定接口URL,接口请求发起时自动断点,直接定位请求参数、拦截逻辑、响应处理报错,排查接口异常、请求篡改问题。
(5)事件断点
浏览器预设所有事件类型(点击、输入、滚动、键盘事件),开启后触发对应事件自动断点,快速定位事件绑定、事件冒泡相关Bug。
(6)debugger 手动断点
代码中写入 debugger,浏览器开发者工具打开时自动暂停,适用于线上临时调试、异步回调、动态执行代码场景。
function calc(a, b) {
debugger; // 手动断点,打开控制台自动暂停
return a * b;
}
calc(10, 20);
11.2.2 断点调试核心操作
-
逐行执行(F10):单步执行,跳过函数内部,逐行排查当前代码逻辑;
-
步入函数(F11):进入自定义函数内部,逐层排查嵌套逻辑;
-
步出函数(Shift+F11):跳出当前函数,回到函数调用上层;
-
执行到下一个断点(F8):跳过当前暂停,直接执行到下一处断点;
-
监视变量:自定义监听变量/表达式,实时查看变量值变化;
-
作用域查看:实时查看局部、全局、块级作用域所有变量状态。
11.3 SourceMap 源码映射(线上排错核心)
项目打包后代码会被压缩、混淆、合并,一行打包代码对应数十行源码,线上报错无法直接定位源码位置,SourceMap 就是解决该问题的核心方案。
11.3.1 核心原理
SourceMap 是一份映射文件(.map),记录打包后压缩代码与原始源码的行列对应关系,浏览器加载后可自动还原源码,实现线上报错精准定位。
11.3.2 工程配置规范
开发环境:开启 SourceMap,方便本地调试定位源码错误;
生产环境:默认关闭 SourceMap,避免源码泄露;如需线上排错,可单独上传 .map 文件至私有服务器,不对外暴露。
11.3.3 常见问题解决
1. 线上报错显示混淆代码:未开启/加载 SourceMap 文件;
2. 源码映射错位:打包工具版本不匹配、缓存未清理;
3. 源码泄露风险:禁止生产环境直接暴露 .map 文件。
11.4 网络抓包与接口调试(前端必备)
接口异常、跨域、请求超时、参数错误是日常高频问题,熟练使用网络调试工具可快速定位前端/后端问题。
11.4.1 浏览器 Network 面板核心功能
-
筛选请求类型:区分 XHR/Fetch、JS、CSS、图片、文档请求,精准筛选接口;
-
查看请求详情:请求头、请求参数、响应体、状态码、响应耗时、请求大小;
-
请求重发(Replay):复制接口请求,无需刷新页面重复调用;
-
编辑重发(Edit and resend):修改请求参数、请求头、请求方式,调试异常参数;
-
模拟弱网:切换慢速3G/自定义限速,排查弱网下接口超时、渲染异常问题;
-
屏蔽请求:屏蔽第三方冗余请求,简化调试环境。
11.4.2 状态码快速排错口诀
2xx 成功:200正常、201创建成功、204无返回内容;
3xx 重定向:301永久重定向、302临时重定向、304缓存命中;
4xx 前端错误:404路径错误、401未登录、403权限不足、400参数错误;
5xx 后端错误:500服务异常、502网关错误、503服务不可用。
11.5 性能调试面板(性能优化专用)
11.5.1 Performance 性能录制
录制页面完整运行流程,精准分析主线程阻塞、函数执行耗时、重排重绘、加载卡顿问题,定位页面卡顿、白屏、交互延迟根源。
核心观测指标:脚本执行耗时、渲染耗时、空闲时间、任务阻塞时长。
11.5.2 Lighthouse 自动化测评
浏览器内置自动化测评工具,一键检测页面性能、兼容性、可访问性、最佳实践、SEO,输出优化评分和详细优化方案,适配页面上线前质检。
11.6 线上异常捕获与错误上报(工程必备)
本地无法复现线上Bug,必须通过全局异常捕获+错误上报,收集线上报错信息、用户设备、操作路径,实现问题快速定位。
11.6.1 前端全局异常捕获全覆盖
// 1. 捕获全局JS运行错误(语法错误、逻辑错误)
window.onerror = function(msg, url, line, col, error) {
console.log("JS异常:", { msg, url, line, col, stack: error.stack });
// 此处可调用上报接口,上传错误信息
return true; // 阻止浏览器默认报错
};
// 2. 捕获未处理的Promise异常
window.unhandledrejection = function(event) {
console.log("Promise异步异常:", event.reason);
event.preventDefault(); // 阻止默认报错
};
// 3. 捕获资源加载异常(图片、JS、CSS加载失败)
window.addEventListener("error", (e) => {
if(e.target.src || e.target.href) {
console.log("资源加载异常:", e.target.src || e.target.href);
}
}, true);
// 4. 捕获路由跳转异常、框架渲染异常
11.6.2 错误上报核心字段
完整上报信息需包含:错误信息、错误堆栈、报错文件、行列数、用户设备、浏览器版本、页面路径、用户操作轨迹、接口请求参数,最大化还原线上报错场景。
11.6.3 主流监控方案
小型项目:自定义全局捕获 + 后端接口上报;
中大型项目:接入 Sentry、Fundebug、阿里ARMS 成熟监控平台,支持异常聚合、告警推送、源码自动解析。
11.7 移动端调试方案(H5 专属)
手机端H5页面无法直接查看控制台日志、排查报错,必备移动端调试工具:
-
vConsole:轻量移动端调试面板,模拟浏览器Console、网络、元素查看,适配所有H5项目;
-
eruda:功能更强的移动端调试工具,支持性能查看、缓存查看、接口日志;
-
Chrome远程调试:手机连接电脑,电脑浏览器直接调试手机页面,精准排查移动端兼容Bug。
11.8 调试工具高频面试&实战总结
-
日志调试原则:优先使用高阶Console API,精准打印结构化数据、堆栈信息,摒弃无脑log打印;
-
断点调试核心:条件断点、DOM断点、XHR断点是高效排错核心,熟练替代低效日志调试;
-
SourceMap核心价值:解决打包混淆代码无法定位线上源码报错的问题,区分生产/开发环境配置;
-
接口排错逻辑:4xx自查前端参数/权限、5xx反馈后端服务问题,通过Network精准定位请求异常;
-
线上监控核心:全覆盖捕获JS、Promise、资源异常,完善错误上报体系,实现线上问题无感排查;
-
性能调试核心:Performance排查卡顿、Lighthouse自动化优化测评,是前端性能优化的必备工具。
学习阶段规划
阶段 1 基础入门(1~2 周)
ES 基础语法 → 变量类型运算符 → 对象数组 → 函数 this → ES6 常用特性
阶段 2 浏览器核心(2 周)
BOM/DOM、事件、网络请求、跨域、存储、二进制文件
阶段 3 异步底层(1~2 周)
Promise、async/await、浏览器 + Node 事件循环、并发控制
阶段 4 面向对象与底层(2 周)
原型链、继承、Proxy/Reflect 元编程、正则、GC 内存机制
阶段 5 Node.js(1~2 周)
CommonJS、核心模块、搭建简易服务、npm 工程
阶段 6 工程化与 TS(2 周)
Webpack/Vite、ESLint、TypeScript 完整语法
阶段 7 高级拓展 + 面试实战(长期)
Web 高级 API、安全、性能优化、全套手写源码刷题
&spm=1001.2101.3001.5002&articleId=162076062&d=1&t=3&u=20604540f6f742b088b5f34374e10e42)
3万+

被折叠的 条评论
为什么被折叠?



