跳到主要内容

数据结构 — 狼人松鼠的 90 天记录

这一章讲三件事: 把多个值装进一个值的两种容器(数组、对象); 「同一」与「相同」的区别为什么是本章最重要的理论;以及一个完整的小型数据分析—— 主角每天记录自己做了什么、是否变成了松鼠,我们用统计帮他从 90 天记录里揪出元凶。 读完你会拥有一整套「数据怎么摆」的词汇,和一条从原始记录到结论的主走查。

1. 先看故事:结构跟着问题走

Jacques 每晚八点到十点之间会变成一只毛茸茸的松鼠。他怀疑变化由某件事触发,于是每天记录做了什么、有没有变,指望从一天的记录里找出规律1

注意书的顺序:先有问题(找触发因素),再有数据结构。Babbage 那句著名的反问放在章首当题词——「给机器喂错的数,能出来对的答案吗?」——就是在提醒:结构错了,后面全错。

2. 数组:一排按序号取的格子

要把 2、3、5、7、11 装在一起,数组(array)是正解:方括号、逗号隔开2

let listOfNumbers = [2, 3, 5, 7, 11];
listOfNumbers[2]; // → 5
listOfNumbers[0]; // → 2
listOfNumbers[2 - 1]; // → 3 括号里是表达式,先算再取

下标从 0 开始。书里给的适应方法很实用:把下标读成「从头要跳过几个」——[0] 是跳过 0 个,即第一个3

3. 属性与方法:几乎一切值都带「抽屉」

属性(property) 是挂在值上的东西:"text".lengthMath.max 都是在取属性。取法两种,含义不同:

  • 点号 value.x:x 是字面的属性名;
  • 方括号 value[x]:括号里是表达式,先求值,把结果(转成字符串)当属性名4

例外只有两个:null 和 undefined 没有属性,取了就报 TypeError5

属性里装着函数时,它有个专名叫方法(method),如 toUpperCase、数组的 push(尾加)与 pop(尾取并返回)。push/pop 这对名字来自「栈」——后进先出的结构,正是上一章调用栈的那个「栈」6

4. 对象:一只有名字触手的章鱼

数组适合「一排同类的东西」;生活记录的一条却不同:一个活动列表 + 一个变没变。把它们捆成一个值,用对象(object):

let day1 = {
squirrel: false,
events: ["work", "touched tree", "pizza", "running"]
};
day1.wolf; // → undefined(没有的属性读出来就是 undefined)
day1.wolf = false; // 赋值即创建

延续触手比喻:对象是一只有很多触手的章鱼,每条触手上写着名字;delete 剪掉一条触手;in 问「有没有这条触手」。把属性设成 undefined 和 delete 不一样:前者触手还在、只是抓了个空,in 仍为 true7

花括号在 JS 里有两义:语句开头是语句块,其他位置是对象。由此有个著名的坑:箭头函数直接返回对象要加括号——n => ({prop: n}),否则花括号被当成函数体8

配套小工具:Object.keys 列出属性名;Object.assign 把一个对象的属性抄进另一个9。而数组呢?数组就是对象——typeof [] 是 "object",只不过它的触手按数字排成一排。书里的画法:「长而扁的章鱼」10

5. 可变性与身份:本章的理论核心

字符串、数字、布尔是不可变的:"cat" 的任何代码都改不成 "rat",你只能造新字符串。对象不同:同一个对象可以在不同时刻有不同内容11

于是「相等」裂成两个概念12:

let object1 = {value: 10};
let object2 = object1; // 两条触手抓同一只章鱼
let object3 = {value: 10}; // 另一只内容相同的章鱼

object1 == object2 // → true 同一个东西
object1 == object3 // → false 内容相同也不是它

object1.value = 15;
object2.value; // → 15(跟着变)
object3.value; // → 10(各过各的)

JS 的 == 对对象只比身份、不比内容,也没有内建的「深比较」(想比内容得自己写,这是章末练习)13。const 与此是两回事:const 锁的是触手不换目标,章鱼的内容照样能改——const score = {visitors: 0}; score.visitors = 1 合法,score = {} 报错14

6. 主走查:90 天记录揪出花生

现在做正事。那本记录是对象数组:journal 里每条是 {events: […], squirrel: true/false}15

问题:哪个活动与「变松鼠」相关?书里请出统计学里度量两个布尔变量关联的 phi 系数(φ):把所有日子按「变没变 × 做没做某事」数进 2×2 表,公式输出 -1 到 1,0 表示无关16

第一步,把「吃披萨」的四格表数出来,压扁成四元素数组 [76, 9, 4, 1](下标是两位二进制:松鼠位在左、事件位在右)。算 φ:

φ = (76×1 − 4×9) / √(5 × 85 × 10 × 80)
= (76 − 36) / √340000
≈ 0.069 ← 趋近 0:吃披萨与变身无关

第二步,对每个事件都算一遍。遍历记录、累加四格的函数 tableFor 里,四格表的下标计算是本章走查的核心一步:某天没做该事件、也没变松鼠 → index 0;做了事件 → +1;变了松鼠 → +2;两者都发生 → index 3。90 天各归各格17

全事件相关性算完,筛掉接近 0 的,只剩几个显眼的:peanuts 0.59(强正相关)、brushed teeth −0.38(强负相关)18

第三步,作者的神来之笔——造一个组合事件「peanut teeth」:吃了花生没刷牙的日子:

phi(tableFor("peanut teeth", JOURNAL));
// → 1

完美相关。 真相是:Jacques 吃了花生又不刷牙才会变身。他戒掉花生,变身停止;故事的结尾,他索性在树上盖了小屋、备好十年花生酱,选择当一只全职松鼠——全书最著名的幽默结局19

回看这条链:问题 → 表示(对象数组)→ 遍历累加(四格表)→ 公式(φ)→ 过滤 → 组合特征(把两个信号并成一个新信号)。 它就是一次完整的数据分析,而每一步都建立在本章前半的容器上。

7. 工具箱:数组与字符串的常用武器

工具干什么怎么记
push / pop尾加 / 尾取
unshift / shift头加 / 头取组成任务队列:remember 排队,getTask 取最前20
indexOf / lastIndexOf找位置,找不到给 -1可带第二个参数定起点21
slice(start, end)切一片,含头不含尾不给 end 就切到底;两个都省=复制整个数组22
concat拼接出新数组与 slice 组合可实现「删一个元素但不改原数组」23

字符串有一批同名同义的:slice、indexOf(而且能找多字符)、trim(去两端空白)、split / join(按指定的分隔串拆开/粘回)、repeat(重复)、padStart(补齐到定长——上一章手写的 zeroPad 就是它)24。但原始类型存不了自定义属性:kim.age = 88 不报错也不生效,再读是 undefined25

三件现代语法糖:rest 参数(function max(...numbers) 把余下参数收成数组,书里用 -Infinity 当起点写出 max);展开(max(...numbers) 把数组拆回参数;对象字面量里 {...coordinates, y: 5} 抄属性,后者覆盖前者);解构(function phi([n00, n01, n10, n11]) 直接在参数位拆数组——同一章的 φ 函数立刻用它重写了一遍;let {name} = obj 拆对象)26

两个零碎:Math.random() 每次给一个 0 到 1 之间的伪随机数(机器存着隐藏值,每次对它做复杂运算派生新数);a?.b 可选链——a 是 null/undefined 时静默得 undefined 而不是报错27

8. JSON:把地址的纠缠压扁

对象和数组在内存里是地址的纠缠:数组套数组,外壳里存的其实是内壳的位置。想存进文件或发到另一台机器,就得序列化(serialization)——转成一份扁平描述28

最流行的格式是 JSON——JavaScript Object Notation,一种数据交换文本格式,长得像 JS 的对象字面量,但有纪律:属性名必须双引号;不许函数、不许计算、不许注释——只能装「数据」29JSON.stringify 把值变成 JSON 字符串,JSON.parse 反向变回值。这一对函数会在第 13 章(浏览器本地存储)和第 14 章(后端)里反复出现。

9. 作者的判断与证据

  • 「数组就是对象」:类型系统的实况(typeof [] 为 "object"),可当场验证10;
  • 「没有内建深比较」:语言事实,书里把「写一个 deepEqual」留成练习13;
  • 「phi 系数适合两个布尔变量」:统计学通例,书里现场算了披萨表的 0.069 做演示;
  • 「数学恐惧者的安慰」:作者在公式前特意安抚读者——「我不打算用密密麻麻的记号折磨你,就这一个公式,而且我们马上把它翻译成 JavaScript」30

判断(我们的,不是书里的): 「造组合事件 peanut teeth」这一步是全章最值得带走的动作—— 它演示了特征构造:两个弱信号组合成一个强信号。数据不足时,先想「有没有该组合而没组合的维度(还能并进来的方面)」。 如果错,会错在: 组合维度是撞大运的搜索,组合多了总会撞出假相关(多重比较问题);书里是 2 个显眼事件才组合,若是 100 个事件两两组合,出现 φ≈1 的巧合并不稀奇。

10. 边界与局限

  • 书里的 phi 计算不含「修正多重比较」的统计学讨论(见上面的判断块);
  • 链表、深比较都只在练习里出现,正文不展开;
  • SetWeakMap 等结构仅一笔带过(Set 在第 06 章练习里出场);
  • JSON 的日期、循环引用等实战坑,书里没讲。

11. 可带走的

  1. 容器跟着问题选:一排同类用数组,一组有名目的用对象,那本记录=对象数组;
  2. 下标=要跳过的个数;value[x] 先算括号再取属性,value.x 是字面名;
  3. 对象=有名字的章鱼;in 查属性,置 undefined ≠ delete;
  4. == 比身份不比内容;const 锁触手不锁章鱼;
  5. 可变是对象的特权,也是 Bug 温床——后面章节(机器人、像素编辑器)会反复选边站;
  6. 主走查链条:对象数组 → 四格表 → φ → 过滤 → 组合特征 → φ=1;
  7. slice 含头不含尾;indexOf 找不到给 -1;push/pop 是栈,unshift/shift 是队列;
  8. rest 收、展开撒、解构拆——三兄弟一个符号「…」两副面孔加一对括号;
  9. JSON 是数据出行的唯一行李箱:无函数、无计算、无注释;
  10. kim.age = 88 静默失效——原始类型不存自定义属性。

12. 原文地图

主题原书章原文位置
Jacques 的记录计划Data Structures: Objects and Arraystext/07-fm-data-structures-objects-and-arrays.txt:25(搜「daily log」)
数组与下标=跳过的个数同上text/07-fm-data-structures-objects-and-arrays.txt:37(搜「listOfNumbers」) · :47(搜「items to skip」)
点号 vs 方括号同上text/07-fm-data-structures-objects-and-arrays.txt:58(搜「literal name」)
null 没有属性同上text/07-fm-data-structures-objects-and-arrays.txt:56(搜「TypeError」)
方法与栈的出处同上text/07-fm-data-structures-objects-and-arrays.txt:80(搜「methods」) · :96(搜「Stacks」)
对象、章鱼比喻同上text/07-fm-data-structures-objects-and-arrays.txt:104(搜「day1」) · :129(搜「octopuses」)
花括号两义、箭头陷阱同上text/07-fm-data-structures-objects-and-arrays.txt:123(搜「two meanings」)
delete 与 in同上text/07-fm-data-structures-objects-and-arrays.txt:131(搜「delete」)
Object.keys / assign同上text/07-fm-data-structures-objects-and-arrays.txt:148(搜「Object.keys」)
数组就是对象同上text/07-fm-data-structures-objects-and-arrays.txt:158(搜「typeof」)
不可变与身份同上text/07-fm-data-structures-objects-and-arrays.txt:179(搜「immutable」) · :185(搜「object1」)
== 不比内容同上text/07-fm-data-structures-objects-and-arrays.txt:210(搜「identity」)
const 锁指向同上text/07-fm-data-structures-objects-and-arrays.txt:204(搜「score」)
phi 公式与「不折磨你」同上text/07-fm-data-structures-objects-and-arrays.txt:237(搜「frequency table」) · :247(搜「10th grade math」)
披萨表 0.069同上text/07-fm-data-structures-objects-and-arrays.txt:253(搜「0.069」)
四格表压成数组同上text/07-fm-data-structures-objects-and-arrays.txt:257(搜「76, 9, 4, 1」)
tableFor 的 index 计算同上text/07-fm-data-structures-objects-and-arrays.txt:278(搜「tableFor」)
相关性筛选与花生同上text/07-fm-data-structures-objects-and-arrays.txt:354(搜「0.1」) · :366(搜「peanuts」)
peanut teeth = 1同上text/07-fm-data-structures-objects-and-arrays.txt:373(搜「peanut teeth」) · :377(搜「1」)
全职松鼠结局同上text/07-fm-data-structures-objects-and-arrays.txt:383(搜「tree house」)
队列与 indexOf/slice/concat同上text/07-fm-data-structures-objects-and-arrays.txt:402(搜「queue」) · :415(搜「slice(2, 4)」) · :426(搜「remove」)
字符串方法与 kim.age同上text/07-fm-data-structures-objects-and-arrays.txt:439(搜「kim」) · :448(搜「coconuts」) · :465(搜「padStart」)
rest 与展开同上text/07-fm-data-structures-objects-and-arrays.txt:494(搜「max(...」) · :522(搜「coordinates」)
Math 对象与随机同上text/07-fm-data-structures-objects-and-arrays.txt:530(搜「namespace」) · :557(搜「pseudorandom」)
解构重写 phi同上text/07-fm-data-structures-objects-and-arrays.txt:582(搜「n00」)
可选链同上text/07-fm-data-structures-objects-and-arrays.txt:600(搜「?.」)
JSON同上text/07-fm-data-structures-objects-and-arrays.txt:621(搜「addresses」) · :627(搜「restrictions」) · :638(搜「stringify」)

Footnotes

  1. 出处:「Data Structures: Objects and Arrays」第 25 段(text/07-fm-data-structures-objects-and-arrays.txt:25,搜「daily log」)。

  2. 出处:「Data Structures: Objects and Arrays」第 35 段(text/07-fm-data-structures-objects-and-arrays.txt:35,搜「square brackets」)。

  3. 出处:「Data Structures: Objects and Arrays」第 47 段(text/07-fm-data-structures-objects-and-arrays.txt:47,搜「items to skip」)。

  4. 出处:「Data Structures: Objects and Arrays」第 58 段(text/07-fm-data-structures-objects-and-arrays.txt:58,搜「literal name」)。

  5. 出处:「Data Structures: Objects and Arrays」第 56 段(text/07-fm-data-structures-objects-and-arrays.txt:56,搜「TypeError」)。

  6. 出处:「Data Structures: Objects and Arrays」第 80 段(text/07-fm-data-structures-objects-and-arrays.txt:80,搜「methods」)与第 96 段(text/07-fm-data-structures-objects-and-arrays.txt:96,搜「Stacks」)。

  7. 出处:「Data Structures: Objects and Arrays」第 129 段(text/07-fm-data-structures-objects-and-arrays.txt:129,搜「octopuses」)与第 131 段(text/07-fm-data-structures-objects-and-arrays.txt:131,搜「delete」)。

  8. 出处:「Data Structures: Objects and Arrays」第 123 段(text/07-fm-data-structures-objects-and-arrays.txt:123,搜「two meanings」)。

  9. 出处:「Data Structures: Objects and Arrays」第 146 段(text/07-fm-data-structures-objects-and-arrays.txt:146,搜「Object.keys」)。

  10. 出处:「Data Structures: Objects and Arrays」第 158 段(text/07-fm-data-structures-objects-and-arrays.txt:158,搜「long, flat octopuses」)。 2

  11. 出处:「Data Structures: Objects and Arrays」第 179 段(text/07-fm-data-structures-objects-and-arrays.txt:179,搜「immutable」)。

  12. 出处:「Data Structures: Objects and Arrays」第 185 段(text/07-fm-data-structures-objects-and-arrays.txt:185,搜「object1」)。

  13. 出处:「Data Structures: Objects and Arrays」第 210 段(text/07-fm-data-structures-objects-and-arrays.txt:210,搜「identity」)。 2

  14. 出处:「Data Structures: Objects and Arrays」第 204 段(text/07-fm-data-structures-objects-and-arrays.txt:204,搜「score」)。

  15. 出处:「Data Structures: Objects and Arrays」第 162 段(text/07-fm-data-structures-objects-and-arrays.txt:162,搜「journal」)。

  16. 出处:「Data Structures: Objects and Arrays」第 235 段(text/07-fm-data-structures-objects-and-arrays.txt:235,搜「Correlation」)与第 237 段(text/07-fm-data-structures-objects-and-arrays.txt:237,搜「frequency table」)。

  17. 出处:「Data Structures: Objects and Arrays」第 278 段(text/07-fm-data-structures-objects-and-arrays.txt:278,搜「tableFor」)与第 294 段(text/07-fm-data-structures-objects-and-arrays.txt:294,搜「which box」)。

  18. 出处:「Data Structures: Objects and Arrays」第 167 段(text/07-fm-data-structures-objects-and-arrays.txt:167,搜「brushed teeth」)与第 366 段(text/07-fm-data-structures-objects-and-arrays.txt:366,搜「peanuts」)。

  19. 出处:「Data Structures: Objects and Arrays」第 373 段(text/07-fm-data-structures-objects-and-arrays.txt:373,搜「peanut teeth」)与第 383 段(text/07-fm-data-structures-objects-and-arrays.txt:383,搜「tree house」)。

  20. 出处:「Data Structures: Objects and Arrays」第 391 段(text/07-fm-data-structures-objects-and-arrays.txt:391,搜「todoList」)与第 402 段(text/07-fm-data-structures-objects-and-arrays.txt:402,搜「queue」)。

  21. 出处:「Data Structures: Objects and Arrays」第 404 段(text/07-fm-data-structures-objects-and-arrays.txt:404,搜「indexOf」)。

  22. 出处:「Data Structures: Objects and Arrays」第 413 段(text/07-fm-data-structures-objects-and-arrays.txt:413,搜「inclusive」)。

  23. 出处:「Data Structures: Objects and Arrays」第 426 段(text/07-fm-data-structures-objects-and-arrays.txt:426,搜「remove」)。

  24. 出处:「Data Structures: Objects and Arrays」第 448 段(text/07-fm-data-structures-objects-and-arrays.txt:448,搜「coconuts」)、第 460 段(text/07-fm-data-structures-objects-and-arrays.txt:460,搜「trim」)、第 463 段(text/07-fm-data-structures-objects-and-arrays.txt:463,搜「padStart」)、第 468 段(text/07-fm-data-structures-objects-and-arrays.txt:468,搜「split」)与第 479 段(text/07-fm-data-structures-objects-and-arrays.txt:479,搜「repeat」)。

  25. 出处:「Data Structures: Objects and Arrays」第 439 段(text/07-fm-data-structures-objects-and-arrays.txt:439,搜「kim」)。

  26. 出处:「Data Structures: Objects and Arrays」第 494 段(text/07-fm-data-structures-objects-and-arrays.txt:494,搜「...numbers」)、第 512 段(text/07-fm-data-structures-objects-and-arrays.txt:512,搜「spreads」)、第 522 段(text/07-fm-data-structures-objects-and-arrays.txt:522,搜「coordinates」)与第 582 段(text/07-fm-data-structures-objects-and-arrays.txt:582,搜「n00」)。

  27. 出处:「Data Structures: Objects and Arrays」第 548 段(text/07-fm-data-structures-objects-and-arrays.txt:548,搜「pseudorandom」)与第 600 段(text/07-fm-data-structures-objects-and-arrays.txt:600,搜「?.」)。

  28. 出处:「Data Structures: Objects and Arrays」第 621 段(text/07-fm-data-structures-objects-and-arrays.txt:621,搜「addresses」)。

  29. 出处:「Data Structures: Objects and Arrays」第 627 段(text/07-fm-data-structures-objects-and-arrays.txt:627,搜「restrictions」)。

  30. 出处:「Data Structures: Objects and Arrays」第 247 段(text/07-fm-data-structures-objects-and-arrays.txt:247,搜「10th grade math」)。