跳到正文
前端知识库
ES6

ES6 面试真题补充:十题边界索引

根据 ES6 面试真题把十个主题的易错边界并入现有专题,集中索引 TDZ、可迭代展开、Promise、Module、Generator、装饰器、WeakMap 和 Proxy 追问。

7 分钟ES6 · Promise · Module · Generator · Decorator · Proxy · 面试

ES6 面试真题补充:十题边界索引

本文根据编号 2《ES6 面试真题》整理。项目已有 0110 专题文章分别覆盖完整 API;这里不重复抄录正文,只把真题中容易漏答的边界集中列出,并链接到对应章节。

1. 变量声明:TDZ 不只是“未赋值”

回答时应补充三点:

  • let/const 在声明到初始化之间处于暂时性死区;同名内层绑定会在整个块中遮蔽外层变量。
  • 经典脚本顶层 var 通常成为 window 属性,let/const 不会;ES module 顶层绑定也不挂到 window
  • const 只禁止绑定重新指向,不能阻止对象或数组内部变更;声明必须同时初始化。
const settings = {}
settings.debug = true // 合法
// settings = {}     // TypeError

循环异步回调使用 let 时,每轮迭代有独立绑定;使用 var 则通常共享一个函数级绑定。这是资料中最常见的输出题变体。(深入阅读:变量提升与暂时性死区const 的不可重新赋值块级作用域

2. 数组扩展:先判断可迭代性

数组展开、参数展开和 for...of 都要求 Symbol.iterator;对象展开是另一套“自有可枚举属性”规则:

const map = new Map([[1, 'one']])
const entries = [...map] // [[1, 'one']]
// [...{ a: 1 }]         // TypeError: object is not iterable
({ ...{ a: 1 } })        // { a: 1 }

Array.from 还能处理带 length 的类数组对象并接受映射函数;Array.of(3)[3],而 Array(3) 是三个空位。includes 能匹配 NaNflat() 默认只展开一层,sort 排数字必须提供比较函数。展开和对象扩展都只做浅拷贝。(深入阅读:扩展运算符与字符串/类数组的转换

3. 函数扩展:默认参数的作用域

默认参数按调用惰性求值,省略参数或传入 undefined 才使用默认值,null 不会触发默认值:

function pair(x = 1, y = x) {
  return [x, y]
}

pair()          // [1, 1]
pair(undefined) // [1, 1]
pair(null)      // [null, null]

参数默认值有自己的参数环境,可以引用前面的参数,但不能引用尚未初始化的后续参数。出现默认参数或剩余参数后,函数的 length 在第一个默认参数处停止计数;这类非简单参数列表的函数体不能再显式写 'use strict'

箭头函数捕获词法 this,没有自己的 argumentsprototype,也不能用 new 调用。需要动态 this 的对象方法或构造函数应使用普通函数。(深入阅读:参数作用域箭头函数的 this 绑定

4. 对象扩展:键、super 与浅合并

计算属性键最终会被转换为字符串或 Symbol;两个普通对象作为键时可能都变成 "[object Object]",需要对象身份作为键时应使用 Map

super 只在方法定义的语境中有特殊含义,表示当前方法所属对象的原型。对象展开和 Object.assign 都是浅合并,但都不会复制原型、不可枚举属性或完整属性描述符;Object.assign 还可能触发目标对象已有 setter。需要保留访问器和描述符时使用 Object.getOwnPropertyDescriptors 配合 Object.defineProperties

Object.is=== 的两个面试差异是:Object.is(NaN, NaN)true,并且能区分 +0-0。(深入阅读:super 关键字对象浅拷贝

5. Promise:执行时机、链式返回与取消

new Promise(executor) 时 executor 立即同步执行;.then.catch.finally 的回调在微任务中执行。每次 .then 都会产生新 Promise,回调的返回值或抛出的异常决定下一环节。

  • then 中忘记 return,下一环节会收到 undefined
  • catch 只能捕获它之前链路中的拒绝或同步抛错。
  • finally 默认透传原值或原错误,除非它自己抛错或返回拒绝 Promise。
  • Promise.all 按输入顺序返回结果,任一项拒绝就立即拒绝,但不会自动取消其他底层请求。
  • Promise.allSettled 等待全部结束,适合批量结果汇总。
  • Promise.resolve(thenable) 会吸收 thenable 的状态,而不是简单包一层对象。

需要取消请求时,应把 AbortController 或库提供的取消机制接入底层任务,不能把 Promise.all 的拒绝误认为已经停止其他请求。(深入阅读:Promise 的执行时机

6. Module:实时绑定与动态导入

ES module 的静态导入可被构建工具分析,导出绑定是实时只读引用,模块通常只求值一次:

// counter.js
export let count = 0
export function increment() { count += 1 }

// main.js
import { count, increment } from './counter.js'
increment()
console.log(count) // 1

导入方不能直接给 count 重新赋值;export default 每个模块最多一个,命名导出可以有多个。import() 是表达式,返回 Promise,适合按路由或功能拆分代码。循环依赖需要注意“绑定存在但尚未初始化”的阶段,不能只靠调整文件顺序解决。

与 CommonJS 对比时,至少说清静态 import 与运行时 require、实时只读绑定与 module.exports 运行时值、以及构建工具 tree shaking 的差异。(深入阅读:动态加载 import()循环引用的处理机制

7. Generator:第一次 next 的参数会被忽略

Generator 调用后返回同时实现 nextSymbol.iterator 的迭代器,不会立即执行。第一次 next(value) 没有暂停的 yield 可以接收参数,传入值会被忽略;之后的 next(value) 才会把值送回上一次暂停处:

function* exchange() {
  const received = yield 'ready'
  return received
}

const iterator = exchange()
iterator.next('ignored') // { value: 'ready', done: false }
iterator.next('ok')      // { value: 'ok', done: true }

for...of 只消费 done: false 的值,不会遍历最终 return 值;yield* 可以委托给其他可迭代对象。Generator 可以配合执行器编排 Promise,但 async/await 已经封装了这类调度。(深入阅读:yield 接收 next 传入的参数

8. Decorator:先交代提案和编译环境

资料中的 @decorator 代码通常依赖 TypeScript 或 Babel,不能假设所有浏览器都能直接运行。回答时要区分:

  • 装饰器工厂的求值与装饰器实际应用的时机。
  • 类装饰器能否返回替代类,方法装饰器如何包装原方法。
  • 旧提案与新提案的参数形状和初始化时机不同。
function readonly(target, name, descriptor) {
  descriptor.writable = false
  return descriptor
}

先说明项目的编译配置,再谈日志、鉴权、依赖注入等 AOP 场景,避免把某个插件的旧语义说成语言普遍行为。(深入阅读:装饰器的编译原理

9. WeakSet、WeakMap:弱引用意味着不可枚举

WeakSet 只能存对象,WeakMap 的键也只能是对象;原始值会抛出 TypeError。两者没有 sizekeysforEach 等遍历 API,这是为了不暴露垃圾回收时机:

const metadata = new WeakMap()
const node = {}
metadata.set(node, { mountedAt: Date.now() })
metadata.get(node)

对象不再被其他强引用持有时,条目可以被回收,但代码不能依赖具体回收时刻。它们适合 DOM 元数据、实例私有数据和对象缓存,不适合需要统计数量或遍历全部键的业务集合。(深入阅读:WeakMap 的内存管理原理

10. Proxy:Reflect 转发与不变量

Proxy 的 trap 拦截 getsethasdeletePropertyownKeys 等基本操作。用 Reflect 转发并传入 receiver,可以保留 getter/setter 的正确接收者:

const target = { count: 0 }
const proxy = new Proxy(target, {
  get(object, key, receiver) {
    return Reflect.get(object, key, receiver)
  },
  set(object, key, value, receiver) {
    if (key === 'count' && !Number.isInteger(value)) return false
    return Reflect.set(object, key, value, receiver)
  },
})

set 等 trap 的返回值必须符合布尔语义;严格模式下返回 false 会抛错。不可配置属性、不可写数据属性等会触发 Proxy 不变量约束,不能随意伪造结果。代理对象与原对象身份不同,作为 Map 键时是两个键;私有字段和某些内建对象的 brand check 也可能因为 this 是代理而失败。需要撤销权限时可使用 Proxy.revocable。(深入阅读:Reflect 对象

真题复习路径

建议先读对应专题的完整原理,再用本页做口述检查:

  1. 变量和函数:TDZ、参数作用域、箭头函数的 this
  2. 数据结构和对象:可迭代协议、浅拷贝、属性描述符。
  3. 异步和模块:Promise 微任务、错误传播、实时绑定和循环依赖。
  4. 元编程:Decorator 的编译边界、WeakMap 的弱引用、Proxy 的不变量。

这样能把 PDF 的十个问题接回项目现有文章,避免同一 API 在多个页面出现互相矛盾的版本。