## 四、数据结构：对象和数组

On two occasions I have been asked, ‘Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ [...] I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

Charles Babbage，《Passages from the Life of a Philosopher》（1864）

## 数据集

``````let listOfNumbers = [2, 3, 5, 7, 11];
console.log(listOfNumbers[2]);
// → 5
console.log(listOfNumbers[0]);
// → 2
console.log(listOfNumbers[2 - 1]);
// → 3
``````

## 属性

``````null.length;
// → TypeError: null has no properties
``````

## 方法

``````let doh = "Doh";
console.log(typeof doh.toUpperCase);
// → function
console.log(doh.toUpperCase());
// → DOH
``````

``````let sequence = [1, 2, 3];
sequence.push(4);
sequence.push(5);
console.log(sequence);
// → [1, 2, 3, 4, 5]
console.log(sequence.pop());
// → 5
console.log(sequence);
// → [1, 2, 3, 4]
``````

`push`方法将值添加到数组的末尾，而`pop`方法则相反，删除数组中的最后一个值并将其返回。

## 对象

``````let day1 = {
squirrel: false,
events: ["work", "touched tree", "pizza", "running"]
};
console.log(day1.squirrel);
// → false
console.log(day1.wolf);
// → undefined
day1.wolf = false;
console.log(day1.wolf);
// → false
``````

``````let descriptions = {
work: "Went to work",
"touched tree": "Touched a tree"
};
``````

`delete`运算符切断章鱼的触手。 这是一个一元运算符，当应用于对象属性时，将从对象中删除指定的属性。 这不是一件常见的事情，但它是可能的。

``````let anObject = {left: 1, right: 2};
console.log(anObject.left);
// → 1
delete anObject.left;
console.log(anObject.left);
// → undefined
console.log("left" in anObject);
// → false
console.log("right" in anObject);
// → true
``````

``````console.log(Object.keys({x: 0, y: 0, z: 2}));
// → ["x", "y", "z"]
``````

`Object.assign`函数可以将一个对象的所有属性复制到另一个对象中。

``````let objectA = {a: 1, b: 2};
Object.assign(objectA, {b: 3, c: 4});
console.log(objectA);
// → {a: 1, b: 3, c: 4}
``````

``````let journal = [
{events: ["work", "touched tree", "pizza",
"running", "television"],
squirrel: false},
{events: ["work", "ice cream", "cauliflower",
"lasagna", "touched tree", "brushed teeth"],
squirrel: false},
{events: ["weekend", "cycling", "break", "peanuts",
"beer"],
squirrel: true},
/* and so on... */
];
``````

## 可变性

``````let object1 = {value: 10};
let object2 = object1;
let object3 = {value: 10};

console.log(object1 == object2);
// → true
console.log(object1 == object3);
// → false

object1.value = 15;
console.log(object2.value);
// → 15
console.log(object3.value);
// → 10
``````

`object1``object2`绑定持有相同对象，这就是为什么改变`object1`会改变`object2`的值。 据说他们具有相同的身份。 绑定`object3`指向一个不同的对象，它最初包含的属性与`object1`相同，但过着单独的生活。

``````const score = {visitors: 0, home: 0};
// This is okay
score.visitors = 1;
// This isn't allowed
score = {visitors: 1, home: 1};
``````

## 松鼠人的记录

``````let journal = [];

function addEntry(events, squirrel) {
journal.push({events, squirrel});
}
``````

``````addEntry(["work", "touched tree", "pizza", "running",
"television"], false);
addEntry(["work", "ice cream", "cauliflower", "lasagna",
"touched tree", "brushed teeth"], false);
addEntry(["weekend", "cycling", "break", "peanuts",
"beer"], true);
``````

（如果你现在把这本书放下，专注于十年级数学课的可怕的再现，坚持住！我不打算用无休止的神秘符号折磨你 - 现在只有这一个公式。我们所做的就是把它变成 JavaScript。）

`n1`表示所有度量之和，其中第一个绑定为`true`，在示例表中为 5。 同样，`n0`表示所有度量之和，其中第二个绑定为假。

## 计算关联性

``````function phi(table) {
return (table[3] * table[0] - table[2] * table[1]) /
Math.sqrt((table[2] + table[3]) *
(table[0] + table[1]) *
(table[1] + table[3]) *
(table[0] + table[2]));
}

console.log(phi([76, 9, 4, 1]));
// → 0.068599434
``````

``````function hasEvent(event, entry) {
return entry.events.indexOf(event) != -1;
}

function tableFor(event, journal) {
let table = [0, 0, 0, 0];
for (let i = 0; i < journal.length; i++) {
let entry = journal[i], index = 0;
if (entry.events.includes(event)) index += 1;
if (entry.squirrel) index += 2;
table[index] += 1;
}
return table;
}

console.log(tableFor("pizza", JOURNAL));
// → [76, 9, 4, 1]
``````

`tableFor`中的循环体通过检查列表是否包含它感兴趣的特定事件，以及该事件是否与松鼠事件一起发生，来计算每个日记条目在表格中的哪个盒子。 然后循环对表中的正确盒子加一。

## 数组循环

`tableFor`函数中，有一个这样的循环：

``````for (let i = 0; i < JOURNAL.length; i++) {
let entry = JOURNAL[i];
// Do something with entry
}
``````

``````for (let entry of JOURNAL) {
console.log(`\${entry.events.length} events.`);
}
``````

`for`循环看起来像这样，在绑定定义之后用`of`这个词时，它会遍历`of`之后的给定值的元素。 这不仅适用于数组，而且适用于字符串和其他数据结构。 我们将在第 6 章中讨论它的工作原理。

## 分析结果

``````function journalEvents(journal) {
let events = [];
for (let entry of journal) {
for (let event of entry.events) {
if (!events.includes(event)) {
events.push(event);
}
}
}
return events;
}

console.log(journalEvents(JOURNAL));
// → ["carrot", "exercise", "weekend", "bread", …]
``````

``````for (let event of journalEvents(JOURNAL)) {
console.log(event + ":", phi(tableFor(event, JOURNAL)));
}
// → carrot:   0.0140970969
// → exercise: 0.0685994341
// → weekend:  0.1371988681
// → bread:   -0.0757554019
// → pudding: -0.0648203724
// and so on...
``````

``````for (let event of journalEvents(JOURNAL)) {
let correlation = phi(tableFor(event, JOURNAL));
if (correlation > 0.1 || correlation < -0.1) {
console.log(event + ":", correlation);
}
}
// → weekend:        0.1371988681
// → brushed teeth: -0.3805211953
// → candy:          0.1296407447
// → work:          -0.1371988681
// → spaghetti:      0.2425356250
// → reading:        0.1106828054
// → peanuts:        0.5902679812
``````

``````for (let entry of JOURNAL) {
if (entry.events.includes("peanuts") &&
!entry.events.includes("brushed teeth")) {
entry.events.push("peanut teeth");
}
}
console.log(phi(tableFor("peanut teeth", JOURNAL)));
// → 1
``````

## 数组详解

``````let todoList = [];
}
}
}
``````

``````console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3
``````

`indexOf``lastIndexOf`方法都有一个可选参数，可以用来指定搜索的起始位置。

``````console.log([0, 1, 2, 3, 4].slice(2, 4));
// → [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// → [2, 3, 4]
``````

`concat`方法可用于将数组粘在一起，来创建一个新数组，类似于`+`运算符对字符串所做的操作。

``````function remove(array, index) {
return array.slice(0, index)
.concat(array.slice(index + 1));
}
console.log(remove(["a", "b", "c", "d", "e"], 2));
// → ["a", "b", "d", "e"]
``````

## 字符串及其属性

``````let kim = "Kim";
kim.age = 88;
console.log(kim.age);
// → undefined
``````

``````console.log("coconuts".slice(4, 7));
// → nut
console.log("coconut".indexOf("u"));
// → 5
``````

``````console.log("one two three".indexOf("ee"));
// → 11
``````

`trim`方法用于删除字符串中开头和结尾的空白符号（空格、换行符和制表符等符号）。

``````console.log("  okay \n ".trim());
// → okay
``````

``````console.log(String(6).padStart(3, "0"));
// → 006
``````

``````let sentence = "Secretarybirds specialize in stomping";
let words = sentence.split(" ");
console.log(words);
// → ["Secretarybirds", "specialize", "in", "stomping"]
console.log(words.join(". "));
// → Secretarybirds. specialize. in. stomping
``````

``````console.log("LA".repeat(3));
// → LALALA
``````

``````let string = "abc";
console.log(string.length);
// → 3
console.log(string[1]);
// → b
``````

## 剩余参数

``````function max(...numbers) {
let result = -Infinity;
for (let number of numbers) {
if (number > result) result = number;
}
return result;
}
console.log(max(4, 1, 9, -2));
// → 9
``````

``````let numbers = [5, 1, 7];
console.log(max(...numbers));
// → 7
``````

``````let words = ["never", "fully"];
console.log(["will", ...words, "understand"]);
// → ["will", "never", "fully", "understand"]
``````

## Math对象

`Math`对象被用作一个容器来分组一堆相关的功能。 只有一个`Math`对象，它作为一个值几乎没有用处。 相反，它提供了一个命名空间，使所有这些函数和值不必是全局绑定。

``````function randomPointOnCircle(radius) {
let angle = Math.random() * 2 * Math.PI;
return {x: radius * Math.cos(angle),
y: radius * Math.sin(angle)};
}
console.log(randomPointOnCircle(2));
// → {x: 0.3667, y: 1.966}
``````

``````console.log(Math.random());
// → 0.36993729369714856
console.log(Math.random());
// → 0.727367032552138
console.log(Math.random());
// → 0.40180766698904335
``````

``````console.log(Math.floor(Math.random() * 10));
// → 2
``````

## 解构

``````function phi(table) {
return (table[3] * table[0] - table[2] * table[1]) /
Math.sqrt((table[2] + table[3]) *
(table[0] + table[1]) *
(table[1] + table[3]) *
(table[0] + table[2]));
}
``````

``````function phi([n00, n01, n10, n11]) {
return (n11 * n00 - n10 * n01) /
Math.sqrt((n10 + n11) * (n00 + n01) *
(n01 + n11) * (n00 + n10));
}
``````

``````let {name} = {name: "Faraji", age: 23};
console.log(name);
// → Faraji
``````

## JSON

JSON 看起来像 JavaScript 的数组和对象的表示方式，但有一些限制。 所有属性名都必须用双引号括起来，并且只允许使用简单的数据表达式 - 没有函数调用，绑定或任何涉及实际计算的内容。 JSON 中不允许注释。

``````{
"squirrel": false,
"events": ["work", "touched tree", "pizza", "running"]
}
``````

JavaScript 为我们提供了函数`JSON.stringify``JSON.parse`，来将数据转换为这种格式，以及从这种格式转换。 第一个函数接受 JavaScript 值并返回 JSON 编码的字符串。 第二个函数接受这样的字符串并将其转换为它编码的值。

``````let string = JSON.stringify({squirrel: false,
events: ["weekend"]});
console.log(string);
// → {"squirrel":false,"events":["weekend"]}
console.log(JSON.parse(string).events);
// → ["weekend"]
``````

## 习题

### 范围的和

``````console.log(sum(range(1, 10)));
``````

``````// Your code here.

console.log(range(1, 10));
// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(5, 2, -1));
// → [5, 4, 3, 2]
console.log(sum(range(1, 10)));
// → 55
``````

### 逆转数组

``````// Your code here.

console.log(reverseArray(["A", "B", "C"]));
// → ["C", "B", "A"];
let arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]
``````

### 实现列表

``````let list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
``````

``````// Your code here.

console.log(arrayToList([10, 20]));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(listToArray(arrayToList([10, 20, 30])));
// → [10, 20, 30]
console.log(prepend(10, prepend(20, null)));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(nth(arrayToList([10, 20, 30]), 1));
// → 20
``````

### 深层比较

`==`运算符可以判断对象是否相等。但有些时候，你希望比较的是对象中实际属性的值。

``````// Your code here.

let obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → true
``````