编程语言研究书库

07 对象与属性:藏在每个属性背后的描述符

07 对象与属性:藏在每个属性背后的描述符

这一章讲什么

JavaScript 里几乎所有东西都是对象:你的 React 组件、你操作的 DOM、你的 Express 服务,连数组和函数都是对象的一种1。而对象在规范里的定义朴素得惊人: 一组「名字-值」对2。朴素定义底下却埋着一整套隐形机关—— 每个属性除了值,还配有一份「描述符」,三个属性决定它能不能改、 循环里看不看得见、删不删得掉。这章把这四个怪现象讲透: 赋值不报错也不生效;属性在 for...in 里凭空消失;delete 返回 false; 还有 Node.js 新手必踩的 process.env.FOO = { a: 1 } 变出 '[object Object]'。

顶层全景:一个属性的完整档案

  你写的:        drawer.contents = 'tax documents'
                     │
  引擎看到的:     属性名 'contents'
                     │
                     ├─ 是哪种属性?
                     │    ├─ 数据属性 → 档案里存 [[Value]] 和 [[Writable]]
                     │    └─ 访问器属性 → 档案里存 [[Get]] 和 [[Set]](读写都执行函数)
                     │
                     └─ 三个通用属性(取值都是 true/false):
                          [[Enumerable]]   为 false → for...in / Object.keys() 里隐身
                          [[Configurable]] 为 false → 删不掉、描述符本身也锁死
                          (外加数据属性专属的 [[Writable]] 为 false → 值改不动)

  图说:你赋值的对象不是一个「槽位」,是一整套带属性的档案;
        三个属性平时都为 true,所以无感;一旦有人(或宿主)动过属性,怪事就来了。

核心原理

3.1 对象:运行时才能定形状的动态映射

和 Java、C++ 对比着看最清楚。在那些静态语言里,对象是编译期就定死的结构: 你在类里声明字段,编译器记住每个字段在内存里的偏移,之后每个实例都按这张图 排布;想加字段?改类定义,重新编译3。

JavaScript 反过来:对象是动态的映射(dynamic map),运行中随时可以加字段—— filingCabinet.contents = 'Tax Documents' 一句话,字段就有了4。 动态性带来静态语言做不到的模式:按运行时条件逐步组装对象、从多个来源混入功能、 甚至给第三方库的对象打补丁5。

代价同样在 3.1 节埋下:C++ 读一个字段是「跳到已知偏移,读字节」; JavaScript 是「在这个动态集合里搜索」。引擎事先不知道对象长什么样, 知道了也可能随时变——不理解这个搜索过程和它的内部结构, 就是 JavaScript 性能问题的主要来源6。搜索怎么加速?那就是 08 章的原型链 和 10 章的隐藏类与内联缓存。

3.2 两种属性:存值的,和跑代码的

规范把属性分两类,区别在「取值这个动作怎么完成」7:

数据属性(data property)就是直觉的样子:值存在档案里([[Value]]), 你存什么取什么,无计算、无副作用8。drawer.contents = 'invoices' 之后 读回来就是 'invoices'。

访问器属性(accessor property)是特务:档案里存的不是值, 是两个函数——读取时跑 getter(取值函数),赋值时跑 setter(赋值函数)。 从外部看它和普 通属性一模一样,但每次读写背后都有代码在执行: 可以算、可以顺带改别的属性、可以有副作用9。

主线例子:一个会自己算账的抽屉。 照原书的例子走一遍——

const dynamicDrawer = {
  _actualContents: ['receipt-001.pdf', 'receipt-002.pdf', 'tax-form.pdf'],
  get contents() {                       // 读取时:不返回值,运行这段代码
    return this._actualContents.length > 0
      ? `${this._actualContents.length} documents`
      : 'empty';
  },
  set contents(newItem) {                // 赋值时:不覆盖,运行这段代码
    if (typeof newItem === 'string') {
      this._actualContents.push(newItem);
    }
  }
};
console.log(dynamicDrawer.contents);       // '3 documents'  ← getter 算的
dynamicDrawer.contents = 'invoice.pdf';    // setter 执行:push 进数组
console.log(dynamicDrawer.contents);       // '4 documents'

每一步旁边都有具体状态:读之前 _actualContents.length 是 3,getter 返回 '3 documents';赋值 'invoice.pdf' 没有覆盖任何东西,而是被 setter 追加进数组,length 变 4,第二次读取返回 '4 documents'10。

访问器能造成「成员访问也会触发副作用」的惊讶——C++ 来的开发者最不习惯这个 (那里的成员访问永远直连内存)。原书的建议:刻意地用访问器; 拿不准,你就不该用它11。

3.3 怪现象之王:process.env 为什么吃掉你的对象

把访问器换成 Node.js 真实场景,怪现象立刻有了生产级的重量:

process.env.FOO = { a: 1 };
process.env.FOO;    // '[object Object]' ???

赋值明明「成功」了:不报错,运行时不吭声。读回来却是一串莫名其妙的字符串12。 谜底:process.env 是个特殊对象,它的每个属性默认都是访问器, setter 拿到你赋的值后,总是先做字符串转换再存13——对象转字符串就是 06 章学过的 '[object Object]'。理解了「属性有两种、访问器会跑代码」, 这个谜就从「见鬼」降级成「哦,setter」;不理解,就会在 Stack Overflow 上 浪费一个下午14。这本书的技术编辑在审稿时质疑过「普通开发者不知道 [[Get]]/[[Value]] 不也过得好好的?」——这三个怪现象(加上 3.5 的 三个属性怪现象)就是作者的答辩15。

3.4 双括号记号:规范的「内部档案」长什么样

先认一下规范的语言。书里大量出现 [[Value]]、[[Get]]、[[Prototype]] 这样的双括号记号:它来自 ECMAScript 规范,用来区分「引擎内部的机械」 和「你写的代码」16。规矩是:

  • 内部方法(internal methods)是引擎执行的操作:你写 obj.foo, 引擎内部执行 [[Get]];
  • 内部槽(internal slots)是引擎用来存数据的位置:[[Value]]、[[Prototype]] 都是槽——档案袋,不是函数17。

你永远不会在代码里写 obj.[[Get]]('foo'),但这些记号是读规范、读 V8 文档的 通行证,本拆解后面几章会反复用到。

3.5 三个属性,三类怪现象

描述符的全貌出来了:数据属性六个档案格([[Value]]、[[Writable]]、 [[Get]]/[[Set]] 不适用、[[Enumerable]]、[[Configurable]]), 访问器属性换成 [[Get]]/[[Set]] 两格;其中 [[Value]]/[[Writable]] 与 [[Get]]/[[Set]] 互斥——一个属性不可能既是数据属性又是访问器18。

三个属性各自制造一类经典 bug,书里列得整整齐齐19:

属性一:writable —— 值为 false 时,赋值静默失败或抛错, 取决于运行模式:严格模式(strict mode)下抛 TypeError,松散模式(sloppy mode) 下安安静静地什么都不发生20。这就解释了「我写 obj.foo = 'bar', 没报错,值却没变」——去查 writable21。

属性二:enumerable —— 值为 false 时,属性存在但隐身: for...in 和 Object.keys() 都列不出它,直接访问却好好的22。 这就解释了「属性明明在,循环里就是看不见」23。

属性三:configurable —— 值为 false 时,delete 失灵, 而且 Object.defineProperty() 也不能再改它的档案:描述符整个锁死24。 注意 writable 和 configurable 管的是两回事: 值能不能改 vs 档案能不能改。一个属性可以「值能改但档案锁死」 (可写但不可配置),也可以反过来25。

想查看任何属性的档案,一句话:Object.getOwnPropertyDescriptor(obj, 'prop'), 返回 { value, writable, enumerable, configurable } 或 { get, set, enumerable, configurable }26。书里的金句:平时永远用不到它; 但怪事发生时,它能让你几秒钟定位问题,而不是搜几小时症状27。

3.6 默认值陷阱:同一个属性,两种出生待遇

最容易踩的坑来了:同样的三属性,默认值随创建方式不同而不同28。

const a = { prop: 'value' };                 // 字面量创建
Object.getOwnPropertyDescriptor(a, 'prop');
// { value: 'value', writable: true, enumerable: true, configurable: true }

const b = {};
Object.defineProperty(b, 'prop', { value: 'value' });   // defineProperty 创建
Object.getOwnPropertyDescriptor(b, 'prop');
// { value: 'value', writable: false, enumerable: false, configurable: false }

字面量创建,三个属性默认全开;Object.defineProperty() 创建, 你不显式写 true,默认全关29。规范的心态是:既然你绕远路用 defineProperty,就当你想要一个受保护的、受限的属性30。 配套还有个 Object.defineProperties()(带 s),一次定义一批, 两 者在档案含义上完全等价,只是一次定义多个更高效——为什么高效,要等 10 章的 隐藏类才讲得清31。

访问器属性的档案形状也不同:没有 value/writable,读出来是 undefined; 取而代之的是 get/set 两个函数32。用 getOwnPropertyDescriptor 一看便知属性是哪种。

3.7 承上启下:档案管「找到之后」,找的过程在下一章

描述符管的是「属性找到之后怎么对待你」。但还有个更根本的问题: drawer.toString() 这个方法,你的 drawer 上根本没有定义,它从哪冒出来的? 答案:继承——属性不一定在自己身上,引擎有一条专门的查找路线 (原型链)来决定一个属性到底从哪来33。档案 + 查找路线,两件 事合起来 才是一个属性的完整一生。查 找路线是下一章(08)的主角。

3.8 原书抽屉例子的完整展开:一个属性的四个动作

07 章的原书例子是一只「抽屉」(drawer),值和状态齐全, 值得把它的四个动作从头到尾走一遍,每一步旁边都有具体状态34:

const drawer = {
  width:      { value: 16, unit: 'inch' },   // 对象值
  height:     { value: 13, unit: 'inch' },
  depth:      { value: 28, unit: 'inch' },
  serialNumber: 1234567890123456789n,         // BigInt 值
  locking:    true,                          // 布尔值
  contents:   'tax documents'                // 字符串值
};

读:drawer.contents → 'tax documents'——数据属性的读, 就是档案里取 [[Value]],无计算、无副作用35。 写:drawer.contents = 'invoices' → 再读就是 'invoices', 档案里的 [[Value]] 被替换36。 查档案:Object.getOwnPropertyDescriptor(drawer, 'contents') → { value: 'invoices', writable: true, enumerable: true, configurable: true }—— 字面量创建,三属性全开37。 动态版:dynamicDrawer(3.2 节)的 contents 换成访问器后, 同样的四个动作,读和写背后都跑了代码,档案形状从 { value, writable, ... } 变成 { get, set, ... }38。

四个动作里藏着本章的真正主题:属性的「外表」完全相同 (drawer.contents 永远是这个写法),「内在」却可以一个是存值、 一个是跑函数——而区分它们的方法,只有 getOwnPropertyDescriptor 这一扇窗。这就是技术编辑质疑「普通开发者不需要懂描述符」时, 作者用 process.env 谜案作答的原因:你可以不懂,直到你撞上。

3.9 本章的章末坐标

5.2 节在原书里是对象章的地基段,它的出口通向下文三处39:

  • 「档案管找到之后,查找管怎么找到」——5.3 的原型链,我们的 08;
  • 「同形状的多属性定义更高效」——5.8 的隐藏类,我们的 10;
  • 「访问器会跑代码」的警告贯穿始终——5.6 的 Proxy 把它推到极限,我们的 09。

三处出口里,5.2 自身回答的问题其实只有一个: 「一个属性除了值,还藏着什么?」 答案是六格档案与三个 true/false。 这九样东西平时全不可见,却各自对应一类生产怪现象; 而 Object.getOwnPropertyDescriptor 是唯一的透视窗—— 书里那句「几秒钟定位,而不是搜几小时症状」,就是为它写的广告词40。

3.10 一句话记住本章:属性是「带档案的值」

压缩成一句可以带走的话:JavaScript 的属性从来不只是「值」, 它是「值 + 六格档案」的组合体;赋值、枚举——也就是逐个列出成员——、删除这些日常动作, 全是档案里某几格的 true/false 在起作用。 记住这句, 四类怪现象(静默失败、循环隐身、delete 失灵、process.env 吃对象) 就从「玄学」降级为「查档案」——而查档案的工具 Object.getOwnPropertyDescriptor,从 ES5(2009)起就没换过。

3.11 描述符与冻结(freeze)家族:同一张档案的多属性操作

边界与局限里提过「冻结家族」(Object.freeze 等把属性一次性锁死的三个 API)没展开,这里补一笔(标注:这一小节的三个 API 原书未细讲,属我们依据描述符机制的补充)41: Object.preventExtensions(obj) 禁止加新属性(档案不可新增)、 Object.seal(obj) 在此外再禁止删(档案的 configurable 全锁)、 Object.freeze(obj) 再再禁止改值(档案的 writable 全锁)。 三档递增,全是「一次性改档案」的语法糖——懂了 3.5 的三个属性, 冻结家族就不需要背:它们只是把三个属性批量锁死的封装。 配套的还有两道常见面试题的答案:「为什么 freeze 之后改值静默失败 (或严格模式下报错)?」——writable 批量变 false,回到 3.5 节; 「为什么 freeze 之后还能改嵌套对象的字段?」——freeze 只锁 第一层档案的那三个属性,嵌套对象的档案不归它管,想深冻要自己递归。 两道题的答案都在同一格 [[Writable]] 上,这就是机制学习的复利: 一个格子,解一串题。

最后把 07 章挂回全书主线:描述符是语言层的构造(规范里的 六格档案,所有引擎必须实现同一套规则),而「档案怎么存」是 10 章 隐藏类的话题,「档案沿链怎么查」是 08 章原型链的话题。 语言层给你档案的定义,实现层给你档案的速度—— 07 章站在两者的分界线上,左边是「属性是什么」,右边是「属性怎么飞」。

作者的判断与证据

有证据的判断:

  • 三属性的行为(writable 静默失败/抛错、enumerable 隐身、configurable 锁死) 是规范事实,书中逐一举例并给出 getOwnPropertyDescriptor 的输出42。
  • 字面量 vs defineProperty 的默认值差异,有代码输出为证43。

作者的主张:

  • 「process.env 谜案」的机制解释(每属性默认是访问器)是作者以 Node.js 核心成员身份给出的实现内情,属一手证词44。
  • 「拿不准就别用访问器」是实践立场,反方(访问器利于封装)书中未辩论。

判断(我们的,不是书里的): 描述符的价值不在日常使用,而在排障时有名字可喊—— 「这个属性 configurable 是 false」比「它删不掉」在沟通与检索上都高一个量级; 本章把三个属性翻译成三种可观察现象(静默失败/循环隐身/delete 无效), 是全书「把规范词汇变成排障词汇」的示范。 如果错,会错在: 在高度依赖元编程的代码库(ORM、状态管理)里,描述符不是 排障词汇而是日常 API,本章「平时不用管」的定调会低估使用频率——但机制本身不变。

边界与局限

  • 双括号档案是语言规范层的描述方式;V8 实际不按六格存(它走隐藏类 + 描述符数组,见 10 章),书里也注明了这点45。
  • 属性遍历的全部 API(for...in、Object.keys、getOwnPropertyNames、 含 symbol 的 getOwnPropertySymbols)之间的差别,原书没列表,只在 enumerable 一处带过。
  • Object.freeze/Object.seal/Object.preventExtensions 这三个 「一次锁定多个属性」的 API 本章完全没出现——它们其实就是描述符属性的一次性版本。
  • process.env 的具体行为属 Node 实现,其他宿主不保证。

可带走的

  1. 对象 = 动态的键值对集合;引擎事先不知道形状,找属性要「搜索」——性能话题的种子。
  2. 属性分两种:数据属性存值;访问器属性存两个函数,读写都会跑代码(可能有副作用)。
  3. process.env.FOO = {} 变 '[object Object]':访问器 setter 做了字符串转换。
  4. 三属性:writable(值)、enumerable(列表)、configurable(档案本身); 对应三类怪现象:静默失败、循环隐身、delete 失灵。
  5. 默认值陷阱:字面量全 true;defineProperty 不显式写就全 false。
  6. Object.getOwnPropertyDescriptor() 是调试这三类怪现象的第一工具。
  7. 双括号记号([[Get]]、[[Value]])是规范的内部机械,读规范前先认它。
  8. 描述符管「找到之后」;「怎么找到」是下一章的原型链。

原文地图

主题原书章原文位置
对象 = 名字-值对5.1 What are objects?text/16-ch05-01-5-1-what-are-objects.txt:7(搜「name-value pairs」)
动态映射 vs C++ 定结构同上text/16-ch05-01-5-1-what-are-objects.txt:30(搜「dynamic maps」) · :49(搜「whole process of searching」)
两种属性定义5.2 Propertiestext/17-ch05-02-5-2-properties.txt:13(搜「Data properties」) · :16(搜「Accessor properties」)
编辑的质疑与三个怪现象同上text/17-ch05-02-5-2-properties.txt:138(搜「D. Vanderkam」) · :151(搜「Silent failures」) · :154(搜「disappear from loops」) · :157(搜「cannot be deleted」)
process.env 谜案同上text/17-ch05-02-5-2-properties.txt:175(搜「process.env.FOO」) · :181(搜「coerced into strings」)
双括号记号同上text/17-ch05-02-5-2-properties.txt:194(搜「double brackets」) · :206(搜「internal slots」)
数据属性存值同上text/17-ch05-02-5-2-properties.txt:226(搜「stores its value directly」)
getter/setter 行为同上text/17-ch05-02-5-2-properties.txt:297(搜「runs the getter」) · :330(搜「3 documents」)
「刻意地用访问器」同上text/17-ch05-02-5-2-properties.txt:345(搜「Use accessors deliberately」)
getOwnPropertyDescriptor同上text/17-ch05-02-5-2-properties.txt:365(搜「reveals what is normally hidden」)
writable 与两种模式同上text/17-ch05-02-5-2-properties.txt:403(搜「strict and sloppy」)
enumerable 隐身同上text/17-ch05-02-5-2-properties.txt:444(搜「invisible to attempts to iterate」)
configurable 锁死同上text/17-ch05-02-5-2-properties.txt:478(搜「delete no longer removes」)
writable vs configurable同上text/17-ch05-02-5-2-properties.txt:516(搜「writable controls whether」)
默认值差异同上text/17-ch05-02-5-2-properties.txt:547(搜「All true」) · :573(搜「All false」) · :583(搜「more restricted」)
defineProperties 多属性版同上text/17-ch05-02-5-2-properties.txt:589(搜「multiple properties at once」) · :612(搜「equivalent in terms」)
访问器档案形状同上text/17-ch05-02-5-2-properties.txt:624(搜「different descriptor shape」) · :661(搜「no value or writable」)
「几秒钟定位」同上text/17-ch05-02-5-2-properties.txt:690(搜「seconds rather than hours」)
通向原型链同上text/17-ch05-02-5-2-properties.txt:696(搜「toString property is not defined」)
抽屉例子的四动作5.2 Propertiestext/17-ch05-02-5-2-properties.txt:254(搜「const drawer = {」) · :373(搜「getOwnPropertyDescriptor(drawer」)
章末坐标与出口同上text/17-ch05-02-5-2-properties.txt:690(搜「seconds rather than hours」) · :696(搜「prototype chain」)

Footnotes

  1. 出处:「5 Objects」第 85 段(text/15-ch05-5-objects.txt:85,搜「Objects all the way down」)。「甚至数组和函数也只是对象的不同种类」见同段。 ↩

  2. 出处:「5.1 What are objects?」第 7 段(text/16-ch05-01-5-1-what-are-objects.txt:7,搜「name-value pairs」)。 ↩

  3. 出处:「5.1 What are objects?」第 24 段(text/16-ch05-01-5-1-what-are-objects.txt:24,搜「fixed structures」)。「编译器知道内存布局」在同段。 ↩

  4. 出处:「5.1 What are objects?」第 30 段(text/16-ch05-01-5-1-what-are-objects.txt:30,搜「dynamic maps」);例句见第 37 段(搜「filingCabinet.contents」)。 ↩

  5. 出处:「5.1 What are objects?」第 43 段(text/16-ch05-01-5-1-what-are-objects.txt:43,搜「mix in functionality」)。 ↩

  6. 出处:「5.1 What are objects?」第 49 段(text/16-ch05-01-5-1-what-are-objects.txt:49,搜「whole process of searching」)。「重大性能问题」在同段末尾。 ↩

  7. 出处:「5.2 Properties」第 13–16 段(text/17-ch05-02-5-2-properties.txt:13,搜「Data properties that store」)。 ↩

  8. 出处:「5.2 Properties」第 226 段(text/17-ch05-02-5-2-properties.txt:226,搜「No computation, no side effects」)。 ↩

  9. 出处:「5.2 Properties」第 297 段(text/17-ch05-02-5-2-properties.txt:297,搜「does not retrieve a stored value」)。 ↩

  10. 出处:「5.2 Properties」第 310–332 段(text/17-ch05-02-5-2-properties.txt:310,搜「dynamicDrawer」;:330 搜「3 documents」)。逐步状态与书中代码一致。 ↩

  11. 出处:「5.2 Properties」第 345 段(text/17-ch05-02-5-2-properties.txt:345,搜「Use accessors deliberately」)。 ↩

  12. 出处:「5.2 Properties」第 175–181 段(text/17-ch05-02-5-2-properties.txt:175,搜「process.env.FOO」;:181 搜「nonsensical string value」)。 ↩

  13. 出处:「5.2 Properties」第 181 段(text/17-ch05-02-5-2-properties.txt:181,搜「every property is defined by default to be an accessor」)。 ↩

  14. 出处:「5.2 Properties」第 145–162 段(text/17-ch05-02-5-2-properties.txt:162,搜「searching google for symptoms」)。「诊断从几小时变几秒」的姊妹句见第 690 段。 ↩

  15. 出处:「5.2 Properties」第 138 段(text/17-ch05-02-5-2-properties.txt:138,搜「show me why」)。三个怪现象清单见第 151–157 段。 ↩

  16. 出处:「5.2 Properties」第 194 段(text/17-ch05-02-5-2-properties.txt:194,搜「double brackets」)。 ↩

  17. 出处:「5.2 Properties」第 200 段与第 200 段(text/17-ch05-02-5-2-properties.txt:200,搜「operations that the engine performs」;:206 搜「storage locations」)。 ↩

  18. 出处:「5.2 Properties」第 124 段与表 5.1(text/17-ch05-02-5-2-properties.txt:33,搜「Table 5.1」;互斥说明在 :124,搜「mutually exclusive」)。 ↩

  19. 出处:「5.2 Properties」第 151–157 段(text/17-ch05-02-5-2-properties.txt:151,搜「Silent failures that make no sense」)。「没有词汇描述你看到的 bug」见第 145 段(搜「vocabulary」)。 ↩

  20. 出处:「5.2 Properties」第 403 段(text/17-ch05-02-5-2-properties.txt:403,搜「strict and sloppy」)。「严格模式校验更严」的说明在同段。 ↩

  21. 出处:「5.2 Properties」第 151 段(text/17-ch05-02-5-2-properties.txt:151,搜「obj.foo = 'bar' and nothing happens」)。 ↩

  22. 出处:「5.2 Properties」第 444 段(text/17-ch05-02-5-2-properties.txt:444,搜「for...in loops and the Object.keys()」)。 ↩

  23. 出处:「5.2 Properties」第 154 段(text/17-ch05-02-5-2-properties.txt:154,搜「invisible to enumeration」)。 ↩

  24. 出处:「5.2 Properties」第 478 段(text/17-ch05-02-5-2-properties.txt:478,搜「descriptor becomes locked」)。 ↩

  25. 出处:「5.2 Properties」第 516 段(text/17-ch05-02-5-2-properties.txt:516,搜「writable controls whether you can change the value」)。 ↩

  26. 出处:「5.2 Properties」第 365–381 段(text/17-ch05-02-5-2-properties.txt:373,搜「getOwnPropertyDescriptor(drawer」)。 ↩

  27. 出处:「5.2 Properties」第 690 段(text/17-ch05-02-5-2-properties.txt:690,搜「seconds rather than hours」)。 ↩

  28. 出处:「5.2 Properties」第 528 段(text/17-ch05-02-5-2-properties.txt:528,搜「different default values」)。 ↩

  29. 出处:「5.2 Properties」第 534–545 段与第 557–573 段(text/17-ch05-02-5-2-properties.txt:534,搜「object literal syntax」;:557 搜「Object.defineProperty()」)。 ↩

  30. 出处:「5.2 Properties」第 583 段(text/17-ch05-02-5-2-properties.txt:583,搜「more restricted, protected property」)。 ↩

  31. 出处:「5.2 Properties」第 589 段与第 589 段(text/17-ch05-02-5-2-properties.txt:589,搜「define multiple properties at once」;:612 搜「equivalent in terms of how they define」)。 ↩

  32. 出处:「5.2 Properties」第 661 段(text/17-ch05-02-5-2-properties.txt:661,搜「no value or writable attribute」)。 ↩

  33. 出处:「5.2 Properties」第 696 段(text/17-ch05-02-5-2-properties.txt:696,搜「The method is inherited」)。 ↩

  34. 出处:「5.2 Properties」第 254–279 段(text/17-ch05-02-5-2-properties.txt:255,搜「width: { value: 16」):抽屉对象与 contents 的读写。 ↩

  35. 出处:「5.2 Properties」第 226 段(text/17-ch05-02-5-2-properties.txt:226,搜「No computation, no side effects」)。 ↩

  36. 出处:「5.2 Properties」第 279 段(text/17-ch05-02-5-2-properties.txt:279,搜「replaces it」)。 ↩

  37. 出处:「5.2 Properties」第 372–381 段(text/17-ch05-02-5-2-properties.txt:373,搜「getOwnPropertyDescriptor(drawer」):返回档案的四字段。 ↩

  38. 出处:「5.2 Properties」第 624–661 段(text/17-ch05-02-5-2-properties.txt:641,搜「typeof descriptor.get」):访问器档案的 get/set 形状。 ↩

  39. 出处:「5.2 Properties」第 690–696 段(text/17-ch05-02-5-2-properties.txt:690,搜「seconds rather than hours」;:696 搜「prototype chain mechanism」)。 ↩

  40. 出处:「5.2 Properties」第 690 段(text/17-ch05-02-5-2-properties.txt:690,搜「seconds rather than hours」)。 ↩

  41. 补充(不在书里,来自通用知识):preventExtensions/seal/freeze 的语义是描述符三属性的批量版,ES5 起标准化。原书仅在「批量锁档案」一句带过,未展开。 ↩

  42. 出处:「5.2 Properties」第 403–506 段(text/17-ch05-02-5-2-properties.txt:417,搜「internalId」;:460 搜「['contents']」;:486 搜「serialNumber」)。 ↩

  43. 出处:「5.2 Properties」第 541–545 段与第 569–573 段(text/17-ch05-02-5-2-properties.txt:547,搜「All true」;:573 搜「All false」)。 ↩

  44. 出处:「5.2 Properties」第 181 段(text/17-ch05-02-5-2-properties.txt:181,搜「a special kind of object」)。补充(不在书里,来自通用知识):process.env 在近年 Node 版本中实现细节有调整,但「赋值即字符串化」的行为保持稳定。 ↩

  45. 出处:「5.2 Properties」第 239 段(text/17-ch05-02-5-2-properties.txt:239,搜「Implementations like V8 use different internal mechanisms」)。 ↩

On this page