← 返回手写题目录

📚 JS 常用 API 速查手册

字符串 · 数组 · 对象 · ES6+ 新特性
每个 API 都有:用法 · 场景 · 参数 · 输出 · 易错点

📌 复习方法:先看场景回忆用法 → 展开看代码对照 → 重点记易错点(标 🔥 的)

🔤 字符串 API

前端最高频用的字符串方法。模板字符串、replace、split、slice 是日常四大件。

str.includes(searchStr, fromIndex?)常用
作用
判断字符串中是否包含某子串,返回布尔值
场景
URL 判断(url.includes('/admin'))、搜索高亮、表单校验
参数
① 要查找的子串 ② 可选:起始位置
输出
boolean
'Hello World'.includes('World') // true 'Hello World'.includes('world') // false(区分大小写) 'a-b-c'.includes('-', 2) // true(从索引2开始找)
str.split(separator, limit?)必会
作用
用分隔符把字符串切成数组
场景
URL 路径解析、CSV 数据解析、标签按逗号拆分
参数
① 分隔符(字符串或正则)② 可选:最多切几段
输出
string[]
'a,b,c'.split(',') // ['a','b','c'] 'a-b-c'.split('-', 2) // ['a','b'](只切2段) '2024-01-15'.split('-') // ['2024','01','15'] 'abc'.split('') // ['a','b','c'](空串→逐字符) '1 2 3'.split(/\s+/) // ['1','2','3'](正则切空格)
str.slice(start, end?) / substring / substr必会
作用
截取字符串的一部分
场景
截取文件后缀、显示省略号、截取日期片段
参数
① 起始索引(负数=倒数)② 可选:结束索引(不含)
输出
string
'Hello'.slice(1, 3) // 'el'(索引1到3,不含3) 'Hello'.slice(-2) // 'lo'(后2个字符) 'file.txt'.slice(-4) // '.txt'(取后缀) 'Hello'.slice(1, -1) // 'ell'(去掉首尾)
🔥 三兄弟区别(面试常问)
slice:支持负数索引,推荐,字符串数组都能用
substring:不支持负数(负数变 0),会自动调换参数顺序
substr(start, length):第二个参数是长度,已废弃不推荐
str.replace(pattern, replacement) / replaceAll必会
作用
替换字符串中匹配的部分
场景
模板渲染({{name}} → 实际值)、敏感词过滤、空格清理
参数
① 要替换的内容(字符串或正则)② 替换为的内容(字符串或函数)
输出
string(返回新字符串,原串不变)
// 基础:只替换第一个匹配 'a-b-c'.replace('-', '+') // 'a+b-c'(只换第一个) // 全局替换:用 /g 正则 或 replaceAll 'a-b-c'.replace(/-/g, '+') // 'a+b+c' 'a-b-c'.replaceAll('-', '+') // 'a+b+c'(ES2021,更直观) // 函数替换(高级用法,常考) 'a1b2c3'.replace(/\d/g, m => `[${m}]`) // 'a[1]b[2]c[3]' // 模板渲染(捕获组 $1) '2024-01-15'.replace(/(\d+)-(\d+)-(\d+)/, '$3/$2/$1') // '15/01/2024'(日期格式转换)
函数替换参数(match, p1, p2, ..., offset, string) => 新值。p1/p2 是捕获组,用得最多。
str.match(regex) / str.matchAll(regex)常用
作用
从字符串中提取匹配内容
参数
① 正则表达式。带 /g 返回所有匹配;不带返回第一个匹配+捕获组详情
场景
提取 URL 中所有数字、提取所有邮箱、解析日志
输出
match:数组或 null;matchAll:迭代器(要配 /g)
// match + /g:返回所有匹配的字符串 'a1b2c3'.match(/\d/g) // ['1','2','3'] // match 无 /g:返回第一个匹配 + 捕获组详情 '2024-01'.match(/(\d+)-(\d+)/) // ['2024-01', '2024', '01', index:0, input:'2024-01', groups:undefined] // matchAll:返回所有匹配 + 捕获组(ES2020) [...'a1b2c3'.matchAll(/([a-z])(\d)/g)] // [['a1','a','1'], ['b2','b','2'], ['c3','c','3']]
str.padStart(targetLen, padStr?) / padEnd常用
作用
用指定字符在头部/尾部填充到指定长度
场景
日期补零('1'→'01')、表格对齐、序号补零
参数
① 目标长度 ② 可选:填充字符(默认空格)
输出
string
'1'.padStart(2, '0') // '01'(日期补零) '5'.padStart(3, '0') // '005' 'abc'.padEnd(6, '*') // 'abc***' String(5).padStart(4, '0') // '0005'(序号补零) // 实战:倒计时显示 00:05 const fmtTime = s => `${Math.floor(s/60)}:${String(s%60).padStart(2,'0')}'`
str.trim() / trimStart / trimEnd常用
作用
去除字符串两端空白
参数
无参数
输出
返回新字符串,不修改原串
场景
用户输入清洗(去除首尾空格)、表单校验前预处理
' hello '.trim() // 'hello' ' hello '.trimStart() // 'hello ' ' hello '.trimEnd() // ' hello' // 也去掉换行符、tab '\n\t hello \n'.trim() // 'hello'
str.startsWith(prefix) / endsWith(suffix)常用
作用
判断字符串是否以某子串开头/结尾
参数
① 要判断的前缀/后缀字符串 ② 可选:起始位置(startsWith 的搜索起点)
输出
boolean
场景
判断 URL 协议(http://)、判断文件类型(.jpg)、权限判断
'https://a.com'.startsWith('https') // true 'photo.jpg'.endsWith('.jpg') // true 'isAdmin'.startsWith('is') // true(变量名判断)
str.indexOf(searchStr) / lastIndexOf常用
作用
查找子串首次/最后出现的位置,找不到返回 -1
参数
① 要查找的子串 ② 可选:起始搜索位置
输出
number 索引;找不到返回 -1
场景
判断是否包含(>=0)、定位字符位置、切割点查找
'a-b-c'.indexOf('-') // 1(第一个 - 的位置) 'a-b-c'.lastIndexOf('-') // 3(最后一个 - 的位置) 'abc'.indexOf('z') // -1(不存在) // 判断包含:indexOf >= 0 是 includes 的老写法
` 模板字符串 `必会
作用
用反引号包裹,支持变量插值和多行
参数
${} 内放任意 JS 表达式(变量、三元、函数调用)
输出
string
场景
拼接 HTML、拼接 URL、错误信息拼装
const name = 'Tom', age = 18 // 变量插值 ${} const msg = `姓名:${name},年龄:${age}` // '姓名:Tom,年龄:18' // 多行字符串(不用 \n) const html = `<div> <p>${name}</p> </div>` // 嵌套表达式 const status = age >= 18 ? '成年' : '未成年' const desc = `${name} 是${status}` // 标签模板(高级,常考) function tag(strings, ...values) { return strings.reduce((acc, s, i) => acc + s + (values[i] || ''), '') } tag`Hello ${name}!` // 'Hello Tom!'

📊 数组 API

数组方法是前端面试的绝对核心。map / filter / reduce 三件套必须形成肌肉记忆。

arr.map(callback)必会
作用
把数组每个元素映射成新值,返回新数组
场景
后端数据转前端展示、提取某个字段、格式化
参数
回调 (item, index, array) => 新值
输出
新数组(长度和原数组相同,不修改原数组)
const nums = [1, 2, 3] // 每个元素 ×2 nums.map(n => n * 2) // [2, 4, 6] // 提取对象字段(最常用!) const users = [{id:1,name:'Tom'}, {id:2,name:'Jerry'}] users.map(u => u.name) // ['Tom', 'Jerry'] users.map(u => u.id) // [1, 2] // 转换结构(VO → DTO) users.map(u => ({ label: u.name, value: u.id })) // [{label:'Tom',value:1}, {label:'Jerry',value:2}](下拉框数据) // 带索引 nums.map((n, i) => `第${i}个:${n}`) // ['第0个:1', '第1个:2', '第2个:3']
🔥 map vs forEach:map 返回新数组,forEach 返回 undefined。需要结果用 map,只遍历用 forEach
arr.filter(callback)必会
作用
过滤出满足条件的元素,返回新数组
场景
条件筛选(找出上架商品)、删除某项、权限过滤
参数
回调 (item, index, array) => boolean(true 保留)
输出
新数组(长度可能小于原数组)
const nums = [1, 2, 3, 4] nums.filter(n => n > 2) // [3, 4] // 筛选上架商品 products.filter(p => p.status === '上架') // 多条件筛选(搜索 + 分类) products.filter(p => p.name.includes(keyword) && p.category === selectedCategory ) // 删除某项(按 id) list.filter(item => item.id !== removeId) // 去除 falsy 值(0、null、''、false 都去掉) [0, 'a', null, 'b', ''].filter(Boolean) // ['a','b']
Boolean 技巧.filter(Boolean) 去除所有假值,是去除 null/undefined/空串的常用简写。
arr.reduce(callback, initialValue)必会
作用
把数组归并成单个值(累加、聚合、转对象)
场景
求和、数组转对象、分组、计算购物车总价
参数
回调 (累加器, 当前元素, 索引, 数组) => 新累加器;初始值
输出
单个值(类型取决于初始值)
const nums = [1, 2, 3, 4] // 求和 nums.reduce((sum, n) => sum + n, 0) // 10 // 购物车总价(最常见场景) cart.reduce((total, item) => total + item.price * item.qty, 0) // 数组转对象(以 id 为 key,业务高频) users.reduce((map, u) => (map[u.id] = u, map), {}) // { '1':{id:1,...}, '2':{id:2,...} } // 分组(按字段) users.reduce((groups, u) => { (groups[u.dept] ||= []).push(u) return groups }, {}) // { '技术':[{...}], '产品':[{...}] } // 找最大值 nums.reduce((max, n) => Math.max(max, n)) // 4 // 扁平化 [[1,2],[3,4]].reduce((acc, arr) => acc.concat(arr), []) // [1,2,3,4]
🔥 初始值很关键:不传初始值时,第一个元素当初始值,数组为空会报错。求和用 0,转对象用 {},转数组用 []
arr.find(callback) / findIndex / findLast常用
作用
找出第一个满足条件的元素 / 其索引
参数
回调 (item, index, array) => boolean
场景
按 id 查记录、查某状态的第一条订单
输出
find 返回元素本身(找不到返回 undefined);findIndex 返回索引(找不到返回 -1)
const users = [{id:1,name:'Tom'}, {id:2,name:'Jerry'}] // 找元素 users.find(u => u.id === 2) // {id:2, name:'Jerry'} users.find(u => u.id === 99) // undefined(不存在) // 找索引 users.findIndex(u => u.id === 2) // 1 users.findIndex(u => u.id === 99) // -1 // findLast(ES2023,从后往前找) [1,2,3,2,1].findLast(n => n === 2) // 2(找最后一个2)
find vs filter:find 找第一个就停(O(1)~O(n)),filter 遍历全部(O(n))。找一个用 find,找多个用 filter。
arr.some(callback) / every(callback)常用
作用
some:是否至少一个满足;every:是否全部满足
参数
回调 (item, index, array) => boolean
场景
some:判断有无管理员;every:表单全部校验通过
输出
boolean(短路求值:some 遇到 true 就停,every 遇到 false 就停)
const users = [{role:'admin'}, {role:'user'}] // some:有没有管理员? users.some(u => u.role === 'admin') // true // every:是不是都是管理员? users.every(u => u.role === 'admin') // false // 表单校验:所有字段都填了 const fields = [name, email, phone] fields.every(f => f.trim() !== '') // 全部非空才 true // 空数组特例 [].some(fn) // false(没元素满足) [].every(fn) // true(⚠️ 没元素不满足!空数组 every 返回 true)
🔥 空数组陷阱[].every() 返回 true(空真),使用前要先判空。这是面试常考的坑。
arr.forEach(callback)常用
作用
遍历数组,无返回值
参数
回调 (item, index, array) => void
场景
打印日志、批量操作 DOM、副作用操作
输出
undefined(不能链式调用)
const users = [{id:1,name:'Tom'}, {id:2,name:'Jerry'}] // 只是遍历打印 users.forEach(u => console.log(u.name)) // 批量操作 DOM document.querySelectorAll('.item').forEach(el => { el.style.color = 'red' }) // ❌ 错误用法:想用结果却用 forEach const names = users.forEach(u => u.name) // undefined!应该用 map
选型:需要新数组 → map;过滤 → filter;累加 → reduce;纯遍历 → forEach。forEach 不能 break/continue,要中断用 for...of。
arr.flat(depth?) — 降维扁平化ES2019
作用
把嵌套数组"压扁"。depth 指定压几层,默认 1,Infinity 压到底
参数
可选:深度 depth,默认 1,传 Infinity 压到底
场景
后端返回的数据嵌套了 List<List>,要拍平成一层展示
输出
返回新数组,不改原数组
// 降一层(默认) [1, [2, [3]]].flat() // [1, 2, [3]] // ↑ 外层方括号拆掉,但里面的 [3] 还在 // 降两层 [1, [2, [3]]].flat(2) // [1, 2, 3] // 压到底(不知道有几层就 Infinity) [1, [2, [3, [4]]]].flat(Infinity) // [1, 2, 3, 4] // 附带能力:去除空位(稀疏数组补齐) [1, , 3].flat() // [1, 3](中间的空 slot 被清掉)
arr.flatMap(callback) — map + 自动拍平一层必会
作用
对每个元素 map,然后把结果自动 flat(1)。等价于 arr.map(fn).flat(),但只遍历一次、性能更好
参数
回调 (item, index, array) => 数组或值(返回数组会被拆开)
输出
新数组(已自动 flat 一层),不修改原数组
关键限制
⚠️ 只能拍平 一层。回调返回深层嵌套数组时,里层不会继续被压扁
场景
一对一变多对多、提取嵌套字段、同时返回多个结果、过滤+转换二合一
// 📌 先看最基础的区别 // map:每个元素返回一个数组 → 得到"数组的数组" [1, 2, 3].map(n => [n, n * 10]) // [[1,10], [2,20], [3,30]] ← 二维数组,没拍平 // flatMap:map 完自动拆掉一层 → 得到一维数组 [1, 2, 3].flatMap(n => [n, n * 10]) // [1, 10, 2, 20, 3, 30] ← 一维!这就是 flatMap 的全部魔力 // ⚠️ 限制:只拍一层。返回嵌套数组时,里层还在 [1, 2].flatMap(n => [[n, n*10]]) // [[1,10], [2,20]] ← 还剩一层没拍掉(要 Infinity 用 flat)
flatMap 实战 5 例(这才是它真正好用的地方)
━━━ 例1:提取每篇文章的标签(一对一变多对多)━━━ const articles = [ { title: 'Vue', tags: ['前端', '框架'] }, { title: 'Redis', tags: ['后端', '缓存'] }, { title: 'MySQL', tags: ['后端', '数据库'] } ] // ❌ 用 map:得到 [['前端','框架'], ['后端','缓存'], ['后端','数据库']] articles.map(a => a.tags) // ✅ 用 flatMap:自动拍平 → ['前端','框架','后端','缓存','后端','数据库'] const allTags = articles.flatMap(a => a.tags) // 配合 Set 统计不重复标签 [...new Set(allTags)] // ['前端','框架','后端','缓存','数据库'] ━━━ 例2:给每个数字生成"原值+翻倍"两组数据(一对多展开)━━━ [1, 2, 3].flatMap(n => [n, n * 2]) // [1,2, 2,4, 3,6] // 场景:时间轴上每个时段要拆成"开始/结束"两条记录 ━━━ 例3:flatMap 同时做"过滤 + 转换"(比 filter+map 链式更省一次遍历)━━━ // 需求:从 [1,2,3,4] 中保留偶数,并都 ×10 [1, 2, 3, 4].flatMap(n => n % 2 === 0 ? [n * 10] : [] // 不要的返回空数组,自动被拍掉 ) // [20, 40] // 妙处:返回 [] 就等于"过滤掉",返回 [x] 就等于"保留",返回 [x,y] 就"展开" ━━━ 例4:把句子拆成单词(一句话 → 多个词)━━━ ['hello world', 'foo bar'].flatMap(s => s.split(' ')) // ['hello','world','foo','bar'] // 对比 map:[['hello','world'], ['foo','bar']],还要再 flat 一次 ━━━ 例5:把订单里的每个商品"摊开"成独立行(订单聚合 → 明细列表)━━━ const orders = [ { id: 1, items: [{name:'书',qty:2}, {name:'笔',qty:3}] }, { id: 2, items: [{name:'本',qty:1}] } ] orders.flatMap(o => o.items.map(it => ({ orderId: o.id, ...it }))) // [{orderId:1,name:'书',qty:2}, {orderId:1,name:'笔',qty:3}, {orderId:2,name:'本',qty:1}] // 一个订单一行 → 变成每个商品一行,还带上了 orderId
🔥 高频疑问:flatMap 能不能做"对象数组转树"?面试易错
结论先说:不能。这是最容易混淆的点。要看清你做的是哪个方向——
方向本质用 flatMap?用什么
树 → 扁平列表(拍平)展开 / 拆嵌套合适flatMap + 递归,或 flat
扁平列表 → 树(建树)聚合 / 找爸爸挂上去不行Map 建索引 O(n)
━━━ ✅ 方向一:树 → 扁平列表,flatMap 完美胜任 ━━━ const tree = [ { id: 1, name: '总部', children: [ { id: 2, name: '技术部', children: [ { id: 3, name: '前端组', children: [] } ]} ]} ] // 递归 + flatMap:把当前层 + 所有子层 摊到一个数组里 function treeToFlat(nodes) { return nodes.flatMap(node => [ { id: node.id, name: node.name }, ...treeToFlat(node.children || []) // 递归处理子节点,再展开 ]) } treeToFlat(tree) // [{id:1,name:'总部'}, {id:2,name:'技术部'}, {id:3,name:'前端组'}] // 原理:flatMap 把"自己 + 子树递归结果"自动拍平,比 reduce 写法更直观 ━━━ ❌ 方向二:扁平列表 → 树,flatMap 帮不上忙 ━━━ // 输入:[{id:1,pid:null}, {id:2,pid:1}, {id:3,pid:2}] // 目标:{id:1, children:[{id:2, children:[{id:3}]}]} // 这是"根据 pid 找爸爸再挂上去",是聚合不是展开,flatMap 没用武之地 // 正确做法:用 Map 建索引(O(n),前端建树的标准答案) function listToTree(list) { const map = {}, tree = [] list.forEach(item => map[item.id] = { ...item, children: [] }) list.forEach(item => { const node = map[item.id] if (item.pid) map[item.pid].children.push(node) // 挂到爸爸下面 else tree.push(node) // 没爸爸 → 顶层 }) return tree }
一句话记忆:flatMap 是「展开器」,只能把嵌套变扁(树→列表)。建树是「聚合器」的活,靠 Map 找爸爸挂孩子。方向反了,工具就用错了
flat vs flatMap 怎么选?
场景用 flat用 flatMap
已经拿到嵌套数组,只想压平arr.flat(Infinity)
一边 map 一边拍平arr.flatMap(fn)
要压两层以上flat(n)❌ 只能一层
map 回调返回多个值(一对多)✅ 最合适
判断口诀:要不要"转换 + 拍平一步到位"?要 → flatMap。只是单纯压已有的嵌套 → flat。
Array.from() / Array.of()常用
作用
from:把类数组/可迭代对象转真数组;of:用参数创建数组
参数
from(可迭代对象, 可选map函数);of(值1, 值2, ...)
输出
返回新数组
场景
from:arguments 转、Set/Map 转、querySelectorAll 转;生成序列数组
// 类数组转真数组 Array.from(document.querySelectorAll('div')) // NodeList → 真数组 Array.from(new Set([1,2,2])) // Set → 数组 // 第二参数:map 函数(边转边处理) Array.from('123', Number) // [1, 2, 3](字符串转数字数组) Array.from({length:5}, (_, i) => i) // [0,1,2,3,4](生成序列!) // Array.of:弥补 Array() 的坑 Array.of(3) // [3] Array(3) // [empty × 3](⚠️ 这是长度3的空数组!) Array.of(1, 2, 3) // [1, 2, 3]
生成序列Array.from({length:n}, (_, i) => i) 是生成 [0,1,...,n-1] 的标准写法,必记。
arr.sort(compareFn?)必会
作用
原地排序(修改原数组),返回排序后的数组
输出
返回排序后的原数组(⚠️ 原地修改,非新数组)
场景
列表按时间/价格/状态排序
参数
回调 (a, b) => 负数|0|正数。<0 a 前,>0 b 前,=0 不变
// 🔥 默认按字符串排序(大坑!) [10, 2, 1, 21].sort() // [1, 10, 2, 21] —— 不是数字序! // 数字升序 [10, 2, 1].sort((a, b) => a - b) // [1, 2, 10] // 数字降序 [10, 2, 1].sort((a, b) => b - a) // [10, 2, 1] // 对象按字段排序 users.sort((a, b) => new Date(a.createTime) - new Date(b.createTime)) // 多字段排序(先按 status,相同再按 time) orders.sort((a, b) => { if (a.status !== b.status) return a.status - b.status return new Date(b.time) - new Date(a.time) }) // 中文按拼音排序 ['北京', '上海', '广州'].sort(new Intl.Collator('zh-CN').compare)
🔥 两大坑:① 默认按字符串排(10 排在 2 前面);② 原地排序,修改原数组!要保留原数组先 [...arr].sort()
arr.slice(start?, end?) / splice / concat常用
作用
slice:截取(不改原数组);splice:增删改(改原数组);concat:拼接
参数
slice(start,end?) 截取;splice(index,删除数,新增项) 增删改;concat(...arr) 拼接
输出
slice/concat 返回新数组;splice 返回被删元素数组并修改原数组
// slice:截取,不改原数组(和字符串 slice 一样) [1, 2, 3, 4].slice(1, 3) // [2, 3] [1, 2, 3, 4].slice(-2) // [3, 4] [1, 2, 3].slice() // 浅拷贝(slice 不带参数 = 浅拷贝) // splice:增删改,修改原数组 const arr = [1, 2, 3, 4, 5] arr.splice(1, 2) // 删除:从索引1删2个 → [2,3],arr 变 [1,4,5] arr.splice(1, 0, 'a', 'b') // 插入:在索引1前插入 → arr 变 [1,'a','b',4,5] arr.splice(1, 1, 'X') // 替换:删1个插1个 → arr 变 [1,'X','b',4,5] // concat:拼接 [1, 2].concat([3, 4]) // [1,2,3,4] [1, 2].concat(3, [4]) // [1,2,3,4](参数会被扁平化一层) // 现代写法:用扩展运算符 [...a, ...b]
🔥 slice vs splice:差一个字母意思完全相反!slice 返回副本(不改原数组),splice 修改原数组返回被删的元素。面试必问。
扩展运算符 ...arr必会
作用
展开数组/可迭代对象
参数
任意可迭代对象(数组、字符串、Set、Map、arguments)
输出
展开后的元素序列
场景
数组复制、合并、转参数、去重
// 数组复制(浅拷贝) const copy = [...arr] // 数组合并 const merged = [...arr1, ...arr2] // 数组去重 [...new Set([1, 2, 2, 3])] // [1, 2, 3] // 把数组展开成函数参数 Math.max(...[1, 5, 3]) // 等价 Math.max(1, 5, 3) → 5 // 把 NodeList 转 数组 [...document.querySelectorAll('div')] // 解构赋值配合 const [first, ...rest] = [1, 2, 3, 4] // first=1, rest=[2,3,4]

🗂️ 对象 API

Object.keys/values/entries 三件套 + assign/fromEntries,是对象操作的核心。

Object.keys(obj) / values / entries必会
作用
获取对象的所有 键 / 值 / 键值对数组
参数
目标对象 obj
场景
遍历对象、统计属性数量、对象转数组
输出
keys: string[];values: value[];entries: [key, value][]
const user = { name: 'Tom', age: 18, city: '北京' } Object.keys(user) // ['name', 'age', 'city'] Object.values(user) // ['Tom', 18, '北京'] Object.entries(user) // [['name','Tom'], ['age',18], ['city','北京']] // 遍历对象(最常用) Object.entries(user).forEach(([key, value]) => { console.log(key, value) }) // 统计属性数量 Object.keys(user).length // 3 // 判断对象是否为空 Object.keys(user).length === 0 // false
Object.assign(target, ...sources)必会
作用
合并对象,修改 target 返回 target
参数
① target 目标对象(会被修改)② ...sources 源对象(后者覆盖前者)
场景
合并配置、浅拷贝、对象扩展
输出
返回 target(已合并)
const defaults = { api: '/api', timeout: 5000 } const userConfig = { timeout: 10000 } // 合并到新对象(浅拷贝) Object.assign({}, defaults, userConfig) // { api:'/api', timeout:10000 }(后者覆盖前者) // 合并到现有对象(修改 target) const obj = { a: 1 } Object.assign(obj, { b: 2 }, { c: 3 }) // obj 变成 {a:1, b:2, c:3} // 扩展运算符等价写法(推荐) { ...defaults, ...userConfig } // 合并多个对象 Object.assign({}, obj1, obj2, obj3) // 后者覆盖前者
🔥 浅合并:嵌套对象是整体覆盖不是递归合并。{a:{x:1}} 和 {a:{y:2}} 合并得到 {a:{y:2}},不是 {a:{x:1,y:2}}。深合并要自己写。
Object.fromEntries(entries)ES2019
作用
把键值对数组转回对象(entries 的逆操作)
参数
键值对数组 [[k, v], ...] 或 Map、URLSearchParams
输出
新对象
场景
Map 转对象、URL 参数转对象、过滤对象属性
// entries 的逆操作 Object.fromEntries([['name', 'Tom'], ['age', 18]]) // { name:'Tom', age:18 } // Map → Object Object.fromEntries(new Map([['k1', 'v1'], ['k2', 'v2']])) // URLSearchParams → Object Object.fromEntries(new URLSearchParams('?name=Tom&age=18')) // { name:'Tom', age:'18' } // 过滤对象属性(保留 age > 18 的) Object.fromEntries( Object.entries(users).filter(([k, v]) => v.age > 18) ) // 转换属性值(key 不变,value 翻倍) Object.fromEntries( Object.entries(prices).map(([k, v]) => [k, v * 2]) )
Object.freeze(obj) / isFrozen常用
作用
冻结对象:使其属性不可增删改(浅冻结,嵌套对象还能改)
场景
常量配置、状态管理(Vuex/Pinia)防止用户修改配置对象
参数
要冻结的对象 obj
输出
返回被冻结的对象(同引用)
const config = Object.freeze({ API_BASE: '/api', MAX_SIZE: 100 }) config.API_BASE = '/other' // 静默失败(严格模式抛错) config.newProp = 'x' // 静默失败 Object.isFrozen(config) // true // ⚠️ 只冻结一层,嵌套对象还能改 const obj = Object.freeze({ nested: { a: 1 } }) obj.nested.a = 99 // 成功!只冻结了顶层 // 深冻结(递归) function deepFreeze(obj) { Object.keys(obj).forEach(key => { if (typeof obj[key] === 'object') deepFreeze(obj[key]) }) return Object.freeze(obj) }
🔥 freeze vs const:const 防止变量重新赋值;freeze 防止对象属性被修改。const + freeze 才是完整的常量保护。
Object.hasOwnProperty(prop) / Object.hasOwn(obj, prop)常用
作用
判断对象自身(非继承)是否拥有某属性
参数
属性名(字符串 / Symbol)
输出
boolean
场景
遍历对象时过滤继承属性、判断字段是否存在
const obj = { a: 1 } 'a' in obj // true(包括继承的) 'toString' in obj // true(继承自 Object.prototype) obj.hasOwnProperty('a') // true(只看自身) obj.hasOwnProperty('toString') // false(继承的不算) // ⚠️ hasOwnProperty 的问题:如果对象自己定义了同名方法 const bad = { hasOwnProperty: null } bad.hasOwnProperty('a') // 报错! // ✅ ES2022 推荐写法 Object.hasOwn(obj, 'a') // true(静态方法,更安全)
in vs hasOwnPropertyin 会查原型链(包括继承);hasOwnProperty 只看自身。遍历对象时用 hasOwnProperty 更安全。
in / delete / Reflect.ownKeys常用
作用
in:判断属性是否存在(含继承);delete:删除属性;Reflect.ownKeys:获取所有键(含 Symbol)
参数
in:属性名;delete:属性名;Reflect.ownKeys:对象
输出
in:boolean;delete:boolean;Reflect.ownKeys:键数组(含 Symbol)
const obj = { a: 1, b: 2, [Symbol('s')]: 3 } 'a' in obj // true delete obj.b // true(删除属性 b,obj 变 {a:1, [Symbol]:3}) 'b' in obj // false(已删除) // Reflect.ownKeys:获取所有键(包括 Symbol) Reflect.ownKeys(obj) // ['a', Symbol(s)](Object.keys 拿不到 Symbol) // ⚠️ delete 不能删除不可配置属性 const obj2 = {} Object.defineProperty(obj2, 'x', { configurable: false, value: 1 }) delete obj2.x // false(删不掉)

📦 解构赋值

ES6 最实用特性之一。函数参数、接口数据处理、配置读取全靠它。

数组解构必会
作用
按位置从数组取值赋给变量
场景
交换变量、解析函数返回的多值、取首尾
语法
const [a, b] = arr,配合默认值 [a, b=10]=arr 和 rest [first, ...rest]=arr
输出
解构出的变量;位置没对应则为 undefined
// 基础 const [a, b, c] = [1, 2, 3] // a=1, b=2, c=3 // 跳过某些 const [first, , third] = [1, 2, 3] // first=1, third=3(跳过第二个) // 剩余参数 rest const [first, ...rest] = [1, 2, 3, 4] // first=1, rest=[2,3,4] // 默认值 const [a, b = 10] = [1] // a=1, b=10(b 没值用默认) // 交换变量(经典用法) let x = 1, y = 2 [x, y] = [y, x] // x=2, y=1(不用临时变量!) // 解析函数多返回值 const [data, error] = await fetchData() // 嵌套解构 const { a: [x, y] } = { a: [1, 2] } // x=1, y=2
对象解构按属性名
作用
按属性名从对象取值赋给变量
场景
接口数据取字段、函数参数、配置读取、props
语法
const {name, age} = obj;重命名 {name:userName};默认值 {age=18};rest {id, ...rest}
输出
解构出的变量;属性不存在则为 undefined(有默认值时用默认)
// 基础 const { name, age } = { name: 'Tom', age: 18 } // name='Tom', age='18' // 重命名(重要!原名: 新名) const { name: userName, age: userAge } = user // userName = user.name, userAge = user.age // 默认值 const { name, age = 18 } = { name: 'Tom' } // age=18(没传用默认) // 重命名 + 默认值(实战最常用) const { data: list = [] } = response // response.data 取不到给 [] // 嵌套解构 const { user: { name } } = response // 直接取到 response.user.name // 函数参数解构(Vue/React 最常用) function render({ title, content = '', visible = true }) { // 直接用 title/content/visible,有默认值 } render({ title: '标题' }) // content='', visible=true // rest 解构(剩余属性) const { id, ...rest } = user // id=user.id, rest=其余所有属性 // 常用于:提交时去掉 id
实战技巧:解构时配合默认值 { data = [] } = res,能优雅地处理接口返回空值,避免 undefined 报错。

⑤ Promise 与异步

现代异步编程基础。async/await 是 ES2017 引入的语法糖,但底层仍是 Promise。

Promise.all / allSettled / race / any必会
作用
批量处理多个 Promise
参数
一个 Promise 数组(或任何可迭代对象)
输出
都返回 Promise,resolve 结果见下表(all 返回数组、allSettled 返回状态数组等)
场景
all:加载多张图片后显示;allSettled:批量请求不中断;race:超时控制
方法行为全部成功有一个失败
Promise.all全部成功才成功✅ 返回结果数组❌ 立即 reject
Promise.allSettled等全部完成(无论成败)✅ 返回 [{status:'fulfilled', value}]✅ 返回 [{status:'rejected', reason}]
Promise.race第一个完成(无论成败)✅ 返回第一个的结果❌ 返回第一个的失败
Promise.any第一个成功✅ 返回第一个成功结果❌ 全失败才报 AggregateError
// all:并行请求多个接口,全成功才渲染页面 const [user, orders, coupons] = await Promise.all([ getUser(), getOrders(), getCoupons() ]) // allSettled:批量请求,即使部分失败也不影响其他 const results = await Promise.allSettled( urls.map(url => fetch(url)) ) results.forEach(r => { if (r.status === 'fulfilled') console.log('成功', r.value) else console.error('失败', r.reason) }) // race:超时控制(5秒内必须返回,否则失败) await Promise.race([ fetch('/api/data'), new Promise((_, reject) => setTimeout(() => reject(new Error('超时')), 5000)) ]) // any:尝试多个镜像源,谁快用谁 const data = await Promise.any([ fetch(mirror1Url), fetch(mirror2Url), fetch(mirror3Url) ])
🔥 all vs allSettled:all 有一个失败就全部失败(用不了);allSettled 无论成败都返回,失败的有 reason。批量请求用 allSettled 更稳健
async / await必会
作用
用同步写法写异步代码,避免回调地狱
语法
async 标记函数(返回 Promise),await 暂停等待 Promise 完成
场景
接口请求、文件读取、所有异步操作
输出
async 函数总是返回 Promise;await 表达式的值是 Promise resolve 的结果
// 基础:async 函数返回 Promise,await 等待 Promise 完成 async function loadData() { const res = await fetch('/api/user') // 等待请求完成 const data = await res.json() return data // 返回值会被包成 Promise } // 错误处理:用 try/catch async function safeLoad() { try { const data = await fetchData() return data } catch (err) { console.error('加载失败', err) return null } } // 并发请求(不要一个一个 await) // ❌ 慢:串行(总耗时 = 各请求耗时之和) const a = await getA() const b = await getB() // ✅ 快:并行(总耗时 = 最慢的那个) const [a, b] = await Promise.all([getA(), getB()]) // IIFE:顶层不能用 await 时用立即执行函数 ;(async () => { const data = await loadData() })()
🔥 串行 vs 并行:连续 await 是串行(慢);用 Promise.all 包起来是并行(快)。这是性能优化的高频考点。

🧩 ES6 其他特性

let/const、箭头函数、Set/Map、Proxy、class 等。

let / const / var必会
特性varletconst
作用域函数作用域块级作用域 {}块级作用域 {}
变量提升✅ 提升且初始化为 undefined❌ 暂时性死区(TDZ)❌ 暂时性死区
重复声明✅ 允许❌ 报错❌ 报错
可重新赋值❌(引用不可变,属性可变)
挂到 window✅(浏览器)
// var 的提升(坑) console.log(x) // undefined(不报错,已提升) var x = 1 // let 的暂时性死区 console.log(y) // ReferenceError! TDZ let y = 1 // for 循环闭包(经典面试题) // ❌ var:所有 i 都是 3(共享同一个 i) for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100) // 3,3,3 } // ✅ let:每个迭代有独立的 i for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100) // 0,1,2 } // const 的陷阱:引用不可变,内容可变 const obj = { a: 1 } obj.a = 2 // ✅ 成功(修改属性可以) obj = {} // ❌ 报错(重新赋值不行)
最佳实践:默认用 const,需要重新赋值才用 let永远不要用 var
箭头函数 () => {}必会
作用
更简洁的函数写法,没有自己的 this/arguments,不能 new
语法
(参数) => { 函数体 };单行可省略 return;单参数可省略括号;返回对象要 () => ({...})
场景
回调函数、map/filter/reduce 的回调、React 函数组件
输出
函数本身的返回值(this 继承外层作用域,不是调用者)
// 各种简写 const add = (a, b) => a + b // 单行,自动 return const greet = name => `Hi ${name}` // 单参数省略括号 const noop = () => {} // 无参数 const getObject = () => ({ a: 1 }) // 返回对象要加括号! // 🔥 this 指向区别(最重要) const obj = { name: 'Tom', // 普通函数:this 指向调用者(obj) normal() { return this.name }, // 'Tom' // 箭头函数:没有 this,继承外层(全局/window) arrow: () => this.name // undefined(window.name) } // 🔥 箭头函数不能用 new const Foo = () => {} new Foo() // TypeError: Foo is not a constructor // 🔥 没有 arguments const fn = () => console.log(arguments) // 报错!用 ...args 代替 const fn2 = (...args) => console.log(args) // ✅
🔥 this 指向:箭头函数没有自己的 this,继承外层作用域的 this。对象方法不要用箭头函数(this 不指向对象)。
Set / WeakSet常用
作用
Set:存储唯一值的集合;WeakSet:只能存对象引用(弱引用)
参数
可选:初始可迭代对象(如数组)
输出
Set 实例(有 size、add/has/delete 方法)
场景
数组去重、判断存在性、存储已访问标记
// Set 基础 const set = new Set([1, 2, 2, 3]) // Set {1, 2, 3}(自动去重) set.add(4) // 添加 set.has(2) // true(判断存在) set.delete(2) // true(删除) set.size // 3(数量) // 数组去重(最常用) [...new Set([1, 2, 2, 3])] // [1, 2, 3] // 字符串去重 [...new Set('abca')].join('') // 'abc' // 判断两个数组是否有交集 const hasIntersect = (a, b) => { const setB = new Set(b) return a.some(x => setB.has(x)) } // 遍历(注意:Set 没有索引,用 for...of 或 forEach) for (const item of set) { console.log(item) }
WeakSet 用途:给 DOM 节点打"已访问"标记,节点销毁时标记自动被 GC,不造成内存泄漏。日常开发用得不多,了解即可。
Map / WeakMap常用
作用
Map:键可为任意类型的键值对;WeakMap:键必须为对象(弱引用)
参数
可选:初始键值对数组 [[k,v],...]
输出
Map 实例(有 size、get/set/has/delete 方法)
场景
存储键值对(键不是字符串时)、缓存数据、存储对象关联数据
// Map 基础 const map = new Map() map.set('name', 'Tom') // 设置 map.set(123, '数字键') // 键可以是数字 map.set(document.body, 'DOM节点') // 键可以是对象! map.get('name') // 'Tom'(取值) map.has('name') // true(判断) map.delete('name') // 删除 map.size // 数量 // Map vs Object // Object 的键只能是 string/Symbol;Map 的键可以是任意类型 // Map 有 size、保留插入顺序、增删性能更好 // Map 与数组互转 const map = new Map([['a', 1], ['b', 2]]) // 数组 → Map [...map] // Map → 数组 [['a',1],['b',2]] // WeakMap:键必须是对象,不阻止垃圾回收 // 用途:给 DOM 节点附加数据(节点销毁时自动清理) const wm = new WeakMap() wm.set(domNode, { clickedTimes: 0 }) wm.get(domNode) // { clickedTimes: 0 }
何时用 Map:① 键不是字符串(如对象/DOM);② 需要频繁增删键值对;③ 需要知道大小(size)。其他情况用 Object 即可。
数值分隔符 / 数字方法ES2021
作用
大数字加下划线增强可读性;数值方法补充
语法
数字字面量任意位置加 _(如 1_000_000);Number.isInteger/isFinite/isNaN 等静态方法
输出
数值分隔符返回数字;各 Number/Math 方法返回对应值
// 数值分隔符 1_000_000 // 1000000(可读性更好) 1_000_000.500 // 1000000.5 // Number.isInteger Number.isInteger(10) // true Number.isInteger(10.5) // false // Number.isFinite / isNaN(比全局更严格) Number.isFinite(100) // true Number.isFinite(Infinity) // false Number.isNaN(NaN) // true // Number.isSafeInteger(安全整数范围) Number.isSafeInteger(9007199254740991) // true(最大安全整数) Number.isSafeInteger(9007199254740992) // false(超出!) // Math 新方法 Math.trunc(4.9) // 4(只取整数部分) Math.sign(-5) // -1(判断正负) Math.cbrt(27) // 3(立方根)

🚀 ES2020+ 新特性

现代前端项目高频使用的较新 API,面试加分项。

可选链 ?.必会
作用
安全访问深层属性,避免 Cannot read property of undefined
参数
无(语法:对象?.属性?.方法?.())
输出
中间任一层为 null/undefined 时返回 undefined,不报错
场景
取后端数据嵌套字段(用户地址城市)、链式调用
// 旧写法(繁琐) const city = user && user.address && user.address.city // 新写法(优雅) const city = user?.address?.city // 任意一层为空返回 undefined,不报错 // 方法调用 const name = user?.getName?.() // 如果 getName 不存在,返回 undefined // 数组下标 const first = arr?.[0] // 如果 arr 是 undefined 不报错 // 函数调用(判断是否是函数) callback?.() // callback 是 undefined 不调用,不报错 // 实战:取接口数据 const cityName = response?.data?.user?.address?.city ?? '未知'
价值:把 a && a.b && a.b.c 简化为 a?.b?.c。前端取后端数据几乎是必用的 API。
空值合并 ??必会
作用
当左侧为 null 或 undefined 时才用右侧值
参数
左侧 ?? 右侧:左侧表达式 + 右侧默认值
输出
左侧为 null/undefined 时返回右侧,否则返回左侧
场景
设置默认值(比 || 更精准)
// 区别:|| 和 ?? 0 || 'default' // 'default'(0 是 falsy,被跳过) 0 ?? 'default' // 0(只有 null/undefined 才用默认)✅ '' || 'default' // 'default'(空串是 falsy) '' ?? 'default' // ''(空串是有效值,保留)✅ false || 'default' // 'default' false ?? 'default' // false(false 是有效值)✅ // 实战:接口返回 0 或空串不要被当成"没值" const count = response.count ?? 0 // count 是 0 时保留 0 const name = user.name ?? '匿名' // name 是 null/undefined 才用匿名 // 配合可选链(黄金搭档) const city = user?.address?.city ?? '未知'
🔥 || vs ??:|| 对所有 falsy 值生效(0、''、false 都跳过);?? 只对 null/undefined 生效。设置默认值优先用 ??
BigIntES2020
作用
表示大于 2^53-1 的大整数
参数
数字字面量加 n,或 BigInt(字符串)
输出
bigint 类型(不能与普通 Number 直接运算)
场景
处理雪花 ID、数据库 bigint、时间戳纳秒
// 普通数字精度丢失 9007199254740992 === 9007199254740993 // true!精度丢失 // BigInt 解决 9007199254740992n === 9007199254740993n // false ✅ // 两种创建方式 const big = 123n // 数字后加 n const big2 = BigInt('12345678901234567890') // 函数转换 // 运算 1n + 2n // 3n 10n / 3n // 3n(整数除法,向下取整) // ⚠️ 不能和普通数字混用 1n + 1 // TypeError!必须都是 BigInt 1n + BigInt(1) // 2n ✅ // 实战:处理 Java 后端的雪花 ID // Java long 最大值是 2^63-1,超过 JS Number 安全范围 // 后端返回字符串,前端用 BigInt 处理
structuredClone(obj)ES2022
作用
原生深拷贝 API,替代 JSON.parse(JSON.stringify())
参数
要深拷贝的对象
输出
深拷贝后的新对象(支持循环引用/Date/Map/Set)
场景
深拷贝对象、状态快照、编辑表单数据备份
// 旧的深拷贝方式(有 6 个坑) JSON.parse(JSON.stringify(obj)) // ❌ 函数丢失、Symbol 丢失、Date 变字符串、循环引用报错 // 新的原生 API(推荐) const copy = structuredClone(obj) // ✅ 支持循环引用、Date、RegExp、Map、Set、ArrayBuffer // ❌ 不支持:函数(报错)、DOM 节点 // 实战:表单编辑前备份 const backup = structuredClone(formData) // 用户取消编辑 → 恢复 const onCancel = () => { formData = structuredClone(backup) }
对比:structuredClone 比 JSON 方式支持循环引用和特殊类型,但不能拷贝函数。三种深拷贝:JSON(最简单有坑)→ structuredClone(推荐)→ 手写(最灵活)。
String.prototype.replaceAllES2021
作用
替换所有匹配的子串(不用正则)
参数
① 要替换的子串或正则(无 /g 也行)② 替换为的内容
输出
string
// 旧:全局替换需要正则 'a-b-c'.replace(/-/g, '+') // 'a+b+c' // 新:直接用 replaceAll 'a-b-c'.replaceAll('-', '+') // 'a+b+c' // 实战:模板变量替换 const template = '{{name}} 你好,{{name}} 再见' template.replaceAll('{{name}}', 'Tom') // 'Tom 你好,Tom 再见' // 危险词过滤 content.replaceAll('敏感词', '***')

🔄 类型转换速查

JS 隐式类型转换是面试必考。核心规则:+ 拼接、== 比较、if 判断。

场景规则示例
转 Boolean6 个 falsy:false/0/''/null/undefined/NaNBoolean('') → false
+ 拼接有字符串就全转字符串'1' + 2 → '12'
数学运算- * / 都转数字'10' - 5 → 5
== 比较不同类型会转换后比较1 == '1' → true
=== 比较不转换,类型不同直接 false1 === '1' → false
if 判断转 Booleanif (0) → false 分支
显式转换常用写法必会
// 字符串 → 数字 Number('123') // 123 parseInt('123.9') // 123(只取整数) parseFloat('123.9')// 123.9 '123' * 1 // 123(隐式转换) '123' - 0 // 123 +'123' // 123(一元运算符) // 数字 → 字符串 String(123) // '123' (123).toString() // '123' 123 + '' // '123'(最常用) // 任意 → 布尔 Boolean('a') // true !!'a' // true(双感叹号) Boolean(0) // false(0 是 falsy) Boolean('') // false(空串是 falsy) // 转整数 Math.floor(3.7) // 3(向下取整) Math.ceil(3.2) // 4(向上取整) Math.round(3.5) // 4(四舍五入) 3.7 | 0 // 3(位运算取整,最快) ~~3.7 // 3(双位非取整)
🔥 == 隐式转换规则表面试必考
表达式结果原因
null == undefinedtrue规定相等(仅此一对)
null == 0falsenull 只和 undefined 相等
NaN == NaNfalseNaN 和谁都不等(包括自己)
1 == '1'true字符串转数字
0 == falsetruefalse 转 0
0 == ''true空串转 0
'' == falsetrue都转 0
[] == falsetrue[] 转空串再转 0
[] == ![]trueJS 最经典坑题
建议永远用 ===,不用 ==。除非判断 null/undefined:x == null(能同时匹配两者)。这是社区共识。
🎯 复习重点(按面试频率):
数组三件套 map/filter/reduce(必须形成肌肉记忆)
解构赋值 + 扩展运算符(写代码天天用)
可选链 ?. + 空值合并 ??(现代 JS 必备)
Promise.all/allSettled(批量请求必考)
let/const + 箭头函数 this(基础必问)
== vs === 隐式转换(经典考点)

一句话总结:API 不是用来背的,是用出来的。把 map/filter/reduce/解构/可选链这 5 个用熟,覆盖 80% 日常开发。