编程语言研究书库

06 原始类型、转换与相等: bug 的头号产房

06 原始类型、转换与相等: bug 的头号产房

这一章讲什么

书里开篇放了三个生产 bug:支付系统因为 if (!discountCode) 把 '0' 当成 「没折扣」而重复扣款;排序函数因为接口改成传字符串而悄悄慢 40 倍; 'yes' == true 居然是 false——尽管 if ('yes') 明明算真1。 三个 bug,同一个病根:JavaScript 会在你没同意的地方,替你把值从一种类型 转成另一种类型。 这一章把最后四种原始类型(undefined、null、boolean、symbol) 讲完,然后把「转换」这个魔鬼的完整规则摊开:它在哪里发生、按什么规则转、 为什么 [] == false 成立而 if ([]) 又当真,以及怎么写代码才能不被它咬。

顶层全景:七个原始类型,三种相撞方式

  原始类型:undefined · null · boolean · number · bigint · string · symbol
                │
                ├─ 放进条件里     → 转 boolean(八个 falsy 值,其余全 true)
                ├─ 做算术(+ - * /)→ 加号偏字符串,减乘除转数字
                └─ 互相比较(== < >)→ == 链式转换;< > 两边是字符串就按字母序
                │
                唯一免疫区:===(严格相等,类型不同直接 false,永不转换)

核心原理

3.1 两种「空」:undefined 是没说话,null 是说「没有」

JavaScript 有两 个表示「没有」的值,而且故意分开:undefined 表示 「还没被赋值」,null 表示「刻意留空」2。作者的比方:问孩子在干嘛, 没吭声(undefined)多半是走神没听见;中气十足答「没干嘛!」(null) 那绝对有情况3。工程翻译:函数返回 null 是在说「我查过了,这里什么都没有」; 返回 undefined 是在说「我还没查」或「你问错了」。

两者的行为差异立刻可见:null == undefined 是 true(宽松相等特赦这一对), 但 null === undefined 是 false;Number(null) 是 0,而 Number(undefined) 是 NaN4。后者尤其坑:0 能参与运算,NaN 让所有运算变 NaN。

约定俗成的分工(不是规范,是社区惯例)5:

用 undefined 表示用 null 表示
变量还没初始化刻意清空一个值
函数没写 return「查过了,不存在」的查询结果
对象上不存在的属性数据库查询的「无结果」

检验时的经典写法:if (val == null)。这是全书给 == 开的唯一一张许可证: 它一次抓住 undefined 和 null 两种空,不用写两个判断;过了这关, val 就保证是个有值的值6。反过来说,val === undefined 单独用通常是错的—— 它会把「刻意清空」的 null 放过去。

3.2 falsy 与 truthy:条件里的八个叛徒

主线例子:那个重复扣款的支付函数。 代码本意是「没有折扣码就用默认值」:

function processPayment(amount, discountCode, isPriority) {
  if (!discountCode) {           // ← bug 在这里
    discountCode = 'NONE';
  }
  const finalAmount = amount - (discountCode * 0.01 * amount);
  ...
}
processPayment(100, '0', false);  // '0' 是有效码(0% 折扣),却被当成「没有」!

病根是 JavaScript 的一个决定:条件表达式不只认 true/false, 而是把任何值都先转成布尔值——转出来是 false 的叫 falsy, 转出来是 true 的叫 truthy7。falsy 一共只有八个,背下来: undefined、null、false、0、-0、0n、NaN、''(空字符串)8。 除此之外的一切都是 truthy——包括这几个反直觉的:'0'(非空字符串)、 'false'(字符串!)、[](空数组)、{}(空对象)9。

所以 '0' 明明是真值,!discountCode 却先把它转布尔再取反: '0' → true → ! → false—— 不,等等,'0' 是 truthy,!'0' 是 false,条件不成立,折扣码保留? 书里这个 bug 的实际机制更绕:它针对的是 discountCode 参与 * 0.01 乘法时 被转成数字 0 的场景——无论哪种路径,教训同一个:条件判断做了你没看见的转换, 而你的数据里恰好有落在 falsy 名单上的值10。修法永远是「检查你真正想检查的」: if (code == null) 只抓两种空,把 '' 和 0 当合法值放行11。

作者补了一桩亲历事故为 falsy 的危害定级:一位客户的 falsy 检查写错 (少了个 !),配合一次意外的 truthy 转换,把整个用户数据库泄了出去12。 「理解 truthy/falsy 不是可选项」这话,放在这个代价上不夸张。

3.3 布尔运算的真本事:选值,不只是判真

&& 和 || 常被叫「逻辑运算符」,但它们实际做的是选值, 还带短路: a && b 在 a falsy 时直接返回 a,根本不执行 b; a || b 在 a truthy 时直接返回 a13。所以:

null && doSomething();      // → null,doSomething 从未被调用
'' && 'world';              // → ''(短路)
name || 'Anonymous';        // → name 非空用 name,否则用 'Anonymous'
0 || 42;                    // → 42(0 是 falsy)

把任何值转布尔的万能技巧是双感叹号 !!:第一个 ! 转布尔并取反, 第二个再取反回来,!!'hello' → true,!!null → false14。

两个后来者都是为 falsy 的坑打的补丁:

  • 空值合并 ??:只因为 null/undefined 才算「空」, 其他 falsy 值(0、''、false)都算数。0 ?? 42 → 0,'' ?? 'default' → ''15。 它存在的理由书里写得很直白:开发者被 falsy 坑得太频繁,规范专门加了它16。
  • 逻辑赋值 ||=、&&=、??=:右边只在需要时执行, name ||= 'Anonymous' 等于「name 为 falsy 就赋值」17。

这一族运算符的共同本质:它们是控制流(决定程序下一步执行哪段代码的机制),不是布尔代数——返回值可以是任意类型18。

3.4 symbol:保证不撞名的属性键

前六种原始类型各管一件事:空、真假、整数、大整数、文本。第七种 symbol (符号)解决一个更现代的问题:属性名冲突。场景:你写的库要往用户对象上 挂元数据(描述数据的数据),用字符串 'id' 当属性名,哪天用户的代码或另一个库也用了 'id', 互相覆盖,双双爆炸19。

symbol 的保证只有一条,但足够硬:每个 symbol 都唯一,即使描述字符串相同 Symbol('foo') === Symbol('foo') 是 false;描述串只用于调试, 不参与身份20。对比字符串:'foo' === 'foo' 是 true(同内容即同值, 这类类型叫值类型;symbol 不遵守这条)21。

symbol 的三种来源:

写法结果
Symbol('app.id')每次新建,永远唯一
Symbol.for('app.id')全局注册表:同 key 返回同一个 symbol,跨 iframe 也共享
Symbol.iterator 等语言的 well-known symbols(知名符号),语言自己用来定义协议22

知名符号是这一章通向第 5 章的桥:for...of 背后查的是 Symbol.iterator, instanceof 查 Symbol.hasInstance——语言给自己加新功能时, 就靠 symbol 预留的键位不撞旧代码23。

成本要记账:symbol 是原始类型里的重量级选手,V8 里一个数字占 8 字节, 一个 symbol 约 24–32 字节(对象头、内容指纹、名字指针、标志位)24。 所以「只在有必要时用」——而多数应用代码一辈子不需要亲手造 symbol, 知道它存在、看得懂别人的代码即可25。

3.5 转换在哪里发生:一张固定的位置表

JavaScript 的隐式转换(术语 coercion,强制转换)名声很差, 但书里的核心立场是:它发生的位置是固定、可以逐个列出、可学的—— 学会这张表,bug 可以在发生前被看见26。

位置规则例子
- * / %总是转数字'10' - 5 → 5;'abc' - 1 → NaN;true / false → Infinity
+偏向字符串:任一侧是字符串就拼串'5' + 3 → '53';5 + 3 → 8
==链式转换后再比(下面 3.6 专讲)5 == '5' → true;'' == 0 → true
< > <= >=两字符串按字母序;否则转数字'10' < '9' → true!'10' < 9 → false
条件、!、&&/``
模板字面量 ${}、+ 拼串转字符串`Count: ${42}` → 'Count: 42'
内置方法参数各自想要特定的类型,悄悄转parseInt('42px') → 42;Math.max('10', 5) → 10

两个最容易栽的点,单独立碑。第一,+ 和它的兄弟们规则不同: '5' + 3 拼串得 '53','5' - 3 转数字得 2——同一个 '5',两个方向27。 第二,'10' < '9' 是 true:两边都是字符串时按字母序逐位比, '1' 在 '9' 前面;混合类型才转数字28。这就是 ["10", "2", "1"].sort() 给出 ["1", "10", "2"] 的原因—— 默认 sort 把元素当字符串排29。

三个关键转换的具体规则值得各记一小段30:

转数字里要警惕的:Number('') 是 0(空串不是 NaN!)、Number(' ') 是 0、 Number([]) 是 0、Number([5]) 是 5、Number([1,2]) 是 NaN、Number('0x12') 是 18——十六进制字符串会被「好心」转换,一个校验年龄的函数 '0x12' >= 18 && '0x12' <= 120 居然放行31。

转字符串里最怪的仍然是数组:String([]) 是 '',String([1,2,3]) 是 '1,2,3', String([null]) 是 ''32。为什么?这里藏着全章最漂亮的机制推演:

3.6 主线推演:[1] 为什么能转数字,[1,2] 为什么是 NaN

Number([1]) 是 1,Number([1,2]) 是 NaN——表面毫无道理,拆开就合理: 数组根本没有「转数字」这条路。真实的链条是两次转换: 数组先转字符串,字符串再转数字,Number([1]) 实际是 Number(String([1]))33。于是:

  Number([1])   = Number('1')    = 1    ✓
  Number([1,2]) = Number('1,2')  = NaN  ← 解析数字时撞见逗号,放弃
  Number([])    = Number('')     = 0    ← 空串转数字是 0(上一轮刚说过)

一个规则(数组→字符串是 join)、一个规则(空串→0),两个看似无关的特例 拼出了数组转数字的全部怪行为34。这个推演值得模仿:遇到任何「不可能」的 转换结果,问「它中间经过了哪条链」——规范的转换永远是一环一环的, 没有一步登天的魔法。

再看链式转换的巅峰:[] == false。三步35:

  ① false 转数字        → 0
  ② [] 转字符串再转数字 → '' → 0
  ③ 0 == 0              → true ✓

而 if ([]) 是真——空数组 truthy。空数组「等于 false」又「在条件里当真」, 两个都对,因为它俩走的是两条不同的转换链36。作者在这里罕见地放飞: 「这一条,你翻开规范想问作者们当时怎么想的,完全可以理解」37。 老手的自保法则就来自这里:怀疑时用 ===,远离聪明的比较38。

3.7 性能的账:40 倍慢与「边界转换一次」铁律

转换不只产 bug,还产慢代码。主线例子:排序时间戳(标出某个时间点的值)。 开发环境一切正常:

const timestamps = [-3429705600, 1232467200, 1611158400];
timestamps.sort((a, b) => a - b);          // 数字,飞快

上线后接口改成传 ISO 字符串,代码一行没动,功能还对—— 但慢 40 倍:排序比较函数 a - b 要转数字,而比较会执行约 n·log n 次, 一万条时间戳就是几十万次字符串转数字39。代码没有坏,只是每一步都 在交转换税;税单在数据量放大后才寄到。

规则的原文值得记住:数据在进入系统时转换一次,不要在代码里反复转、 尤其绝不在循环里转。一次转换微不足道,重复转换让应用爬着走40。 配套细节:一个一万个元素的数组被转字符串,是实打实地遍历、拼接、 造出成千上万个中间串(还记得 03 章的 ConsString 吗),最后得到个 NaN—— 两头亏41。

规范层还有个注脚:所有这些转换背后是规范定义的十六个抽象操作 (如 ToInt32),你永远不会直接调用它们,但它们解释了为什么 String.fromCharCode(65.9) 截断成 'A'、Array(3.7) 变成 3 个槽、 | 0 成了取整偏方42。作者的建议同样干脆:别背十六个操作; 有用的是三件事:类型上显式、边界处转换、比较用 ===43。

3.8 == 与 ===:一页规范 vs 一条规则

宽松相等 == 的规则在规范里占了一整页,一个表达式可能串三次转换 (true == '1' 就是三转换的实例)44。严格相等 === 的规则一句话: 不做任何类型转换,类型不同直接 false45。简单意味着快、意味着不出意外, 所以书里的判词:「拿不准时,用严格相等」46。

== 唯一被认可的用法,就是 3.1 节那张许可证:x == null。 其他场景下 == 提供的「便利」,本质是把你该显式做的转换藏起来—— 原型阶段可以用,生产代码的地基别压在上面47。

3.9 V8 的收尾:原始值零分配

最后看一眼实现层:这七个原始类型在 V8 里怎么存?数字走 SMI/HeapNumber(05 章), 字符串走六种内部表示(03 章),而 null、undefined、true、false 连堆都不用上——V8 用指针标记(pointer tagging)把这些值直接编码成 「不可能是真指针」的特殊位模式(比如 0x08…0000 是 null、…0001 是 true), 存在指针槽位里,用时零解引用48。这类值叫立即值(immediate values)—— 用得飞快,因为根本没有「内存里的真身」这回事49。

至此三层的拼图又齐一块:语言定义转换规则 → V8 给每种原始值挑最省的存法 → 而 bug 全都生在「你没料到转换会发生」的地方。

3.5b 转换位置全表:七类场所,七个规则

3.5 节给了速查表,这里把原书七张表的完整规则摊开,每个场所一句话记住43:

① 算术运算符。 -、*、/、%、&、|、^ 一律先转数字再算: '10' - 5 → 5,'5' & '2' → 0b101 & 0b010 = 0,'abc' - 1 → NaN, 最精彩的是 true / false → 1 / 0 → Infinity44。

② 加号。 唯一的两面派:两侧都是数字才做加法,任一侧是字符串就拼串; '5' + 3 → '53',5 + '3' → '53',5 + 3 → 845。 数组在这里的戏份最足:[1] + [2] → '12',因为两边都先转字符串。

③ 宽松相等 ==。 规则最多:数字与字符串比,转数字; 布尔与任何比,先转数字;null 与 undefined 互判相等(且不跟别人比); 对象与原始值比,对象先转原始值。5 == '5' → true,0 == false → true, '' == 0 → true,null == undefined → true,但 null == 0 → false46。

④ 关系比较 < > <= >=。 两字符串按字典序逐字符比;混合类型转数字。 '10' < '9' → true(字典序!),'10' < 9 → false(转数字)47。 null < 1 → true(null 转 0),undefined < 1 → false(undefined 转 NaN,NaN 跟谁都不成立)48。

⑤ 逻辑与条件场合。 就是 falsy/truthy 那八值名单,3.2 节已拆透。

⑥ 模板字面量与字符串拼接。 ${} 里的任何东西转字符串: `Count: ${42}` → 'Count: 42','Value: ' + null → 'Value: null', 'Sum: ' + undefined → 'Sum: undefined'49。

⑦ 内置方法的参数。 每个方法按自己想要的类型悄悄转:parseInt('42px') → 42 (解析到非数字就停)、Math.max('10', 5) → 10、Array(3.7) → 长度 3 (小数截断)、'hello'.charAt('2') → 'l'(字符串参数转数字)50。

七类场所里,最危险的是 ③④⑤——它们藏在「看起来只是比一比」的地方, 而且「测试时都对」。表单字段返回的字符串能通过数值校验, 偶尔返回 null 的接口不影响算术,这些静默的通过,全是把 bug 推迟到生产环境51。

3.6b 三个关键转换:完整清单与坑位一览

原书把十六个转换操作归拢成「最常用、最常出 bug 的三个」, 这里给每个的完整清单,坑位加粗52:

转数字,要警惕的: Number(undefined) → NaN;Number('') 与 Number(' ') → 0 (空串是 0 不是 NaN!);Number('hello') → NaN; Number([]) → 0;Number([5]) → 5;Number([1,2]) → NaN; Number('0x12') → 18(十六进制照收)53。 常规的全在意料之中:false → 0、true → 1、null → 0、'42' → 42、'3.14' → 3.14。

转字符串,唯一的怪区是数组:String([]) → ''、String([1,2,3]) → '1,2,3'、 String([null]) → '';其余全是直觉:undefined → 'undefined'、 null → 'null'、true → 'true'54。

转布尔,就是 falsy 名单反着用:八个值转 false,其余全 true(3.2 节)。

两个「为什么会这样」的答案都藏在链条里:Number([5]) 是 「数组→字符串 '5' →数字 5」;String([null]) 是 「[null] →join 成 ''(null 在 join 里变成空串)→ ''」。 没有一步是特例,全是两个规则的组合55。

3.6c [] == false 全链复盘与「自保法则」

3.6 节的推演值得再压成一张清单,因为它是全书最适合装进口袋的标本56:

  [] == false
  ① false 是布尔值 → 转数字        false → 0
  ② [] 是对象 → 转原始值          [] → ''(join)→ '' 再转数字 → 0
  ③ 0 == 0                        → true

而同一页的对面:if ([]) 是 true——空数组 truthy。 「等于 false」与「在条件里当真」同时成立,各自遵循各自的规则, 规范一条没违反57。作者在这里罕见地替读者发声:「这一条,你翻开规范 想问作者们当时怎么想的,完全可以理解」58——然后给出老手的自保法则: 怀疑时用 ===,远离聪明的比较59。

3.7b 性能账的完整推演:税是怎么收到 40 倍的

3.7 节的排序例子,把账细算一遍。对 n 个元素排序,比较函数大约执行 n·log₂n 次;每次执行 a - b 要转两次数字(如果输入是字符串)60。 n = 10,000 时,log₂n ≈ 13.3,比较约 13 万次,字符串转数字约 26 万次; n = 1,000 时也是数千次转换。同样的转换在边界处做一次,成本是零头; 在比较函数里做几十万次,成本就是 40 倍61。

同一个病在循环里换个姿势再来:if (value > 0) 里的 value 若是字符串, 每圈一次转换;字符串循环拼接又叠加 03 章的 ConsString 账—— 转换税和内存税经常一起收62。规则的原文再引一次,因为它是 本章最该贴墙的一句:「数据在进入系统时转换一次,不要在代码里反复转、 尤其绝不在循环里转。一次转换微不足道,重复转换让应用爬着走」63。

3.8b 规范的十六个抽象操作:只知道存在就够

规范在背后定义了十六个抽象操作(abstract operations)负责所有自动转换, 你永远不会直接调用它们,但两个面试级的实例值得认识64:

  • ToInt32 / ToUint32 家族:String.fromCharCode(65.9) 截断成 'A', String.fromCharCode('65') 先转数字再取模,String.fromCharCode(65601) 按 65536 取模回到 65;Array(3.7) 长度变 3、Array(2**32) 直接 RangeError; 位运算 x | 0 之所以成为「取整偏方」,就是它强制 ToInt3265。
  • 为什么 fromCharCode 宽容而 fromCodePoint 严格:前者走 ToUint16 (截断+取模),后者要求合法码点,非法就抛——同一个家族,两个脾气66。

作者的忠告同样干脆:别背十六个操作;有用的是三件事—— 类型上显式、边界处转换、比较用 ===67。让语言的自动转换当原型的 便利,别当生产的地基。

3.1b 两种「空」的完整论证

3.1 节给了结论,这里把原书的论证链补全68。作者先立论: 单一 null 的语言乍看更简单,但迟早要在别处把区别再造出来—— 可选类型(option types)、Maybe 单子、可空注解, 全是「没赋值」与「刻意空」这对区分的还魂69。JavaScript 从一开始 就把它内置了:undefined 是「还没赋值」,null 是「刻意留空」70。

那个孩子比方值得完整转述,因为它是全书最精准的一个直觉锚: 问孩子在干嘛,没回答(undefined)——多半走神没听见,可以假设没事; 中气十足答一句「没干嘛!」(null)——绝对有情况,得去查71。 工程翻译同样成对:函数返回 null 是在说「我查过了,这里啥也没有」; 返回 undefined 是在说「我还没查」或「你问错问题了」72。 惯例表(3.1 节)不是规范,是社区在「该不该分」这个问题上投了三十年的票。

检验写法也因此有对错:val === undefined 把「刻意清空」的 null 放过去,通常不是你要的;val == null 一次网住两种空, 过了这关,val 保证是个「有值」的值73——这是松散相等在全书的 唯一合法岗位,书里专门用「注意」框供起来了74。

3.2b falsy 名单的边界情况:−0 与 0n

八个 falsy 值里有两个常被忽略:−0 和 0n(BigInt 的零)。 0 === -0 是 true,但两者都是 falsy;0n 也一样75。 反直觉阵营更值得背:'0' 是非空字符串 → truthy;'false' 是字符串 → truthy;[]、{} 「空」得不能再空,可它们不是那八个值 → truthy76。 判断口诀不是「空不空」,而是**「在不在名单上」**——名单只有八个, 背下来比任何直觉都可靠。

作者给 falsy 的重要性定过级,值得再引一次: 理解 truthy/falsy「不是可选项」;他的一位客户因为一个写错的 falsy 检查 (少一个 !)加上一次意外的 truthy 转换,把整个用户数据库泄了出去77。 这一类 bug 的恐怖在于:代码能跑、测试能过,出事的方式是「数据被静默放行」。

3.3b 三个补丁运算符的对照实例

原书给了一组并排例子,适合直接当 cheatsheet78:

0 ?? 42;              // 0     —— 0 不是「空」,原样返回
'' ?? 'default';      // ''    —— 空串不是「空」
null ?? 'backup';     // 'backup'
'' || 'default';      // 'default' —— || 认 falsy
0 || 42;              // 42
'' && 'world';        // ''    —— 短路,直接返回 ''(不执行后面)

&&=、||=、??= 是它们的赋值版:name ||= 'Anonymous' 等于 「name 为 falsy 就赋值」;??= 只认 null/undefined79。 这一族运算符的共同本质原书说得很准:它们做的是选值和控制流, 不是布尔代数——返回值可以是任意类型,别再当「逻辑运算」理解80。

3.4b symbol 与字符串的本质对照

「值类型」这个性质值得单独一记:字符串(以及数字、布尔)是值类型—— 两个变量内容相同,它们就全等(m === n 为 true);symbol 不是, Symbol('foo') === Symbol('foo') 永远是 false81。 这个「不遵守值相等」正是 symbol 的全部存在理由: 你要的就是一个「除了我自己,全世界没有任何东西等于它」的凭证, 拿它当属性名,撞名在机制上不可能82。

描述串(Symbol('mySymbol'))只是调试标签,不参与身份: sym1.description 和 sym2.description 都是 'mySymbol', 可 sym1 === sym2 还是 false83。全局注册表(Symbol.for)是 「想要跨模块共享同一个 symbol」的解法:同 key 返回同一个, 甚至跨 iframe/跨 realm 也共享——普通 Symbol() 则永远是私有的84。

内存账再记一遍:数字 8 字节,symbol 约 24–32 字节 (对象头 + 内容指纹 + 名字指针 + 标志位)85。 所以「只在有必要时用」——而多数应用代码一辈子不需要亲手造 symbol; 知道它存在、看得懂 Symbol.iterator 们,就够86。

3.9b 指针标记:四个值的家

3.9 节一句话带过,这里把原书那张位模式表转述出来87:

值V8 中的位模式(64 位)说明
undefined0x08000000000000XX固定模式,尾几位随版本漂移
null0x0800000000000000另一个固定模式
true0x0800000000000001「否则不可能出现」的指针值
false0x0800000000000002同上

这类值叫立即值(immediate values):不占用堆内存, 使用时零次解引用,所以「用起来飞快」88。 把 06 章的实现层拼回前面几章:数字走 SMI/HeapNumber(05 章), 字符串走六种内部表示(03 章),null/undefined/布尔走立即值—— V8 给每种原始值都挑了最省的姿势,而它们对代码完全隐形。 这就是「实现层」这本书反复回来的原因:看不见,但它天天决定你的程序快慢。

3.10 本章的章末总结(原书八条)

原书第 4 章总结八条,逐条对应本章各节,照录作核对单89:

  1. undefined=没赋值,null=刻意空——两种「没有」;
  2. falsy 只有八个:undefined, null, false, 0, -0, 0n, NaN, '',其余全 truthy;
  3. symbol 保证唯一的属性键,让库能安全地扩展对象;
  4. 转换自动发生在算术、比较、模板字面量和无数内置方法里;
  5. + 偏字符串拼接,- * / 总是转数字;
  6. == 按复杂规则链式转换,=== 永不转换;
  7. 在边界处一次性转换数据,别让循环和比较函数反复转换;
  8. V8 用指针标记让 null/undefined/布尔零分配。

八条里没有一条靠「背」能守得住——每一条背后都是本章拆过的机制: 名单、链条、位置表、位模式。转换是规则,不是魔法;规则就能学, 学了就能防。 这就是第 4 章相对网上一切「JS 怪癖合集」的区别。

作者的判断与证据

有证据的判断:

  • falsy 名单、三个关键转换的输入输出,全部是规范可查的事实, 书中用代码块逐一实测50。
  • [] == false 的三步推演与规范一致,可逐步复算51。
  • 「40 倍慢」来自书中实验描述(数字版 vs 字符串版排序)52。

作者的主张:

  • 「'0' 被当成 false」开场 bug 的归因叙述(双扣款)是作者讲的故事, 机制解释( falsy 名单 + 转换)成立,但故事本身无独立佐证。
  • 「泄漏用户数据库」事故同样是一手叙述,细节(缺一个 !)无法从外部验证53。
  • 「x == null 是 == 唯一该用的场景」是社区主流立场,书中作为定论给出。

判断(我们的,不是书里的): 这一章把「隐式转换」从「JS 很怪」的谈资 改写成了一张固定格子地图——触发点有限、规则可查、性能账可算——这个 reframing 是真正降低 bug 率的:随机应变的惊愕变成了可背诵的清单。 如果错,会错在: 团队如果因此把 == 一律禁掉、连 x == null 也禁, 就会多写不少啰嗦的双重检查;规则的价值在于「知道例外」,一刀切会让例外变成 新的背锅点。

边界与局限

  • 本章只讲原始类型之间的转换;对象参与转换时另有 valueOf/toString 的调用顺序,明确留给第 5 章——MEAP 里那部分(5.7 节)讲得很简略。
  • 「十六个抽象操作」没有展开清单;ToPrimitive 的三分支优先级 (Date 类对象优先 toString 等)原书完全没碰。
  • == 的完整规则表(规范那一页)本书只给了入口,没有逐一推演—— 想深挖只能读规范原文。
  • 指针标记的具体位模式随 V8 版本变化,书中也注明 X 位会漂移54。

可带走的

  1. 两种空:undefined=没赋值,null=刻意空;x == null 一次抓俩,是 == 的唯一许可证。
  2. falsy 只有八个:undefined, null, false, 0, -0, 0n, NaN, '';其余全 truthy,包括 '0'、[]、{}。
  3. 条件判断前先问:我查的是「空不空」还是「是不是某个具体值」?用 == null 或显式比对,别用真值性当存在性检查。
  4. &&/||/?? 是选值运算符:?? 只认 null/undefined,0 和 '' 都能安全通过。
  5. + 偏字符串、- * / 转数字、== 链式转换、< 两字符串按字母序——四个位置,四组规则。
  6. 任何怪转换,拆成链:Number([1,2]) = Number('1,2') = NaN。
  7. 数据在边界转换一次;绝不让比较函数、循环体里的转换重复发生。
  8. 拿不准就用 ===;让隐式转换当原型的便利,别当生产的地基。

原文地图

主题原书章原文位置
开篇三个 bug4 Primitives, Coercion, and Equalitytext/14-ch04-4-primitives-coercion-and-equality.txt:41(搜「double-charging」) · :92(搜「Three different bugs」)
两种「空」同上text/14-ch04-4-primitives-coercion-and-equality.txt:127(搜「not yet assigned」) · :133(搜「Nothing, dad」)
null/undefined 行为差异同上text/14-ch04-4-primitives-coercion-and-equality.txt:177(搜「null == undefined」)
使用惯例表同上text/14-ch04-4-primitives-coercion-and-equality.txt:205(搜「Conventions for choosing」)
val == null 模式同上text/14-ch04-4-primitives-coercion-and-equality.txt:271(搜「val == null」) · :299(搜「few cases where loose equality」)
falsy 八值名单同上text/14-ch04-4-primitives-coercion-and-equality.txt:331(搜「falsy value is any」)
truthy 的惊喜成员同上text/14-ch04-4-primitives-coercion-and-equality.txt:366(搜「Everything else is truthy」)
折扣码 bug 与修复同上text/14-ch04-4-primitives-coercion-and-equality.txt:393(搜「applyDiscount」) · :428(搜「applyDiscountFixed」)
数据库泄漏事故同上text/14-ch04-4-primitives-coercion-and-equality.txt:518(搜「leak their entire user database」)
双感叹号与短路同上text/14-ch04-4-primitives-coercion-and-equality.txt:542(搜「double-bang」) · :578(搜「a && b」)
?? 的来历同上text/14-ch04-4-primitives-coercion-and-equality.txt:627(搜「nullish coalescing」)
逻辑赋值同上text/14-ch04-4-primitives-coercion-and-equality.txt:658(搜「Assignment shortcuts」)
symbol 的唯一性保证同上text/14-ch04-4-primitives-coercion-and-equality.txt:714(搜「introduced in ES6」) · :784(搜「description string」)
全局注册表同上text/14-ch04-4-primitives-coercion-and-equality.txt:832(搜「global symbol registry」)
知名符号同上text/14-ch04-4-primitives-coercion-and-equality.txt:872(搜「well-known symbols」)
symbol 的内存成本同上text/14-ch04-4-primitives-coercion-and-equality.txt:892(搜「24-32 bytes」)
40 倍慢的排序同上text/14-ch04-4-primitives-coercion-and-equality.txt:997(搜「40x slower」) · :1790(搜「40 times longer」)
'0x12' 过校验同上text/14-ch04-4-primitives-coercion-and-equality.txt:1039(搜「hexadecimal notation」)
加减乘除 vs 加号同上text/14-ch04-4-primitives-coercion-and-equality.txt:1084(搜「always convert values to numbers」) · :1148(搜「prefers strings」)
字母序比较同上text/14-ch04-4-primitives-coercion-and-equality.txt:1307(搜「alphabetical comparison」)
三个关键转换同上text/14-ch04-4-primitives-coercion-and-equality.txt:1571(搜「three」) · :1601(搜「Number(false)」) · :1688(搜「String([])」)
[1] vs [1,2] 的链条同上text/14-ch04-4-primitives-coercion-and-equality.txt:1816(搜「Number([1])」) · :1823(搜「Number(String([1]))」)
[] == false 三步同上text/14-ch04-4-primitives-coercion-and-equality.txt:1730(搜「false becomes 0」)
边界转换规则同上text/14-ch04-4-primitives-coercion-and-equality.txt:1856(搜「Convert your data once at the boundaries」)
十六个抽象操作同上text/14-ch04-4-primitives-coercion-and-equality.txt:1870(搜「sixteen abstract operations」) · :1923(搜「don’t do that」)
== 的一页规则 vs ===同上text/14-ch04-4-primitives-coercion-and-equality.txt:1959(搜「full page」) · :1965(搜「no type conversions」)
指针标记与立即值同上text/14-ch04-4-primitives-coercion-and-equality.txt:452(搜「pointer tagging」) · :510(搜「immediate values」)

Footnotes

  1. 出处:「4 Primitives, Coercion, and Equality」第 41 段(text/14-ch04-4-primitives-coercion-and-equality.txt:41,搜「double-charging」)与第 60–82 段的代码(搜「processPayment」)。三个 bug 的分述见第 98–104 段(搜「The !discountCode check」)。 ↩

  2. 出处:「4 Primitives, Coercion, and Equality」第 127 段(text/14-ch04-4-primitives-coercion-and-equality.txt:127,搜「not yet assigned」)。 ↩

  3. 出处:「4 Primitives, Coercion, and Equality」第 133 段(text/14-ch04-4-primitives-coercion-and-equality.txt:133,搜「Nothing, dad」)。 ↩

  4. 出处:「4 Primitives, Coercion, and Equality」第 177–189 段(text/14-ch04-4-primitives-coercion-and-equality.txt:177,搜「null == undefined」;Number(null)→0、Number(undefined)→NaN 的注释见第 187、189 段)。 ↩

  5. 出处:「4 Primitives, Coercion, and Equality」第 205–252 段(text/14-ch04-4-primitives-coercion-and-equality.txt:199,搜「There aren’t any formal rules」;表格在 :205 起)。 ↩

  6. 出处:「4 Primitives, Coercion, and Equality」第 266–299 段(text/14-ch04-4-primitives-coercion-and-equality.txt:271,搜「val == null」;:299 搜「few cases where loose equality is exactly what you want」)。 ↩

  7. 出处:「4 Primitives, Coercion, and Equality」第 319 段(text/14-ch04-4-primitives-coercion-and-equality.txt:319,搜「truthy and falsy values」)。 ↩

  8. 出处:「4 Primitives, Coercion, and Equality」第 331–361 段(text/14-ch04-4-primitives-coercion-and-equality.txt:331,搜「falsy value is any of the following」)。 ↩

  9. 出处:「4 Primitives, Coercion, and Equality」第 366–383 段(text/14-ch04-4-primitives-coercion-and-equality.txt:366,搜「Everything else is truthy」)。'0'、'false'、[]、{} 四个例子在同段列出。 ↩

  10. 出处:「4 Primitives, Coercion, and Equality」第 41、98–104 段(text/14-ch04-4-primitives-coercion-and-equality.txt:98,搜「The !discountCode check thinks」)。折扣码为空串时 '' * 0.01 得 NaN 的第二个 bug 见第 101 段。 ↩

  11. 出处:「4 Primitives, Coercion, and Equality」第 428–433 段(text/14-ch04-4-primitives-coercion-and-equality.txt:428,搜「applyDiscountFixed」)。 ↩

  12. 出处:「4 Primitives, Coercion, and Equality」第 518 段(text/14-ch04-4-primitives-coercion-and-equality.txt:518,搜「leak their entire user database」)。 ↩

  13. 出处:「4 Primitives, Coercion, and Equality」第 572–611 段(text/14-ch04-4-primitives-coercion-and-equality.txt:578,搜「If a is falsy, b is not evaluated」)。各例结果见第 593–611 段。 ↩

  14. 出处:「4 Primitives, Coercion, and Equality」第 542–556 段(text/14-ch04-4-primitives-coercion-and-equality.txt:542,搜「double-bang」)。 ↩

  15. 出处:「4 Primitives, Coercion, and Equality」第 627–648 段(text/14-ch04-4-primitives-coercion-and-equality.txt:633,搜「only treats null and undefined」)。 ↩

  16. 出处:「4 Primitives, Coercion, and Equality」第 627 段(text/14-ch04-4-primitives-coercion-and-equality.txt:627,搜「Because of how often developers」)。 ↩

  17. 出处:「4 Primitives, Coercion, and Equality」第 658–681 段(text/14-ch04-4-primitives-coercion-and-equality.txt:672,搜「name ||=」)。 ↩

  18. 出处:「4 Primitives, Coercion, and Equality」第 690 段(text/14-ch04-4-primitives-coercion-and-equality.txt:690,搜「any value type」)。 ↩

  19. 出处:「4 Primitives, Coercion, and Equality」第 702–708 段(text/14-ch04-4-primitives-coercion-and-equality.txt:702,搜「property name collisions」)。 ↩

  20. 出处:「4 Primitives, Coercion, and Equality」第 741–804 段(text/14-ch04-4-primitives-coercion-and-equality.txt:772,搜「k === l」;:784 搜「description string passed into Symbol()」)。 ↩

  21. 出处:「4 Primitives, Coercion, and Equality」第 741–756 段(text/14-ch04-4-primitives-coercion-and-equality.txt:741,搜「value types」)。字符串同内容即同值的例子见第 748–753 段。 ↩

  22. 出处:「4 Primitives, Coercion, and Equality」第 845–866 段(text/14-ch04-4-primitives-coercion-and-equality.txt:849,搜「Symbol.for」);「跨 realm/iframe 也共享」见第 865 段(搜「even across different realms」)。 ↩

  23. 出处:「4 Primitives, Coercion, and Equality」第 820 段与第 820 段(text/14-ch04-4-primitives-coercion-and-equality.txt:820,搜「Every time you use a for...of」;:878 搜「well-known symbols」)。 ↩

  24. 出处:「4 Primitives, Coercion, and Equality」第 892–907 段(text/14-ch04-4-primitives-coercion-and-equality.txt:892,搜「24-32 bytes」)。 ↩

  25. 出处:「4 Primitives, Coercion, and Equality」第 727 段(text/14-ch04-4-primitives-coercion-and-equality.txt:727,搜「unlikely that you will directly make use」)。 ↩

  26. 出处:「4 Primitives, Coercion, and Equality」第 1072 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1072,搜「specific, predictable ways」)。 ↩

  27. 出处:「4 Primitives, Coercion, and Equality」第 1084–1151 段(减乘除表)与第 1148 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1148,搜「prefers strings」);「开发者 constantly 被绊倒」见第 1203 段(搜「constantly tripping」)。 ↩

  28. 出处:「4 Primitives, Coercion, and Equality」第 1307 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1307,搜「Uses alphabetical comparison」)。 ↩

  29. 出处:「4 Primitives, Coercion, and Equality」第 1991 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1991,搜「comparing strings, not numbers」)。 ↩

  30. 出处:「4 Primitives, Coercion, and Equality」第 1571 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1571,搜「sixteen conversion operations」)。布尔转换见第 1583 段,数字转换见第 1601–1642 段,字符串转换见第 1653–1694 段。 ↩

  31. 出处:「4 Primitives, Coercion, and Equality」第 1039 段与第 1607–1642 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1039,搜「hexadecimal notation」;:1624 搜「Number(undefined)」;:1636 搜「Number([])」)。 ↩

  32. 出处:「4 Primitives, Coercion, and Equality」第 1688–1694 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1688,搜「String([])」)。 ↩

  33. 出处:「4 Primitives, Coercion, and Equality」第 1823 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1823,搜「Number(String([1]))」)。 ↩

  34. 出处:「4 Primitives, Coercion, and Equality」第 1823–1829 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1823,搜「bails out」)。「一万元素数组转字符串」的代价见第 1835 段。 ↩

  35. 出处:「4 Primitives, Coercion, and Equality」第 1718–1739 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1730,搜「The false becomes 0」)。 ↩

  36. 出处:「4 Primitives, Coercion, and Equality」第 1744–1757 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1744,搜「Arrays are always truthy」;:1757 搜「following the specification perfectly」)。 ↩

  37. 出处:「4 Primitives, Coercion, and Equality」第 1770 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1770,搜「what in the world the authors were thinking」)。 ↩

  38. 出处:「4 Primitives, Coercion, and Equality」第 1757 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1757,搜「avoid clever comparisons」)。 ↩

  39. 出处:「4 Primitives, Coercion, and Equality」第 997 段与第 997 段(text/14-ch04-4-primitives-coercion-and-equality.txt:997,搜「40x slower」;:1790 搜「hundreds of thousands of conversions」)。 ↩

  40. 出处:「4 Primitives, Coercion, and Equality」第 1856 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1856,搜「Convert your data once at the boundaries」)。「爬着走」在同段。 ↩

  41. 出处:「4 Primitives, Coercion, and Equality」第 1835 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1835,搜「Array(10_000)」)。 ↩

  42. 出处:「4 Primitives, Coercion, and Equality」第 1870–1917 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1884,搜「String.fromCharCode(65.9)」;:1911 搜「bitwise OR with zero」)。「截断与取模」的行为(65601 % 65536 = 65)见第 1897 段注释。 ↩

  43. 出处:「4 Primitives, Coercion, and Equality」第 1923 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1923,搜「don’t do that」)。 ↩ ↩2

  44. 出处:「4 Primitives, Coercion, and Equality」第 1959 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1959,搜「take up a full page」)。 ↩ ↩2

  45. 出处:「4 Primitives, Coercion, and Equality」第 1965 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1965,搜「no type conversions」)。 ↩ ↩2

  46. 出处:「4 Primitives, Coercion, and Equality」第 1965 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1965,搜「When in doubt, use strict equality」)。 ↩ ↩2

  47. 出处:「4 Primitives, Coercion, and Equality」第 1923 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1923,搜「a convenience when prototyping」)。 ↩ ↩2

  48. 出处:「4 Primitives, Coercion, and Equality」第 452 段与表(text/14-ch04-4-primitives-coercion-and-equality.txt:452,搜「pointer tagging」;具体位模式见第 460–502 段的表:null=0x0800000000000000、true=0x0800000000000001、false=0x0800000000000002)。 ↩ ↩2

  49. 出处:「4 Primitives, Coercion, and Equality」第 510 段(text/14-ch04-4-primitives-coercion-and-equality.txt:510,搜「immediate values」)。 ↩ ↩2

  50. 出处:「4 Primitives, Coercion, and Equality」第 337–361 段与第 1601–1694 段(text/14-ch04-4-primitives-coercion-and-equality.txt:337,搜「undefined」列表)。 ↩ ↩2

  51. 出处:「4 Primitives, Coercion, and Equality」第 1730–1739 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1739,搜「0 == 0」)。 ↩ ↩2

  52. 出处:「4 Primitives, Coercion, and Equality」第 997 段(text/14-ch04-4-primitives-coercion-and-equality.txt:997,搜「40x slower」)。注意这是书中描述的开发 anecdote,非严格基准报告。 ↩ ↩2

  53. 出处:「4 Primitives, Coercion, and Equality」第 518 段(text/14-ch04-4-primitives-coercion-and-equality.txt:518,搜「missing ! and an unexpected truthy conversion」)。 ↩ ↩2

  54. 出处:「4 Primitives, Coercion, and Equality」第 460–502 段的表(text/14-ch04-4-primitives-coercion-and-equality.txt:474,搜「may change from one version of V8」)。 ↩ ↩2

  55. 出处:「4 Primitives, Coercion, and Equality」第 1823–1829 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1823,搜「Number(String([1]))」):「其实没有数组转数字——先转字符串再转数字。」 ↩

  56. 出处:「4 Primitives, Coercion, and Equality」第 1718–1757 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1730,搜「false becomes 0」)。 ↩

  57. 出处:「4 Primitives, Coercion, and Equality」第 1744–1757 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1744,搜「Arrays are always truthy」;:1757 搜「following the specification perfectly」)。 ↩

  58. 出处:「4 Primitives, Coercion, and Equality」第 1770 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1770,搜「what in the world the authors were thinking」)。 ↩

  59. 出处:「4 Primitives, Coercion, and Equality」第 1757 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1757,搜「avoid clever comparisons」)。 ↩

  60. 出处:「4 Primitives, Coercion, and Equality」第 997 段(text/14-ch04-4-primitives-coercion-and-equality.txt:997,搜「converts strings to numbers」):「a - b 每次比较都要把两个字符串转数字;一万条时间戳,就是几十万次转换。」 ↩

  61. 出处:「4 Primitives, Coercion, and Equality」第 997 段(text/14-ch04-4-primitives-coercion-and-equality.txt:997,搜「40x slower」)。 ↩

  62. 出处:「4 Primitives, Coercion, and Equality」第 1843 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1843,搜「similar problems」):「同样的问题出现在循环里:if (value > 0) 在 value 是字符串时会反复转换;字符串拼接又给内存管理加压。」 ↩

  63. 出处:「4 Primitives, Coercion, and Equality」第 1856 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1856,搜「Convert your data once at the boundaries」)。 ↩

  64. 出处:「4 Primitives, Coercion, and Equality」第 1870 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1870,搜「sixteen abstract operations」)。 ↩

  65. 出处:「4 Primitives, Coercion, and Equality」第 1883–1901 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1883,搜「String.fromCharCode(65)」;:1897 搜「65601 % 65536」;:1911 搜「bitwise OR with zero」)。 ↩

  66. 出处:「4 Primitives, Coercion, and Equality」第 1911 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1911,搜「throws errors for invalid inputs」)。 ↩

  67. 出处:「4 Primitives, Coercion, and Equality」第 1923 段(text/14-ch04-4-primitives-coercion-and-equality.txt:1923,搜「don’t do that」)。 ↩

  68. 出处:「4 Primitives, Coercion, and Equality」第 121–133 段(text/14-ch04-4-primitives-coercion-and-equality.txt:121,搜「two kinds of nothing」)。 ↩

  69. 出处:「4 Primitives, Coercion, and Equality」第 127 段(text/14-ch04-4-primitives-coercion-and-equality.txt:127,搜「reinvent this distinction」):「单一 null 的语言看起来更简,最后都通过可选类型、Maybe 单子、可空注解把这对区别再造出来。」 ↩

  70. 出处:「4 Primitives, Coercion, and Equality」第 127 段(text/14-ch04-4-primitives-coercion-and-equality.txt:127,搜「not yet assigned」)。 ↩

  71. 出处:「4 Primitives, Coercion, and Equality」第 133 段(text/14-ch04-4-primitives-coercion-and-equality.txt:133,搜「Nothing, dad」)。 ↩

  72. 出处:「4 Primitives, Coercion, and Equality」第 164 段(text/14-ch04-4-primitives-coercion-and-equality.txt:164,搜「I checked, and there’s nothing here」)。 ↩

  73. 出处:「4 Primitives, Coercion, and Equality」第 266–282 段(text/14-ch04-4-primitives-coercion-and-equality.txt:266,搜「processValue」):第一段判 undefined 被标为 incorrect,第二段 val == null 被标为 correct。 ↩

  74. 出处:「4 Primitives, Coercion, and Equality」第 293–299 段(text/14-ch04-4-primitives-coercion-and-equality.txt:299,搜「few cases where loose equality」)。 ↩

  75. 出处:「4 Primitives, Coercion, and Equality」第 331–361 段(text/14-ch04-4-primitives-coercion-and-equality.txt:331,搜「falsy value is any」):八个值依次列出,含 -0 与 0n。 ↩

  76. 出处:「4 Primitives, Coercion, and Equality」第 366–383 段(text/14-ch04-4-primitives-coercion-and-equality.txt:366,搜「surprising examples」):'0'、'false'、[]、{} 四个 truthy 例子。 ↩

  77. 出处:「4 Primitives, Coercion, and Equality」第 518 段(text/14-ch04-4-primitives-coercion-and-equality.txt:518,搜「leak their entire user database」)。 ↩

  78. 出处:「4 Primitives, Coercion, and Equality」第 633–648 段(text/14-ch04-4-primitives-coercion-and-equality.txt:633,搜「only treats null and undefined」);||/&& 的例子见第 593–611 段。 ↩

  79. 出处:「4 Primitives, Coercion, and Equality」第 658–681 段(text/14-ch04-4-primitives-coercion-and-equality.txt:658,搜「Assignment shortcuts」)。 ↩

  80. 出处:「4 Primitives, Coercion, and Equality」第 690 段(text/14-ch04-4-primitives-coercion-and-equality.txt:690,搜「control flow」):「它们关乎控制流和选值。」 ↩

  81. 出处:「4 Primitives, Coercion, and Equality」第 741–756 段(text/14-ch04-4-primitives-coercion-and-equality.txt:741,搜「value types」):字符串例 m === n 为 true;symbol 例第 770–774 段为 false。 ↩

  82. 出处:「4 Primitives, Coercion, and Equality」第 784 段(text/14-ch04-4-primitives-coercion-and-equality.txt:784,搜「guaranteed to never equal」)。 ↩

  83. 出处:「4 Primitives, Coercion, and Equality」第 791–804 段(text/14-ch04-4-primitives-coercion-and-equality.txt:796,搜「description」)。 ↩

  84. 出处:「4 Primitives, Coercion, and Equality」第 845–866 段(text/14-ch04-4-primitives-coercion-and-equality.txt:849,搜「Symbol.for」);「跨 realm/iframe」见第 865 段(搜「even across different realms」)。 ↩

  85. 出处:「4 Primitives, Coercion, and Equality」第 892–907 段(text/14-ch04-4-primitives-coercion-and-equality.txt:892,搜「24-32 bytes」)。 ↩

  86. 出处:「4 Primitives, Coercion, and Equality」第 727 段(text/14-ch04-4-primitives-coercion-and-equality.txt:727,搜「unlikely that you will directly」)。 ↩

  87. 出处:「4 Primitives, Coercion, and Equality」第 452–510 段(text/14-ch04-4-primitives-coercion-and-equality.txt:452,搜「pointer tagging」;具体位模式见第 472–502 段的表)。 ↩

  88. 出处:「4 Primitives, Coercion, and Equality」第 510 段(text/14-ch04-4-primitives-coercion-and-equality.txt:510,搜「immediate values」)。 ↩

  89. 出处:「4 Primitives, Coercion, and Equality」第 2003–2029 段(text/14-ch04-4-primitives-coercion-and-equality.txt:121,搜「two kinds of nothing」;八条依次列出)。 ↩

On this page