JAVASCRIPT与TYPESCRIPT · KNOWLEDGE CHAPTER
K07

值、对象与集合

JavaScript 对象应按“身份、属性与协议”理解。身份回答两个值是否指向同一实体;属性描述键来自哪里、是否可枚举、读取是否触发代码;协议决定复制、遍历、排序和代理究竟执行哪些操作。不要把对象想成无副作用的 JSON,也不要把数组 length 当成实际元素数量。

JavaScript与TypeScript2 道章节练习8 道主练关联
先修知识:无

核心模型

JavaScript 对象应按“身份、属性与协议”理解。身份回答两个值是否指向同一实体;属性描述键来自哪里、是否可枚举、读取是否触发代码;协议决定复制、遍历、排序和代理究竟执行哪些操作。不要把对象想成无副作用的 JSON,也不要把数组 length 当成实际元素数量。

原理与机制

先定义相等关系,再选择比较工具

=== 不做类型转换,== 按转换规则比较;边界输入宜明确转换。Object.is 把 NaN 视为相同且区分正负零,=== 在这两点上相反。Set、Map 键和 includes 使用 SameValueZero,识别 NaN 且不区分正负零,indexOf 则按严格相等。对象按身份比较,业务实体可按稳定 id;深比较需约定原型、循环、Symbol、Map 等。JSON 字符串不是通用判等:缺失与 undefined 可能合并,键插入顺序又可能让相近内容序列化不同。

属性读取会穿越原型,也可能执行函数

属性先查自身再沿内部原型查找,实例赋同名普通属性通常遮蔽原型,而修改继承数组会影响所有共享者。构造器 prototype 与实例内部原型不同;class 的实例方法通常在原型,字段属于实例,静态成员属于类。Object.keys 取自有可枚举字符串键,for...in 还取继承可枚举字符串键,Reflect.ownKeys 取全部自有键含 Symbol。defineProperty 新属性省略的 writable、enumerable、configurable 默认 false;判断来源用 Object.hasOwn,不能信任对象自带方法。

复制与合并要说明信息损失及写入方式

展开与 Object.assign 复制可枚举自有值,包括 Symbol,都是浅复制且可能读取 getter;assign 还可触发目标 setter,失败时已有写入不回滚。structuredClone 支持多种结构化数据与循环,却不完整保留类原型、描述符、函数或 DOM;transfer 会让源资源失去使用权。JSON.parse 本身不会因 __proto__ 直接污染全局,危险常出现在后续赋值或递归路径合并。外部配置用 schema、允许字段、显式安全默认值;Map 或无原型字典只能保护相应容器。

Proxy 拦截操作,Reflect 保留默认语义

get/set trap 是基本操作入口,Reflect.get(t,k,receiver) 让访问器继续以代理作为 this,因此 getter 内部读取仍可被观测;直接 t[k] 会改变接收者。set 应返回布尔结果,严格模式下失败可能抛 TypeError,还必须遵守不可配置、不可写属性等不变量。代理并不透明:Map 内部槽与 #私有字段可能拒绝代理接收者;改用 target 可修品牌检查,却绕过内部观测。只校验 set 也挡不住 defineProperty、delete 或直接修改暴露 target。

数组方法遵循各自的索引和比较规则

空槽是缺少自有索引,显式 undefined 是索引存在;length 包含空槽。map/forEach 先检查索引及原型链,普通空槽会跳过;迭代器直接读取,展开通常把空槽变成 undefined,includes 也把普通空槽当 undefined,indexOf 通常跳过。delete 留空槽,splice 移位。sort 原地修改,比较器需负数、零、正数并满足一致关系,布尔返回不够;默认按字符串码元排序。toSorted 返回浅新数组,稳定排序保留同键输入顺序,但规范不承诺统一算法和复杂度。

最小示例

javascript
javascript
const a = [ , undefined, NaN ];
const b = [...a];
console.log(Object.hasOwn(a, 0), Object.hasOwn(b, 0));
console.log(a.includes(undefined), a.indexOf(NaN));
const source = { nested: { n: 1 } };
const copy = { ...source };
copy.nested.n = 2;
console.log(source.nested.n, source === copy);
const target = { n: 3, get doubled() { return this.n * 2; } };
const seen = [];
const proxy = new Proxy(target, { get(t, k, r) {
  seen.push(k); return Reflect.get(t, k, r);
}});
console.log(proxy.doubled, seen.join(','));

预期依次得到 false true、true -1、2 false、6 doubled,n。第一组区分缺失和显式值;第二组说明比较方法并非统一;第三组表明新外壳仍共享内部对象;最后一组说明保留 receiver 后 getter 的内部属性访问再次进入代理。教学日志是有限数组,生产观测不能无界累积。

环境:现代 Node.js;可保存为 .cjs 后执行。

核验:未执行教学示例;说明中的结果为机制推演,不是本轮测试通过记录。

边界与取舍

边界

  • Object.assign 已修改的目标不会因后续 getter 抛错回滚;新对象展开失败也不代表 getter 副作用被撤销。
  • map 并非绝对跳过所有“空槽位置”,原型链同名索引会参与;练习不要污染全局 Array.prototype。
  • toSorted 和对象展开都不是深拷贝;代理字段校验也不等于防篡改或鉴权。

取舍

  • 按 id 比较通常更稳定便宜;深比较可验证快照但需明确类型范围和循环策略。
  • 不可变更新便于追踪变化,却有复制成本;结构化克隆适合数据快照,类和资源宜显式重建或转移。

口述示范

我先区分对象身份与内容,再说明属性是否自有、可枚举,以及读取是否有副作用。复制、代理和数组方法各走不同协议;测试必须覆盖原型、getter、空槽、NaN 和非法排序值,而不是只验证普通 JSON。

章节练习

练习 1

假设 TranslateFlow 从外部 JSON 合并配置,怎样避免继承权限或危险路径写入?

展开检查点
  • 明确 schema 与允许字段,安全默认值显式设置,自有字段用 Object.hasOwn。
  • 区分 JSON 解析、assign 目标 setter 与递归合并;无原型字典不是全局防护。
练习 2

列表快照改名影响原对象,排序又异常,如何构造最小证据?

展开检查点
  • 证明浅拷贝嵌套共享;比较结构化克隆支持范围与资源转移。
  • 检查空槽、NaN、比较器负零正返回、原数组修改及稳定 tie-breaker。

参考来源与核验边界

核验日期 2026-10-04。ECMAScript 在线文档标题显示下一版编辑草案,不把草案标题当成正式发布版本;本章讨论既有机制。toSorted 属 ES2023 方法,旧环境需检查支持。 本章新增示例未在本轮执行,不新增测试通过声明。

内容版本:2026-10-04.2 · 题目和来源 ID 保持原样,本站不将面经标签解释为企业官方出题或高频保证。