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 真实场景,怪现象立刻有了生产级的重量:
赋值明明「成功」了:不报错,运行时不吭声。读回来却是一串莫名其妙的字符串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),值和状态齐全, 值得把它的四个动作从头到尾走一遍,每一步旁边都有具体状态38:
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]],无计算、无副作用39。
写:drawer.contents = 'invoices' → 再读就是 'invoices',
档案里的 [[Value]] 被替换40。
查档案:Object.getOwnPropertyDescriptor(drawer, 'contents') →
{ value: 'invoices', writable: true, enumerable: true, configurable: true }——
字面量创建,三属性全开41。
动态版:dynamicDrawer(3.2 节)的 contents 换成访问器后,
同样的四个动作,读和写背后都跑了代码,档案形状从
{ value, writable, ... } 变成 { get, set, ... }42。
四个动作里藏着本章的真正主题:属性的「外表」完全相同
(drawer.contents 永远是这个写法),「内在」却可以一个是存值、
一个是跑函数——而区分它们的方法,只有 getOwnPropertyDescriptor
这一扇窗。这就是技术编辑质疑「普通开发者不需要懂描述符」时,
作者用 process.env 谜案作答的原因:你可以不懂,直到你撞上。
3.9 本章的章末坐标¶
5.2 节在原书里是对象章的地基段,它的出口通向下文三处43:
- 「档案管找到之后,查找管怎么找到」——5.3 的原型链,我们的
08; - 「同形状的多属性定义更高效」——5.8 的隐藏类,我们的
10; - 「访问器会跑代码」的警告贯穿始终——5.6 的 Proxy 把它推到极限,我们的
09。
三处出口里,5.2 自身回答的问题其实只有一个:
「一个属性除了值,还藏着什么?」 答案是六格档案与三个 true/false。
这九样东西平时全不可见,却各自对应一类生产怪现象;
而 Object.getOwnPropertyDescriptor 是唯一的透视窗——
书里那句「几秒钟定位,而不是搜几小时症状」,就是为它写的广告词44。
3.10 一句话记住本章:属性是「带档案的值」¶
压缩成一句可以带走的话:JavaScript 的属性从来不只是「值」,
它是「值 + 六格档案」的组合体;赋值、枚举——也就是逐个列出成员——、删除这些日常动作,
全是档案里某几格的 true/false 在起作用。 记住这句,
四类怪现象(静默失败、循环隐身、delete 失灵、process.env 吃对象)
就从「玄学」降级为「查档案」——而查档案的工具 Object.getOwnPropertyDescriptor,从 ES5(2009)起就没换过。
3.11 描述符与冻结(freeze)家族:同一张档案的多属性操作¶
边界与局限里提过「冻结家族」(Object.freeze 等把属性一次性锁死的三个 API)没展开,这里补一笔(标注:这一小节的三个 API
原书未细讲,属我们依据描述符机制的补充)45:
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的输出34。 - 字面量 vs defineProperty 的默认值差异,有代码输出为证35。
作者的主张:
- 「process.env 谜案」的机制解释(每属性默认是访问器)是作者以 Node.js 核心成员身份给出的实现内情,属一手证词36。
- 「拿不准就别用访问器」是实践立场,反方(访问器利于封装)书中未辩论。
判断(我们的,不是书里的): 描述符的价值不在日常使用,而在排障时有名字可喊—— 「这个属性 configurable 是 false」比「它删不掉」在沟通与检索上都高一个量级; 本章把三个属性翻译成三种可观察现象(静默失败/循环隐身/delete 无效), 是全书「把规范词汇变成排障词汇」的示范。 如果错,会错在: 在高度依赖元编程的代码库(ORM、状态管理)里,描述符不是 排障词汇而是日常 API,本章「平时不用管」的定调会低估使用频率——但机制本身不变。
边界与局限¶
- 双括号档案是语言规范层的描述方式;V8 实际不按六格存(它走隐藏类 + 描述符数组,见 10 章),书里也注明了这点37。
- 属性遍历的全部 API(for...in、Object.keys、getOwnPropertyNames、 含 symbol 的 getOwnPropertySymbols)之间的差别,原书没列表,只在 enumerable 一处带过。
Object.freeze/Object.seal/Object.preventExtensions这三个 「一次锁定多个属性」的 API 本章完全没出现——它们其实就是描述符属性的一次性版本。- process.env 的具体行为属 Node 实现,其他宿主不保证。
可带走的¶
- 对象 = 动态的键值对集合;引擎事先不知道形状,找属性要「搜索」——性能话题的种子。
- 属性分两种:数据属性存值;访问器属性存两个函数,读写都会跑代码(可能有副作用)。
process.env.FOO = {}变'[object Object]':访问器 setter 做了字符串转换。- 三属性:writable(值)、enumerable(列表)、configurable(档案本身); 对应三类怪现象:静默失败、循环隐身、delete 失灵。
- 默认值陷阱:字面量全 true;defineProperty 不显式写就全 false。
Object.getOwnPropertyDescriptor()是调试这三类怪现象的第一工具。- 双括号记号(
[[Get]]、[[Value]])是规范的内部机械,读规范前先认它。 - 描述符管「找到之后」;「怎么找到」是下一章的原型链。
原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 对象 = 名字-值对 | 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 Properties | text/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 Properties | text/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」) |
-
出处:「5 Objects」第 85 段(
text/15-ch05-5-objects.txt:85,搜「Objects all the way down」)。「甚至数组和函数也只是对象的不同种类」见同段。 ↩ -
出处:「5.1 What are objects?」第 7 段(
text/16-ch05-01-5-1-what-are-objects.txt:7,搜「name-value pairs」)。 ↩ -
出处:「5.1 What are objects?」第 24 段(
text/16-ch05-01-5-1-what-are-objects.txt:24,搜「fixed structures」)。「编译器知道内存布局」在同段。 ↩ -
出处:「5.1 What are objects?」第 30 段(
text/16-ch05-01-5-1-what-are-objects.txt:30,搜「dynamic maps」);例句见第 37 段(搜「filingCabinet.contents」)。 ↩ -
出处:「5.1 What are objects?」第 43 段(
text/16-ch05-01-5-1-what-are-objects.txt:43,搜「mix in functionality」)。 ↩ -
出处:「5.1 What are objects?」第 49 段(
text/16-ch05-01-5-1-what-are-objects.txt:49,搜「whole process of searching」)。「重大性能问题」在同段末尾。 ↩ -
出处:「5.2 Properties」第 13–16 段(
text/17-ch05-02-5-2-properties.txt:13,搜「Data properties that store」)。 ↩ -
出处:「5.2 Properties」第 226 段(
text/17-ch05-02-5-2-properties.txt:226,搜「No computation, no side effects」)。 ↩ -
出处:「5.2 Properties」第 297 段(
text/17-ch05-02-5-2-properties.txt:297,搜「does not retrieve a stored value」)。 ↩ -
出处:「5.2 Properties」第 310–332 段(
text/17-ch05-02-5-2-properties.txt:310,搜「dynamicDrawer」;:330搜「3 documents」)。逐步状态与书中代码一致。 ↩ -
出处:「5.2 Properties」第 345 段(
text/17-ch05-02-5-2-properties.txt:345,搜「Use accessors deliberately」)。 ↩ -
出处:「5.2 Properties」第 175–181 段(
text/17-ch05-02-5-2-properties.txt:175,搜「process.env.FOO」;:181搜「nonsensical string value」)。 ↩ -
出处:「5.2 Properties」第 181 段(
text/17-ch05-02-5-2-properties.txt:181,搜「every property is defined by default to be an accessor」)。 ↩ -
出处:「5.2 Properties」第 145–162 段(
text/17-ch05-02-5-2-properties.txt:162,搜「searching google for symptoms」)。「诊断从几小时变几秒」的姊妹句见第 690 段。 ↩ -
出处:「5.2 Properties」第 138 段(
text/17-ch05-02-5-2-properties.txt:138,搜「show me why」)。三个怪现象清单见第 151–157 段。 ↩ -
出处:「5.2 Properties」第 194 段(
text/17-ch05-02-5-2-properties.txt:194,搜「double brackets」)。 ↩ -
出处:「5.2 Properties」第 200 段与第 200 段(
text/17-ch05-02-5-2-properties.txt:200,搜「operations that the engine performs」;:206搜「storage locations」)。 ↩ -
出处:「5.2 Properties」第 124 段与表 5.1(
text/17-ch05-02-5-2-properties.txt:33,搜「Table 5.1」;互斥说明在:124,搜「mutually exclusive」)。 ↩ -
出处:「5.2 Properties」第 151–157 段(
text/17-ch05-02-5-2-properties.txt:151,搜「Silent failures that make no sense」)。「没有词汇描述你看到的 bug」见第 145 段(搜「vocabulary」)。 ↩ -
出处:「5.2 Properties」第 403 段(
text/17-ch05-02-5-2-properties.txt:403,搜「strict and sloppy」)。「严格模式校验更严」的说明在同段。 ↩ -
出处:「5.2 Properties」第 151 段(
text/17-ch05-02-5-2-properties.txt:151,搜「obj.foo = 'bar' and nothing happens」)。 ↩ -
出处:「5.2 Properties」第 444 段(
text/17-ch05-02-5-2-properties.txt:444,搜「for...in loops and the Object.keys()」)。 ↩ -
出处:「5.2 Properties」第 154 段(
text/17-ch05-02-5-2-properties.txt:154,搜「invisible to enumeration」)。 ↩ -
出处:「5.2 Properties」第 478 段(
text/17-ch05-02-5-2-properties.txt:478,搜「descriptor becomes locked」)。 ↩ -
出处:「5.2 Properties」第 516 段(
text/17-ch05-02-5-2-properties.txt:516,搜「writable controls whether you can change the value」)。 ↩ -
出处:「5.2 Properties」第 365–381 段(
text/17-ch05-02-5-2-properties.txt:373,搜「getOwnPropertyDescriptor(drawer」)。 ↩ -
出处:「5.2 Properties」第 690 段(
text/17-ch05-02-5-2-properties.txt:690,搜「seconds rather than hours」)。 ↩ -
出处:「5.2 Properties」第 528 段(
text/17-ch05-02-5-2-properties.txt:528,搜「different default values」)。 ↩ -
出处:「5.2 Properties」第 534–545 段与第 557–573 段(
text/17-ch05-02-5-2-properties.txt:534,搜「object literal syntax」;:557搜「Object.defineProperty()」)。 ↩ -
出处:「5.2 Properties」第 583 段(
text/17-ch05-02-5-2-properties.txt:583,搜「more restricted, protected property」)。 ↩ -
出处:「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」)。 ↩ -
出处:「5.2 Properties」第 661 段(
text/17-ch05-02-5-2-properties.txt:661,搜「no value or writable attribute」)。 ↩ -
出处:「5.2 Properties」第 696 段(
text/17-ch05-02-5-2-properties.txt:696,搜「The method is inherited」)。 ↩ -
出处:「5.2 Properties」第 403–506 段(
text/17-ch05-02-5-2-properties.txt:417,搜「internalId」;:460搜「['contents']」;:486搜「serialNumber」)。 ↩ -
出处:「5.2 Properties」第 541–545 段与第 569–573 段(
text/17-ch05-02-5-2-properties.txt:547,搜「All true」;:573搜「All false」)。 ↩ -
出处:「5.2 Properties」第 181 段(
text/17-ch05-02-5-2-properties.txt:181,搜「a special kind of object」)。补充(不在书里,来自通用知识):process.env在近年 Node 版本中实现细节有调整,但「赋值即字符串化」的行为保持稳定。 ↩ -
出处:「5.2 Properties」第 239 段(
text/17-ch05-02-5-2-properties.txt:239,搜「Implementations like V8 use different internal mechanisms」)。 ↩ -
出处:「5.2 Properties」第 254–279 段(
text/17-ch05-02-5-2-properties.txt:255,搜「width: { value: 16」):抽屉对象与 contents 的读写。 ↩ -
出处:「5.2 Properties」第 226 段(
text/17-ch05-02-5-2-properties.txt:226,搜「No computation, no side effects」)。 ↩ -
出处:「5.2 Properties」第 279 段(
text/17-ch05-02-5-2-properties.txt:279,搜「replaces it」)。 ↩ -
出处:「5.2 Properties」第 372–381 段(
text/17-ch05-02-5-2-properties.txt:373,搜「getOwnPropertyDescriptor(drawer」):返回档案的四字段。 ↩ -
出处:「5.2 Properties」第 624–661 段(
text/17-ch05-02-5-2-properties.txt:641,搜「typeof descriptor.get」):访问器档案的 get/set 形状。 ↩ -
出处:「5.2 Properties」第 690–696 段(
text/17-ch05-02-5-2-properties.txt:690,搜「seconds rather than hours」;:696搜「prototype chain mechanism」)。 ↩ -
出处:「5.2 Properties」第 690 段(
text/17-ch05-02-5-2-properties.txt:690,搜「seconds rather than hours」)。 ↩ -
补充(不在书里,来自通用知识):
preventExtensions/seal/freeze的语义是描述符三属性的批量版,ES5 起标准化。原书仅在「批量锁档案」一句带过,未展开。 ↩