Skip to content

第 06 章 · 面试题集(ES6+ 新特性)


Q1:let、const、var 的区别?

考察点:作用域、变量提升、TDZ

标准答案

维度varletconst
作用域函数块级块级
变量提升✅(值 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 上的方法是可枚举的)
  • 支持 staticget/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 的区别?

考察点:模块化系统

标准答案

维度ESMCommonJS
语法import / exportrequire / module.exports
加载时机编译时(静态)运行时(动态)
输出引用(live binding)拷贝
异步❌(同步)
Tree-Shaking
浏览器原生❌(需打包)
顶层 thisundefinedmodule.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;?? 只对 nullundefined 生效
  • 给"零是合法值"的场景救命
js
const port = config.port ?? 3000;   // port=0 时不会被覆盖
const max = options.max || 10;      // ❌ max=0 时变成 10

追问

  • ?? 不能直接和 ||&& 混用?(语法报错,需加括号)
  • 可选链能用在赋值左边吗?(不能)

Q9:Map 和 Object 的区别?什么时候用 Map?

考察点:数据结构

标准答案

维度MapObject
键类型任意(包括对象、函数)字符串 / Symbol
顺序严格按插入顺序整数键先排序
大小map.sizeObject.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 都独一无二。

应用场景

  1. 唯一常量 / 状态枚举STATUS.PENDING = Symbol('pending')
  2. 对象唯一键:避免覆盖
  3. Well-known Symbols:让对象具备特殊行为,如 Symbol.iteratorSymbol.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 用过吗?