Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

JavaScript 与 TypeScript / JavaScript & TypeScript

1. JavaScript 与 TypeScript 学习路线 / JavaScript and TypeScript Learning Path

2. JavaScript 核心语义:值、对象、函数与模块 / JavaScript Core Semantics: Values, Objects, Functions, and Modules

3. TypeScript 类型系统:从结构类型到类型编程 / The TypeScript Type System from Structural Typing to Type-Level Programming

4. JavaScript 异步编程与事件循环 / Asynchronous JavaScript and the Event Loop

本页目录

JavaScript 核心语义:值、对象、函数与模块 / JavaScript Core Semantics: Values, Objects, Functions, and Modules ​

📅 创建时间:2026-07-28 🏷️ 标签:#JavaScript #CoreSemantics #Runtime #Prototype #Closure #Proxy 📚 前置知识:[[00-overview]]


📋 本章目标 ​

  • 理解 JS 中值与引用的精确行为,掌握浅拷贝、深拷贝、structuredClone 的边界
  • 掌握 null/undefined 的语义差异,正确使用 ??、?.、默认值模式
  • 理解作用域链与闭包的底层机制,能运用闭包实现封装、模块模式与 once 函数
  • 掌握 this 的四种绑定规则,能诊断并修复常见的 this 丢失场景
  • 深入理解原型链:__proto__ 与 prototype 的关系、class 语法糖本质、继承模式
  • 掌握 Map / Set / WeakMap / WeakSet 的特性与适用场景
  • 理解 Proxy 与 Reflect 的拦截机制及其在响应式系统中的角色
  • 掌握迭代器协议、生成器函数、异步迭代的实际用法
  • 理解 ESM 模块系统的各种导入导出变体及其与 CJS 的互操作
  • 掌握 try/catch/finally、自定义错误类、AggregateError 与全局错误处理

第1部分:值与引用——JS 世界的"复制"真相 ​

1.1 基本类型与引用类型的分界线 ​

JS 的值世界有一条清晰的分界线:基本类型按值传递,对象按引用传递。

┌─────────────────────────────────────────────────────────────┐
│                    值的两大阵营                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  基本类型 (Primitives) — 7 种                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ string  number  bigint  boolean  symbol             │   │
│  │ undefined  null                                      │   │
│  │                                                      │   │
│  │ 特点:不可变、按值比较、赋值时复制                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  引用类型 (Reference Types)                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Object  Array  Function  Date  RegExp               │   │
│  │ Map  Set  WeakMap  WeakSet  Promise  Error          │   │
│  │                                                      │   │
│  │ 特点:可变、按引用比较、赋值时共享同一内存            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

基本类型赋值时,值本身被复制;对象赋值时,只有引用被复制。

ts
// 基本类型:各自独立
let a = 42;
let b = a;
b = 100;
console.log(a); // 42 — a 不受影响

// 引用类型:共享同一对象
const original = { count: 1 };
const alias = original;
alias.count += 1;
console.log(original.count); // 2 — 它们指向同一个对象
1
2
3
4
5
6
7
8
9
10
11

1.2 浅拷贝的陷阱 ​

展开语法 ... 和 Object.assign() 只做浅层复制——第一层属性是新的,但嵌套对象仍然是共享引用。

┌─────────────────────────────────────────────────────────────┐
│                    浅拷贝示意                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  source = { user: { name: 'Ada' }, score: 10 }             │
│                                                             │
│  ┌──────────────────┐     ┌─────────────┐                  │
│  │ source            │     │ { name }    │                  │
│  │  user  ──────────┼────→│             │                  │
│  │  score: 10       │     └─────────────┘                  │
│  └──────────────────┘                                       │
│           │                                                 │
│           │ 浅拷贝                                          │
│           ▼                                                 │
│  ┌──────────────────┐                                       │
│  │ copy              │                                      │
│  │  user  ──────────┼──→ 同一个 { name } 对象!            │
│  │  score: 10       │                                       │
│  └──────────────────┘                                       │
│                                                             │
│  copy.user.name = 'Grace' → source.user.name 也变成 Grace  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ts
const source = { user: { name: 'Ada' } };
const copy = { ...source };

copy.user.name = 'Grace';
console.log(source.user.name); // 'Grace' — 嵌套对象被共享

// Array 同理
const arr = [[1, 2], [3, 4]];
const shallowArr = [...arr];
shallowArr[0].push(99);
console.log(arr[0]); // [1, 2, 99]
1
2
3
4
5
6
7
8
9
10
11

1.3 深拷贝方案对比 ​

不可变更新必须复制所有被修改路径上的对象。

ts
// 方案1:手动逐层展开——适合已知结构的浅层对象
const deepCopy = {
  ...source,
  user: { ...source.user, name: 'Grace' }
};

// 方案2:JSON 序列化——仅限纯数据,丢失函数/Date/undefined/BigInt
const jsonCopy: typeof source = JSON.parse(JSON.stringify(source));
// ⚠️ Date → string, undefined → 消失, BigInt → TypeError

// 方案3:structuredClone——现代深拷贝标准方案
const cloned = structuredClone(source);
// ✅ 支持 Date, RegExp, Map, Set, ArrayBuffer, Blob, 循环引用
// ❌ 不支持 Function, Symbol, DOM 节点

// 方案4:Lodash.cloneDeep / Immer produce——复杂场景的工业选择
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────┐
│                    深拷贝方案对比                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  │ 方案            │ Date │ Fn │ BigInt │ 循环引用 │ 性能  │
│  ├────────────────┼──────┼────┼───────┼─────────┼──────┤
│  │ ... 展开       │ 保留 │保留│ 保留   │ ✗       │ 最快 │
│  │ JSON 序列化    │ 丢失 │丢失│ ✗     │ ✗       │ 中等 │
│  │ structuredClone│ 保留 │✗  │ ✅    │ ✅      │ 快   │
│  │ Lodash cloneDeep│ 保留│保留│ ✅    │ ✅      │ 较慢 │
│                                                             │
│  通用最佳实践:                                              │
│  • 纯数据对象 → structuredClone                             │
│  • 不可变状态树 → Immer produce                             │
│  • 已知浅结构 → 手动展开(最明确、最快)                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

第2部分:null、undefined 与空值世界 ​

2.1 两种"无"的语义 ​

┌─────────────────────────────────────────────────────────────┐
│                    null vs undefined                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  undefined — "不曾存在"                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 变量已声明但未赋值                                 │   │
│  │ • 函数没有 return 语句时的返回值                     │   │
│  │ • 访问对象上不存在的属性                             │   │
│  │ • 函数调用时未传入的参数                             │   │
│  │ • typeof undefined → "undefined"                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  null — "刻意空缺"                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ • 程序显式表达"此处没有对象"                         │   │
│  │ • 通常表示一个预期的空值                             │   │
│  │ • typeof null → "object" (历史遗留 bug)             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

2.2 ?? vs ||——空值合并的精髓 ​

这是 JS 中最容易被误解的操作符对之一。|| 判断的是"假值"(falsy),而 ?? 只判断 null 和 undefined。

ts
// || 的陷阱:0、空字符串、false 都被当作"假"
const count = 0;
const display = count || 10;     // 10 — 0 被错误地替换了!
const enabled = false;
const flag = enabled || true;    // true — false 被吞掉了

// ?? 才是真正的"空值"判断
const correct = count ?? 10;     // 0 — 0 是合法值,保留
const flag2 = enabled ?? true;   // false — false 是合法值,保留
const name = null ?? '匿名';     // '匿名' — null 被替换
1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────────────────────┐
│                    ?? vs || 决策表                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  值               || 'default'         ?? 'default'         │
│  ─────────────────────────────────────────────────────      │
│  null             'default'           'default'             │
│  undefined        'default'           'default'             │
│  false            'default'           false                 │
│  0                'default'           0                     │
│  ''               'default'           ''                    │
│  NaN              'default'           NaN                   │
│                                                             │
│  规则:|| 替换 8 个 falsy 值,?? 只替换 null/undefined      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

2.3 可选链 ?.——优雅的安全访问 ​

ts
// 之前的写法:层层检查,噪音巨大
const city = user && user.address && user.address.city;

// 可选链:短路求值,遇到 null/undefined 立即返回 undefined
const city = user?.address?.city;

// 方法调用
const result = api?.fetch?.('/data');

// 数组索引
const first = arr?.[0];

// 与 ?? 组合:安全访问 + 默认值
const displayName = user?.profile?.name ?? '匿名用户';
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意:?. 是短路操作符,不要滥用——如果 user 为 null 表示程序 bug,让它抛出错误比静默返回 undefined 更好。


第3部分:作用域与闭包 ​

3.1 三种作用域 ​

┌─────────────────────────────────────────────────────────────┐
│                    JS 作用域体系                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  全局作用域 (Global)                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ var 声明的全局变量挂在 window/globalThis 上          │   │
│  │ let/const 声明的全局变量不挂载                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  函数作用域 (Function)                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ var 声明的变量属于整个函数体                         │   │
│  │ 存在"变量提升" (hoisting)                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  块级作用域 (Block) — let / const                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ { }、if、for、while 内部的 let/const 绑定           │   │
│  │ 不存在提升(存在 TDZ — 暂时性死区)                  │   │
│  │ const 声明的是不可重新赋值的绑定,不是不可变值       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ts
// var 的函数作用域——经典陷阱
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 3, 3, 3
}
// 只有一个 i,所有回调共享它

// let 的块作用域——每次迭代创建新的绑定
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0); // 0, 1, 2
}

// TDZ 示例
console.log(x); // ReferenceError — 不是 undefined!
let x = 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

3.2 闭包的底层模型 ​

闭包 = 函数 + 其定义位置的词法环境引用。JS 引擎在执行函数时会创建词法环境(Lexical Environment),包含环境记录和对 [[OuterEnv]] 的引用。

┌─────────────────────────────────────────────────────────────┐
│                    闭包的词法环境链                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  function outer(prefix) {          ← outer 的词法环境       │
│    const secret = 42;              ← { prefix, secret }     │
│                                                             │
│    return function inner(msg) {    ← inner 的词法环境       │
│      return `${prefix}: ${msg}-${secret}`                   │
│    }                              ↑                        │
│  }                                │                        │
│                                   │                        │
│  const fn = outer('INFO')         │                        │
│  fn('start') // 'INFO: start-42' ─┘                        │
│                                                             │
│  inner.[[OuterEnv]] → outer 的词法环境                      │
│  inner 存活 → outer 的环境不能被 GC                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

3.3 闭包的实用模式 ​

ts
// 模式1:封装私有状态
function createCounter(initial = 0) {
  let value = initial;
  return {
    next: () => { value += 1; return value; },
    reset: () => { value = initial; return value; },
    get value() { return value; }, // 只读访问器
  };
}
const counter = createCounter(5);
counter.next(); // 6
counter.next(); // 7
// value 无法从外部直接访问——真正的私有

// 模式2:once 函数
function once<T extends (...args: any[]) => any>(fn: T): T {
  let called = false;
  let result: ReturnType<T>;
  return ((...args: any[]) => {
    if (!called) {
      called = true;
      result = fn(...args);
    }
    return result;
  }) as T;
}

const initApp = once(() => { console.log('初始化完成'); return 'ready'; });
initApp(); // 打印 '初始化完成',返回 'ready'
initApp(); // 什么都不做,直接返回 'ready'

// 模式3:模块模式(IIFE 闭包)
const UserModule = (() => {
  const users: string[] = [];
  return {
    add(name: string) { users.push(name); },
    list() { return [...users]; }, // 返回副本,保护内部数据
  };
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

3.4 闭包的内存考量 ​

闭包引用的是整个外层词法环境,不是它实际用到的变量。这在 V8 中可能导致不必要的大对象长期驻留。

ts
function outer() {
  const hugeData = new Array(10_000_000).fill('x'); // 大对象
  const small = 'used';

  return function inner() {
    // 只用了 small,但 hugeData 也可能无法被 GC
    return small;
  };
}
const fn = outer();
// hugeData 在某些引擎中可能仍然可达

// 最佳实践:不需要的变量手动置 null
function outerFixed() {
  let hugeData: string[] | null = new Array(10_000_000).fill('x');
  const small = 'used';
  hugeData = null; // 明确切断引用

  return function inner() {
    return small;
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

第4部分:函数调用与 this 绑定 ​

4.1 四种绑定规则 ​

┌─────────────────────────────────────────────────────────────┐
│                    this 绑定的四种规则                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 默认绑定 (Default)                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 独立函数调用 → 严格模式 undefined,非严格模式 window │   │
│  │ fn()  // this = undefined (strict)                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  2. 隐式绑定 (Implicit)                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 通过对象调用 → this 指向调用者                        │   │
│  │ obj.fn()  // this = obj                             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  3. 显式绑定 (Explicit)                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ call / apply / bind → 显式指定 this                  │   │
│  │ fn.call(ctx)  // this = ctx                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  4. new 绑定                                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ new Fn() → 创建新对象,this 指向新对象               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  优先级:new > 显式 > 隐式 > 默认                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

4.2 this 丢失的经典场景与修复 ​

ts
const user = {
  name: 'Ada',
  greet() { return `Hello, ${this.name}`; },
};

// 场景1:方法被提取后独立调用——丢失隐式绑定
const g = user.greet;
// g();  // TypeError: Cannot read properties of undefined

// 修复:bind 硬绑定
const boundGreet = user.greet.bind(user);
boundGreet(); // 'Hello, Ada'

// 场景2:回调函数中 this 丢失
class Toggle {
  constructor(public active = false) {}

  handleClick() {
    this.active = !this.active;
  }

  // ❌ 错误:事件回调中 this 指向 DOM 元素
  // button.addEventListener('click', this.handleClick);

  // ✅ 修复方案1:bind 构造函数
  // constructor() { this.handleClick = this.handleClick.bind(this); }

  // ✅ 修复方案2:箭头函数包装
  attach(el: HTMLElement) {
    el.addEventListener('click', () => this.handleClick());
  }
}

// 场景3:setTimeout 回调
const obj = {
  name: 'Timer',
  delayed() {
    // ❌ setTimeout(() => this.log(), 100); — 如果 log 未定义会出错
    // ✅ this 被箭头函数捕获
    setTimeout(() => { console.log(this.name); }, 100);
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

4.3 箭头函数与普通函数的 this 差异 ​

箭头函数没有自己的 this,它的 this 是词法解析时从外层作用域捕获的——this 的值在定义时就决定了,不受调用方式影响。

ts
const group = {
  name: 'Team A',
  members: ['Alice', 'Bob'],

  // 普通函数方法——this 由调用方式决定
  printMembers() {
    return this.members.map(function(m) {
      // ❌ this 指向 undefined(严格模式下的独立调用)
      return `${m} of ${this.name}`;
    });
  },

  // 箭头函数版——this 从 printMembersArrow 的词法作用域捕获
  printMembersArrow() {
    return this.members.map(m => `${m} of ${this.name}`);
  },
};

// group.printMembersArrow() → ['Alice of Team A', 'Bob of Team A']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

判断口诀:谁调用的不重要,看函数定义时的外层作用域的 this 是什么。如果函数不需要动态接收者,更清晰的做法是通过参数传入依赖。


第5部分:原型链深度剖析 ​

5.1 __proto__ 与 prototype——最容易混淆的两个属性 ​

┌─────────────────────────────────────────────────────────────┐
│                    __proto__ vs prototype                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  __proto__ ([[Prototype]]) — 每个对象都有                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 指向该对象的原型                                     │   │
│  │ obj.__proto__ → 属性查找时沿此链向上回溯             │   │
│  │ 规范名称:[[Prototype]],__proto__ 是历史访问器      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  prototype — 只有函数(含 class)有这个属性                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 当用 new 调用该函数时,新对象的 __proto__ 指向它     │   │
│  │ Fn.prototype → 新对象的原型                          │   │
│  │ 箭头函数没有 prototype!                              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  new Fn() 发生的事:                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. 创建空对象 obj                                    │   │
│  │ 2. obj.__proto__ = Fn.prototype                     │   │
│  │ 3. 以 obj 为 this 执行 Fn 构造函数                  │   │
│  │ 4. 返回 obj(除非构造函数显式返回对象)              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

5.2 属性查找与原型链 ​

┌─────────────────────────────────────────────────────────────┐
│                    原型链查找示意                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  const arr = [1, 2, 3]                                     │
│                                                             │
│  arr.push(4)  // 属性查找路径:                              │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ arr 自身有 push 吗? → 没有                           │   │
│  │   ↓ arr.__proto__ → Array.prototype                 │   │
│  │ Array.prototype 有 push 吗? → 有!调用它            │   │
│  │   ↓ 如果还没有 → Object.prototype                   │   │
│  │ Object.prototype 有吗? → 如果还没有 → null(终点)  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  arr.__proto__ === Array.prototype          // true         │
│  arr.__proto__.__proto__ === Object.prototype // true       │
│  arr.__proto__.__proto__.__proto__ === null   // true       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

5.3 class 是语法糖——验证 ​

ts
class Animal {
  constructor(public name: string) {}

  speak() { return `${this.name} makes a sound`; }
}

class Dog extends Animal {
  speak() { return `${this.name} barks`; }
}

// class 底层等价于:
function AnimalFn(this: any, name: string) {
  this.name = name;
}
AnimalFn.prototype.speak = function() {
  return `${this.name} makes a sound`;
};

function DogFn(this: any, name: string) {
  AnimalFn.call(this, name);
}
DogFn.prototype = Object.create(AnimalFn.prototype);
DogFn.prototype.constructor = DogFn;
DogFn.prototype.speak = function() {
  return `${this.name} barks`;
};

// 验证原型链
const dog = new Dog('Rex');
dog instanceof Dog;    // true
dog instanceof Animal; // true
dog instanceof Object; // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

5.4 Object.create()——无构造函数的原型继承 ​

ts
// 直接指定原型,不经过构造函数
const baseConfig = {
  timeout: 5000,
  retries: 3,
  log() { console.log(`timeout=${this.timeout}`); },
};

// devConfig.__proto__ = baseConfig
const devConfig = Object.create(baseConfig);
devConfig.timeout = 10000; // 在 devConfig 自身上创建属性

devConfig.log(); // 'timeout=10000' — 查找:自身有 timeout,不再沿链找
delete devConfig.timeout;
devConfig.log(); // 'timeout=5000' — 自身没有了,沿链找到 baseConfig 的

// 创建"纯字典"(无原型链,避免 __proto__ 键污染)
const dict = Object.create(null);
dict.__proto__ = 'evil';
console.log(dict.__proto__); // 'evil' — 被当作普通属性,不影响原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

5.5 instanceof 与属性遮蔽 ​

ts
// instanceof 检查构造函数的 prototype 是否在对象的原型链上
class A {}
class B extends A {}
const b = new B();

b instanceof B; // true — B.prototype 在 b 的原型链上
b instanceof A; // true — A.prototype 也在链上

// 属性遮蔽 (Property Shadowing)
const parent = { value: 'parent' };
const child = Object.create(parent);
child.value = 'child'; // 在 child 自身上创建 value,遮蔽了 parent 的

console.log(child.value);      // 'child' — 自身属性
console.log(parent.value);     // 'parent' — 未被改变
delete child.value;            // 移除遮蔽
console.log(child.value);      // 'parent' — 再次沿链查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

第6部分:Map、Set、WeakMap、WeakSet ​

6.1 四种集合类型对比 ​

┌─────────────────────────────────────────────────────────────┐
│                    集合类型全景对比                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  │ 特性         │ Map    │ Set    │ WeakMap  │ WeakSet    │
│  ├─────────────┼───────┼───────┼─────────┼───────────┤
│  │ 键类型       │ 任意  │ —     │ 仅对象   │ 仅对象     │
│  │ 迭代        │ ✅    │ ✅    │ ✗       │ ✗         │
│  │ size 属性    │ ✅    │ ✅    │ ✗       │ ✗         │
│  │ GC 行为      │ 阻止  │ 阻止   │ 不阻止   │ 不阻止    │
│  │ 键顺序       │ 插入  │ 插入   │ —       │ —         │
│  │ 典型场景     │ 缓存  │ 去重   │ 私有数据 │ 标记     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14

6.2 Map vs Object——何时用 Map ​

ts
// Map 的优势场景
// 1. 键可以是任意类型(对象、函数、NaN)
const cache = new Map<object, string>();
const key1 = { id: 1 };
cache.set(key1, 'value1');
cache.get(key1); // 'value1' — 对象作为键

// 2. 保持插入顺序,有 size 属性
const m = new Map([['a', 1], ['b', 2]]);
console.log(m.size); // 2

// 3. 遍历便捷
for (const [key, value] of m) { /* ... */ }
const keys = [...m.keys()];
const values = [...m.values()];

// 4. 高性能频繁增删——Map 对此优化,Object 更适合静态属性集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

6.3 WeakMap——私有数据与弱引用 ​

WeakMap 最关键的特性:键是弱引用,不阻止垃圾回收。键所指向的对象如果没有其他引用,即使它还是 WeakMap 的键,也会被 GC 回收。

┌─────────────────────────────────────────────────────────────┐
│                    WeakMap 弱引用示意                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  const wm = new WeakMap();                                 │
│  let obj = { data: 'important' };                          │
│  wm.set(obj, 'metadata');                                  │
│                                                             │
│  ┌─────────┐          ┌──────────────────┐                 │
│  │ obj     │ ───────→ │ { data: ... }    │                 │
│  │ (强引用) │          │                  │                 │
│  └─────────┘          └──────────────────┘                 │
│                              ↑                              │
│  ┌─────────────────┐        │                              │
│  │ wm              │ ───────┘ (弱引用)                     │
│  │ 不阻止 GC        │                                       │
│  └─────────────────┘                                       │
│                                                             │
│  obj = null;  // 唯一的强引用消失                            │
│  → GC 可以回收 { data: ... }                                │
│  → WeakMap 中的条目也随之消失                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ts
// 实战:用 WeakMap 存储私有数据
const _private = new WeakMap<object, { count: number }>();

class Counter {
  constructor() {
    _private.set(this, { count: 0 });
  }

  increment() {
    const p = _private.get(this)!;
    p.count += 1;
    return p.count;
  }
}

// 当 Counter 实例被 GC 时,_private 中的关联数据自动消失
// 没有内存泄漏风险

// 对比:如果用 Map,即使 Counter 实例已经无用了,
// 只要 Map 还活着,实例和私有数据都无法被回收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

6.4 Set 与 WeakSet ​

ts
// Set:去重、成员检查、集合运算
const tags = new Set<string>(['js', 'ts']);
tags.add('js');       // 已存在,忽略
tags.has('js');       // true — O(1)
tags.delete('js');
console.log(tags.size); // 1

// 数组去重的简洁方式(注意:仅基本类型可靠)
const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]

// WeakSet:弱引用对象集合,常用于标记
const visited = new WeakSet<object>();

function process(node: object) {
  if (visited.has(node)) return; // 防止循环处理
  visited.add(node);
  // ... 处理逻辑
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第7部分:Proxy 与 Reflect ​

7.1 Proxy——对对象操作的全面拦截 ​

Proxy 可以拦截对象上的 13 种内部方法,包括属性读取、赋值、删除、函数调用、new 操作等。

┌─────────────────────────────────────────────────────────────┐
│                    Proxy 拦截模型                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  操作 → Proxy (handler) → target                            │
│                                                             │
│  ┌──────────┐     ┌──────────────────┐     ┌──────────┐   │
│  │ 代码     │ ──→ │ Proxy 拦截层     │ ──→ │ 目标对象 │   │
│  │ proxy.x  │     │ get() 陷阱       │     │ target.x │   │
│  │ proxy.x=1│     │ set() 陷阱       │     │          │   │
│  │ x in proxy│    │ has() 陷阱       │     │          │   │
│  │ delete   │     │ deleteProperty()  │     │          │   │
│  │ proxy()  │     │ apply() 陷阱     │     │          │   │
│  │ new proxy│     │ construct() 陷阱  │     │          │   │
│  └──────────┘     └──────────────────┘     └──────────┘   │
│                                                             │
│  共 13 个陷阱,覆盖对象的所有内部操作                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

7.2 实战用例 ​

ts
// 用例1:验证代理
function createValidator<T extends object>(
  obj: T,
  rules: { [K in keyof T]?: (v: T[K]) => boolean }
): T {
  return new Proxy(obj, {
    set(target, prop, value) {
      const validator = (rules as any)[prop];
      if (validator && !validator(value)) {
        throw new TypeError(`Invalid value for ${String(prop)}: ${value}`);
      }
      return Reflect.set(target, prop, value);
    },
  }) as T;
}

const user = createValidator({ name: '', age: 0 }, {
  age: (v: number) => v > 0 && v < 150,
});
// user.age = -1; // TypeError

// 用例2:访问日志
function withLogging<T extends object>(obj: T, label: string): T {
  return new Proxy(obj, {
    get(target, prop) {
      const value = Reflect.get(target, prop);
      console.log(`[${label}] GET ${String(prop)} →`, value);
      return typeof value === 'function'
        ? value.bind(target) // 方法需要绑定正确的 this
        : value;
    },
    set(target, prop, value) {
      console.log(`[${label}] SET ${String(prop)} =`, value);
      return Reflect.set(target, prop, value);
    },
  }) as T;
}

// 用例3:默认值代理
function withDefault<T extends object>(obj: Partial<T>, defaults: T): T {
  return new Proxy(obj, {
    get(target, prop) {
      const value = Reflect.get(target, prop);
      return value !== undefined ? value : Reflect.get(defaults, prop);
    },
  }) as T;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

7.3 Reflect——Proxy 的最佳搭档 ​

Reflect 提供了与 Proxy 陷阱一一对应的静态方法,是执行默认操作的规范方式。

ts
// Proxy 陷阱与 Reflect 方法对照
// ┌──────────────────┬─────────────────────────┐
// │ Proxy 陷阱        │ Reflect 方法            │
// ├──────────────────┼─────────────────────────┤
// │ get              │ Reflect.get(target, prop, receiver)    │
// │ set              │ Reflect.set(target, prop, value, rcvr)  │
// │ has              │ Reflect.has(target, prop)              │
// │ deleteProperty   │ Reflect.deleteProperty(target, prop)   │
// │ apply            │ Reflect.apply(fn, thisArg, args)       │
// │ construct        │ Reflect.construct(ctor, args)          │
// │ ownKeys          │ Reflect.ownKeys(target)                │
// │ getPrototypeOf   │ Reflect.getPrototypeOf(target)         │

// Reflect 的优势:有返回值(成功/失败),不抛异常
try { delete obj.prop; } catch {}           // 旧方式
if (Reflect.deleteProperty(obj, 'prop')) {  // ✅ 返回 boolean
  // 删除成功
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

7.4 Vue 3 响应式概述 ​

Vue 3 的 reactive() 基于 Proxy 实现,追踪属性访问(get 中调用 track)和修改(set 中调用 trigger)。

┌─────────────────────────────────────────────────────────────┐
│                    Vue 3 响应式简化模型                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  reactive(obj) → new Proxy(obj, {                          │
│    get(target, key, receiver) {                            │
│      track(target, key)  // 记录:谁在依赖这个属性          │
│      return Reflect.get(target, key, receiver)             │
│    },                                                      │
│    set(target, key, value, receiver) {                     │
│      const old = target[key]                               │
│      const ok = Reflect.set(target, key, value, receiver)  │
│      if (old !== value) trigger(target, key) // 通知更新   │
│      return ok                                             │
│    }                                                       │
│  })                                                        │
│                                                             │
│  这就是为什么 Vue 3 能检测新增/删除属性                      │
│  (Vue 2 的 Object.defineProperty 做不到)                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

第8部分:迭代器与生成器 ​

8.1 迭代器协议 ​

┌─────────────────────────────────────────────────────────────┐
│                    迭代器协议                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Iterable(可迭代对象)                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 必须实现 [Symbol.iterator]() 方法                    │   │
│  │ 返回一个 Iterator(迭代器)                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Iterator(迭代器)                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 必须实现 next() 方法                                 │   │
│  │ 返回 { value: T, done: boolean }                    │   │
│  │ done 为 true 表示迭代结束                            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  消费方式:for...of / [...spread] / Array.from()           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ts
// 手写一个可迭代对象
const range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;

    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

for (const n of range) { console.log(n); } // 1 2 3 4 5
console.log([...range]);                    // [1, 2, 3, 4, 5]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

8.2 生成器函数 function* ​

生成器是创建迭代器的语法糖——调用生成器函数返回一个迭代器,yield 暂停执行,next() 恢复执行。

ts
// 生成器版本——更简洁
function* rangeGen(from: number, to: number) {
  for (let i = from; i <= to; i++) {
    yield i; // 暂停,返回 { value: i, done: false }
  }
  // 函数结束 → 返回 { value: undefined, done: true }
}

const r = rangeGen(1, 5);
r.next(); // { value: 1, done: false }
r.next(); // { value: 2, done: false }
// ...

// yield*:委托给另一个可迭代对象
function* flat<T>(arrays: T[][]) {
  for (const arr of arrays) {
    yield* arr; // 逐个产出 arr 的元素
  }
}
console.log([...flat([[1, 2], [3, 4]])]); // [1, 2, 3, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────┐
│                    生成器的双向通信                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  function* biDi() {                                        │
│    const x = yield '给外面的值';  // 暂停,向外发出          │
│    const y = yield x * 2;        // 暂停,用传入的值计算     │
│    return y;                                                 │
│  }                                                          │
│                                                             │
│  const gen = biDi();                                        │
│  gen.next()    // { value: '给外面的值', done: false }     │
│  gen.next(10)  // 将 10 赋给 x,执行 x * 2                 │
│                // → { value: 20, done: false }             │
│  gen.next(99)  // 将 99 赋给 y,函数结束                     │
│                // → { value: 99, done: true }              │
│                                                             │
│  关键点:next(arg) 的参数会成为上一个 yield 的返回值        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

8.3 异步迭代与 for await...of ​

ts
// 异步可迭代对象:Symbol.asyncIterator
async function* asyncRange(from: number, to: number) {
  for (let i = from; i <= to; i++) {
    await new Promise(r => setTimeout(r, 100)); // 模拟异步
    yield i;
  }
}

// 对异步可迭代对象使用解构 ❌ 不可以(需要 await)
// 使用 for await...of
for await (const n of asyncRange(1, 5)) {
  console.log(n); // 每隔 100ms 打印一次
}

// 异步生成器在处理分页 API、流式数据时非常有用
async function* paginatedFetch(url: string) {
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (data.items.length === 0) break;
    yield* data.items;
    page++;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

第9部分:模块系统 ​

9.1 ESM 导出/导入变体全集 ​

┌─────────────────────────────────────────────────────────────┐
│                    ESM 语法全览                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  导出 (export)                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ // 命名导出                                          │   │
│  │ export const x = 1;                                 │   │
│  │ export function fn() {}                             │   │
│  │ export class C {}                                   │   │
│  │ export { a, b as alias };  // 集中导出+重命名       │   │
│  │                                                     │   │
│  │ // 默认导出(一个模块只能有一个)                    │   │
│  │ export default function() {}                        │   │
│  │ export { fn as default };                           │   │
│  │                                                     │   │
│  │ // 重导出(聚合模块)                                │   │
│  │ export { foo } from './other.js';                  │   │
│  │ export { default as Foo } from './other.js';       │   │
│  │ export * from './other.js';  // 所有命名导出       │   │
│  │ export * as Lib from './lib.js'; // 命名空间       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  导入 (import)                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ import defaultExport from './mod.js';               │   │
│  │ import { a, b as alias } from './mod.js';           │   │
│  │ import defaultExport, { a } from './mod.js';        │   │
│  │ import * as Lib from './mod.js';  // 命名空间       │   │
│  │ import './side-effects.js';  // 仅执行副作用        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

9.2 动态导入与 import.meta ​

ts
// 动态 import() — 返回 Promise,支持按需加载
async function loadLocale(lang: string) {
  const locale = await import(`./locales/${lang}.js`);
  return locale.default;
}

// import.meta — 模块元信息
console.log(import.meta.url);  // file:///path/to/module.js
console.log(import.meta.dirname); // Node.js 22+ 支持

// 条件加载
if (process.env.NODE_ENV === 'development') {
  const { devTools } = await import('./devtools.js');
  devTools.enable();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

9.3 ESM 与 CJS 互操作 ​

┌─────────────────────────────────────────────────────────────┐
│                    ESM ↔ CJS 互操作                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  CJS 模块                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ // math.cjs                                          │   │
│  │ module.exports = { add: (a,b) => a + b };           │   │
│  │ // 或 exports.add = (a, b) => a + b;                │   │
│  │                                                     │   │
│  │ // 使用                                              │   │
│  │ const { add } = require('./math.cjs');              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ESM 导入 CJS (Node.js 支持)                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ import math from './math.cjs';                      │   │
│  │ // 或 import { add } from './math.cjs';             │   │
│  │ // 或 import * as math from './math.cjs';           │   │
│  │ // Node.js 会包装 CJS 的 exports 作为默认导出       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  CJS 导入 ESM (有限制)                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ const mod = await import('./module.mjs');           │   │
│  │ // CJS 中不能同步 require ESM 模块                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  package.json 中的 type 字段:                              │
│  • "type": "module"  → .js 文件被解析为 ESM               │
│  • "type": "commonjs" 或无 → .js 被解析为 CJS              │
│  • .mjs 强制 ESM,.cjs 强制 CJS,不受 type 影响            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

第10部分:错误处理 ​

10.1 try/catch/finally 的完整语义 ​

ts
// finally 总是在控制流离开 try/catch 之前执行
function demo() {
  try {
    console.log('1. try');
    return 'from try';
  } catch (e) {
    console.log('2. catch');
    return 'from catch';
  } finally {
    console.log('3. finally — always runs');
    // ⚠️ finally 中的 return 会覆盖 try/catch 的 return!
  }
}
console.log(demo());
// 输出:1. try → 3. finally — always runs → 'from try'

// catch 可以省略绑定变量(ES2019+)
try {
  riskyOperation();
} catch { // 不需要 error 对象
  fallback();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

10.2 自定义错误类 ​

ts
// 自定义错误——包含业务上下文
class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number = 500,
    options?: { cause?: unknown }
  ) {
    super(message, options); // Error 构造函数支持 cause 选项
    this.name = 'AppError';
    // 确保 instanceof 正确工作(TS 目标 < ES2015 时需要)
    Object.setPrototypeOf(this, AppError.prototype);
  }
}

class ValidationError extends AppError {
  constructor(
    message: string,
    public readonly fields: Record<string, string>
  ) {
    super(message, 'VALIDATION_ERROR', 400);
    this.name = 'ValidationError';
  }
}

// 使用 error.cause 保留原始错误(ES2022+)
try {
  await fetch('/api/data');
} catch (err) {
  throw new AppError('Failed to fetch data', 'FETCH_ERROR', 502, {
    cause: err, // 保留底层错误
  });
}
// 上层可以访问 error.cause 进行根因分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

10.3 AggregateError——聚合多个错误 ​

ts
// 并行操作中同时发生了多个错误
const errors: Error[] = [];

try { await validateName(input.name); } catch (e) { errors.push(e as Error); }
try { await validateEmail(input.email); } catch (e) { errors.push(e as Error); }
try { await validateAge(input.age); } catch (e) { errors.push(e as Error); }

if (errors.length > 0) {
  throw new AggregateError(errors, 'Validation failed');
}

// 捕获后逐个检查
try {
  await validateForm(input);
} catch (e) {
  if (e instanceof AggregateError) {
    for (const err of e.errors) {
      console.error(err.message);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

10.4 全局错误处理 ​

┌─────────────────────────────────────────────────────────────┐
│                    全局错误处理层次                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  浏览器端                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ window.onerror — 未捕获的同步错误                    │   │
│  │ window.onunhandledrejection — 未处理的 Promise 拒绝  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Node.js 端                                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ process.on('uncaughtException')                     │   │
│  │ process.on('unhandledRejection')                    │   │
│  │ 注意:uncaughtException 后应优雅退出,状态已不可靠   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  框架级(Express 示例)                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ app.use((err, req, res, next) => {                  │   │
│  │   // 4 参数中间件自动成为错误处理中间件               │   │
│  │ });                                                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ts
// 业务层的错误处理原则
// 1. Result 类型——可预期的失败不是异常
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function safeParseJSON<T>(text: string): Promise<Result<T>> {
  try {
    return { ok: true, value: JSON.parse(text) };
  } catch (error) {
    return { ok: false, error: error as Error };
  }
}

const result = await safeParseJSON<User>('{"name":"Ada"}');
if (result.ok) {
  console.log(result.value.name); // 类型安全
} else {
  console.error(result.error.message);
}

// 2. 错误应传播到能够补充上下文或执行恢复的层次
// 3. 永远不要空 catch——吞掉错误让系统变得不可观测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

核心总结 ​

总结1:JS 运行时的五根支柱 ​

┌─────────────────────────────────────────────────────────────┐
│                    JS 核心语义五根支柱                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 值与引用 — 基本类型复制,对象共享;浅拷贝不穿透嵌套     │
│  2. 作用域与闭包 — let/const 块作用域,闭包 = 函数+词法环境│
│  3. this 绑定 — 四规则(new > 显式 > 隐式 > 默认),箭头    │
│     函数无自己的 this                                       │
│  4. 原型链 — __proto__ 查找,prototype 构造,class 是语法糖 │
│  5. 模块与错误 — ESM 静态结构,错误应在能处理的层面捕获     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12

总结2:数据结构选型速查 ​

需求使用不使用
键值对,键是对象或需要顺序/大小MapObject
唯一值集合SetArray (手动去重)
对象私有数据(无泄漏)WeakMapMap
已处理对象标记WeakSetSet
拦截对象操作ProxyObject.defineProperty
纯字典(无原型)Object.create(null){}

总结3:工程心态 ​

JavaScript 的工程能力来自对值、作用域、调用、原型、模块和异步的准确预测。框架会变化,但这些运行时语义长期稳定。能把 this 丢失场景一眼看穿、知道闭包何时引起内存驻留、能用 Proxy 和 WeakMap 设计出零泄漏的私有数据方案——这些才是区分 JS 使用者与 JS 工程师的真正标志。


章节测试 ​

测试1:值与引用 ​

以下代码的输出是什么?

ts
const a = { x: { y: 1 } };
const b = { ...a };
b.x.y = 2;
console.log(a.x.y);
1
2
3
4

A. 1 B. 2 C. undefined D. 抛出错误

测试2:空值合并 ​

以下哪个表达式返回 0? A. 0 || 10 B. 0 ?? 10 C. null ?? 10 D. undefined ?? 10

测试3:闭包 ​

一段代码执行后,闭包引用的外层变量何时可以被垃圾回收?

测试4:this 绑定 ​

ts
const obj = {
  value: 100,
  getValue: () => this.value,
};
const { getValue } = obj;
1
2
3
4
5

getValue() 的 this 指向什么?为什么?

测试5:原型链 ​

Object.create(null) 创建的对象能否使用 hasOwnProperty?为什么?

测试6:Proxy ​

Proxy 的 handler 中,get 陷阱的三个参数分别是什么?receiver 参数在什么场景下不可或缺?

测试7:WeakMap 与 Map ​

为什么 WeakMap 不提供 size 属性和迭代方法?


参考答案 ​

测试1答案 ​

答案:B (2)

展开语法只做浅拷贝。b.x 和 a.x 指向同一个 { y: 1 } 对象,修改 b.x.y 会影响 a.x.y。需要深拷贝请使用 structuredClone(a)。


测试2答案 ​

答案:B (0 ?? 10)

?? 只在左侧为 null 或 undefined 时使用右侧值,0 是合法值,不会被替换。|| 会把所有 falsy 值(包括 0)替换掉。


测试3答案 ​

当没有任何闭包引用该变量所在的词法环境,且该环境不可从任何可达对象的引用链到达时,可以被 GC 回收。具体而言:所有引用该闭包的函数都已不可达,且没有循环引用导致整个作用域链被保留。在现代引擎中,如果一个闭包只捕获了环境中的部分变量,未捕获的变量通常可以被提前回收——但最安全的做法是手动将不再需要的大对象引用置为 null。


测试4答案 ​

getValue() 中的 this 指向全局对象(浏览器中是 window,严格模式下是 undefined),而不是 obj。

原因:箭头函数没有自己的 this,它从定义位置的词法作用域捕获 this。obj 对象字面量不创建新的作用域,所以箭头函数捕获的是模块顶层或全局作用域的 this,不是 obj。


测试5答案 ​

不能。Object.create(null) 创建的对象没有原型链(__proto__ 为 null),因此无法从 Object.prototype 继承 hasOwnProperty 方法。这在创建"纯字典"(不需要原型属性和方法)时是有意为之的行为。如果需要对这样的对象使用 hasOwnProperty,可以通过 Object.prototype.hasOwnProperty.call(obj, key) 或使用 Object.hasOwn(obj, key)(ES2022+)。


测试6答案 ​

get 陷阱的三个参数:

  1. target — 被代理的目标对象
  2. property — 被访问的属性名(string 或 symbol)
  3. receiver — 最初被调用的对象(通常是 proxy 自身)

receiver 在原型链继承场景下不可或缺:当通过子对象访问从原型继承的属性时,如果该属性是 getter 且 getter 内部使用了 this,正确的 receiver 能确保 this 指向子对象而非原型对象。Proxy 实现响应式系统时,正确传递 receiver 给 Reflect.get(target, prop, receiver) 是追踪响应式依赖的前提。


测试7答案 ​

WeakMap 不提供 size 和迭代方法,因为其键是弱引用——键所指向的对象随时可能被垃圾回收,且 GC 的发生时机是不可预测的。如果提供了 size,它的值在不同时刻可能不同(不确定的行为)。如果提供了迭代方法,迭代过程中键可能被 GC,导致不可预期的结果。这种设计确保了 WeakMap 的弱引用语义不会产生不确定的行为。


相关笔记 ​

  • [[00-overview]] - JS/TS 学习路线全景图
  • [[02-typescript-type-system]] - TS 类型系统从基础到类型体操
  • [[03-async-and-event-loop]] - 异步编程与事件循环深度图解

下一步学习 ​

  • [ ] 动手:写一个基于闭包的简易状态管理器(不含任何框架)
  • [ ] 动手:用 WeakMap 实现一个零泄漏的事件发射器
  • [ ] 动手:用 Proxy 实现一个支持嵌套对象和数组的响应式 reactive 函数
  • [ ] 动手:用生成器实现一个可暂停/恢复的任务调度器
  • [ ] 阅读 TypeScript 类型系统 — 在理解 JS 运行时后,建立编译期类型约束

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇1. JavaScript 与 TypeScript 学习路线 / JavaScript and TypeScript Learning Path
下一篇3. TypeScript 类型系统:从结构类型到类型编程 / The TypeScript Type System from Structural Typing to Type-Level Programming

持续记录,持续成长

Copyright © Tidenflow