主题
第 06 章 · 面试题集(ES6+ 新特性)
Q1:let、const、var 的区别?
考察点:作用域、变量提升、TDZ
标准答案:
| 维度 | var | let | const |
|---|---|---|---|
| 作用域 | 函数 | 块级 | 块级 |
| 变量提升 | ✅(值 undefined) | ✅(TDZ) | ✅(TDZ) |
| 重复声明 | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ | ✅ | ❌ |
| 顶层声明挂 window | ✅ | ❌ | ❌ |
通俗理解: var 像不带门的房间,整个楼层都能看见;let/const 像带门的房间,出门就看不见了。
追问:
- 什么是 TDZ?(暂时性死区——
let/const声明前访问会报错) - 为什么
for(let i)+ setTimeout 能正常输出?(每次迭代创建新的词法环境)
Q2:箭头函数和普通函数的区别?
考察点:箭头函数特性
标准答案:
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
| this | 调用时决定 | 定义时决定(外层) |
| arguments | 有 | 无(用 ...args) |
| prototype | 有 | 无 |
| 能 new | ✅ | ❌ |
| call/bind 改 this | ✅ | ❌ |
| 生成器 | ✅ | ❌ |
应用场景判断:
- 对象方法、需要 this 指向对象的:用普通函数
- 回调函数、需要保持外层 this 的:用箭头函数
- React class 组件中的事件处理:箭头函数自动绑定 this
Q3:解构赋值的应用场景?
考察点:ES6 语法熟练度
标准答案:
js
// 1. 函数参数
function fetch({ url, method = 'GET', timeout = 3000 }) {}
// 2. 交换变量
let a = 1, b = 2;
[a, b] = [b, a];
// 3. 函数返回多值
function getUser() { return { name: 'Tom', age: 18 }; }
const { name, age } = getUser();
// 4. 数组取部分
const [first, , third] = [1, 2, 3];
// 5. 配合默认值
const { theme = 'light', size = 'md' } = userPrefs;
// 6. 嵌套解构
const { data: { users: [firstUser] } } = response;追问:
- 解构默认值什么时候生效?(仅 undefined,null 不会)
- 已声明变量解构对象怎么写?(
({a, b} = obj))
Q4:扩展运算符(spread)和剩余参数(rest)的区别?
考察点:... 用法
标准答案:
- spread(扩展):把可迭代对象"摊开",常出现在赋值号右侧、函数实参里
- rest(剩余):把多个值"打包"成数组,常出现在函数形参、解构左侧
js
// spread
const arr = [1, ...[2, 3], 4];
const obj = { ...defaults, ...userInput };
Math.max(...[1, 2, 3]);
// rest
function sum(...nums) { return nums.reduce((a, b) => a + b); }
const [first, ...others] = [1, 2, 3, 4];
const { a, ...rest } = { a: 1, b: 2, c: 3 };通俗理解: spread 是"打散";rest 是"打包"。位置不同含义不同。
追问:
{...obj}是深拷贝吗?(不是,浅拷贝)- 怎么实现深拷贝?(
structuredClone、JSON、自己递归)
Q5:模板字符串相比字符串拼接有什么优势?
考察点:模板字符串
标准答案:
- 支持多行
- 支持表达式插值
${expr} - 支持标签模板做高级处理
- 可读性远胜
'a' + b + 'c'
js
// 老
const html = '<div class="' + cls + '">' +
'<p>' + content + '</p></div>';
// 新
const html = `<div class="${cls}"><p>${content}</p></div>`;
// 标签模板:HTML 转义、SQL 防注入
function safeHtml(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? escapeHtml(values[i]) : ''), '');
}追问:
- 标签模板的应用场景?(i18n、styled-components、HTML/SQL 转义)
Q6:ES6 Class 是真正的类吗?说说 class 的特性
考察点:class 与原型继承
标准答案: 本质是构造函数 + 原型链的语法糖,不是 Java 那种"真正的类"。
特性:
- 必须
new调用,否则报错 - 默认严格模式
- 类声明不会被提升(TDZ)
- 方法不可枚举(普通构造函数加在 prototype 上的方法是可枚举的)
- 支持
static、get/set、#私有字段、extends/super
js
class Animal {
static type = '生物';
#age = 0;
constructor(name) { this.name = name; }
eat() {}
static describe() {}
}
class Dog extends Animal {
constructor(name) { super(name); }
}追问:
extends的实现原理?(双重原型链:Sub.__proto__ = Super处理静态继承,Sub.prototype.__proto__ = Super.prototype处理实例继承)- super 是什么?
Q7:ESM 和 CommonJS 的区别?
考察点:模块化系统
标准答案:
| 维度 | ESM | CommonJS |
|---|---|---|
| 语法 | import / export | require / module.exports |
| 加载时机 | 编译时(静态) | 运行时(动态) |
| 输出 | 引用(live binding) | 拷贝 |
| 异步 | ✅ | ❌(同步) |
| Tree-Shaking | ✅ | ❌ |
| 浏览器原生 | ✅ | ❌(需打包) |
| 顶层 this | undefined | module.exports |
| 支持动态加载 | ✅(import()) | ✅(require 本就动态) |
代码示例:
js
// ESM
import { foo } from './a.js';
export const bar = 1;
const dyn = await import('./b.js');
// CommonJS
const { foo } = require('./a.js');
module.exports = { bar: 1 };追问:
- 为什么 ESM 能 Tree-Shaking?(静态分析 export,未用到的删除)
- Node 怎么用 ESM?(package.json 加
"type": "module"或.mjs)
Q8:可选链 ?. 和空值合并 ?? 解决了什么问题?
考察点:ES2020 特性
标准答案:
可选链 ?.:
- 链式访问深层属性时,中间出现 null/undefined 不报错,直接返回 undefined
- 替代
a && a.b && a.b.c
js
const name = user?.profile?.name; // 安全访问
fn?.(); // 函数可选调用
arr?.[0]; // 数组可选访问空值合并 ??:
||把 0、''、false 都当 falsy;??只对null和undefined生效- 给"零是合法值"的场景救命
js
const port = config.port ?? 3000; // port=0 时不会被覆盖
const max = options.max || 10; // ❌ max=0 时变成 10追问:
??不能直接和||、&&混用?(语法报错,需加括号)- 可选链能用在赋值左边吗?(不能)
Q9:Map 和 Object 的区别?什么时候用 Map?
考察点:数据结构
标准答案:
| 维度 | Map | Object |
|---|---|---|
| 键类型 | 任意(包括对象、函数) | 字符串 / Symbol |
| 顺序 | 严格按插入顺序 | 整数键先排序 |
| 大小 | map.size | Object.keys(o).length |
| 默认原型 | 无 | Object.prototype(污染风险) |
| 迭代 | 直接 for..of | 先取 keys |
| 性能 | 频繁增删更优 | 查询略快 |
何时用 Map:
- 键是非字符串(如 DOM 节点 → 元数据)
- 频繁增删
- 需要保持插入顺序
- 担心原型污染
追问:
- WeakMap 和 Map 的区别?(键弱引用,不阻止 GC,键必须是对象,不可遍历)
- 用 WeakMap 实现私有变量?
Q10:Set 有什么用?怎么数组去重?
考察点:Set 数据结构
标准答案: Set 是无重复值的集合。
js
// 数组去重
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1,2,3]
// 求交集
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const intersection = [...a].filter(x => b.has(x)); // [2, 3]
// 求差集
const diff = [...a].filter(x => !b.has(x)); // [1]注意:Set 用 SameValueZero 算法判断相等,不能去重对象(引用不同就视为不同)。
js
[...new Set([{a:1}, {a:1}])]; // 仍是两个对象追问:
- 怎么用 Set 实现 LRU 缓存?
- Set 里
NaN === NaN为 false,但 Set 怎么处理?(Set 把 NaN 视为相等,不会重复)
Q11:Symbol 有什么用?
考察点:Symbol 应用
标准答案: Symbol 是 ES6 引入的第七种基本类型,每个 Symbol 都独一无二。
应用场景:
- 唯一常量 / 状态枚举:
STATUS.PENDING = Symbol('pending') - 对象唯一键:避免覆盖
- Well-known Symbols:让对象具备特殊行为,如
Symbol.iterator、Symbol.asyncIterator
js
class Range {
constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() {
let cur = this.start;
return {
next: () => cur < this.end ?
{ value: cur++, done: false } :
{ value: undefined, done: true }
};
}
}
for (const n of new Range(1, 4)) console.log(n); // 1 2 3追问:
Symbol.for('id')和Symbol('id')的区别?(前者全局注册可复用,后者每次新建)- Symbol 能被
JSON.stringify序列化吗?(不能,会被忽略)
Q12(加餐):迭代器和生成器是什么?
考察点:可迭代协议
标准答案:
- 可迭代对象(Iterable):实现了
[Symbol.iterator]()方法的对象 - 迭代器(Iterator):含
next()方法的对象,返回{value, done} - 生成器(Generator):用
function*+yield写迭代器的简便方式
js
function* gen() {
yield 1;
yield 2;
return 3;
}
const g = gen();
g.next(); // {value:1, done:false}
g.next(); // {value:2, done:false}
g.next(); // {value:3, done:true}
// 应用:实现无限序列
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
for (let i = 0; i < 5; i++) console.log(fib.next().value);
// 0 1 1 2 3追问:
- 生成器怎么实现 async/await?(generator + 自动执行器,babel 转译就这么做)
- 异步迭代器
for await...of用过吗?