1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-24 04:22:20 +00:00
This commit is contained in:
wizardforcel 2018-05-01 21:25:14 +08:00
parent eb03391929
commit 505443b4f5

122
4.md
View File

@ -254,10 +254,10 @@ addEntry(["weekend", "cycling", "break", "peanuts",
一旦他有了足够的数据点,他打算使用统计学来找出哪些事件可能与变成松鼠有关。 一旦他有了足够的数据点,他打算使用统计学来找出哪些事件可能与变成松鼠有关。
关联性是统计变量之间的独立性的度量。 统计变量与编程变量不完全相同。 在统计学中,您通常会有一组度量,并且每个变量都根据每个度量来测量。 变量之间的相关性通常表示为从 -1 到 1 的值。 相关性为零意味着变量不相关。 相关性为一表明两者完全相关 - 如果你知道一个,你也知道另一个。 负一意味着它们是完全相关的,但它们是相反的 - 当一个是真的时,另一个是假的。 关联性是统计绑定之间的独立性的度量。 统计绑定与编程绑定不完全相同。 在统计学中,您通常会有一组度量,并且每个绑定都根据每个度量来测量。 绑定之间的相关性通常表示为从 -1 到 1 的值。 相关性为零意味着绑定不相关。 相关性为一表明两者完全相关 - 如果你知道一个,你也知道另一个。 负一意味着它们是完全相关的,但它们是相反的 - 当一个是真的时,另一个是假的。
为了计算两个布尔变量之间的相关性度量,我们可以使用 phi 系数(`φ`)。 这是一个公式,输入为一个频率表格,包含观测变量的不同组合的次数。 公式的输出是 -1 和 1 之间的数字。 为了计算两个布尔绑定之间的相关性度量,我们可以使用 phi 系数(`φ`)。 这是一个公式,输入为一个频率表格,包含观测绑定的不同组合的次数。 公式的输出是 -1 和 1 之间的数字。
我们可以将吃比萨的事件放在这样的频率表中,每个数字表示我们的度量中的组合的出现次数。 我们可以将吃比萨的事件放在这样的频率表中,每个数字表示我们的度量中的组合的出现次数。
@ -269,15 +269,15 @@ addEntry(["weekend", "cycling", "break", "peanuts",
(如果你现在把这本书放下,专注于十年级数学课的可怕的再现,坚持住!我不打算用无休止的神秘符号折磨你 - 现在只有这一个公式。我们所做的就是把它变成 JavaScript。 (如果你现在把这本书放下,专注于十年级数学课的可怕的再现,坚持住!我不打算用无休止的神秘符号折磨你 - 现在只有这一个公式。我们所做的就是把它变成 JavaScript。
符号_n_lt; subgt; 01lt; / subgt;表明, 第一个变量松鼠为假0第二个变量披萨为真1。 在披萨表中_n_lt; subgt; 01lt; / subgt; 是 9。 符号_n_lt; subgt; 01lt; / subgt;表明, 第一个绑定松鼠为假0第二个绑定披萨为真1。 在披萨表中_n_lt; subgt; 01lt; / subgt; 是 9。
值_n_lt; subgt; 1lt; / subgt; 表示所有度量之和,其中第一个变量为true在示例表中为5。 同样_n_lt; subgt; 0lt; / subgt; 表示所有度量之和,其中第二个变量为假。 值_n_lt; subgt; 1lt; / subgt; 表示所有度量之和,其中第一个绑定为true在示例表中为5。 同样_n_lt; subgt; 0lt; / subgt; 表示所有度量之和,其中第二个绑定为假。
因此我们以比萨表为例除法线上方的部分被除数为1×769×4=40而除法线下面的部分除数则是10×80×5×85的平方根也就是![](../Images/00127.jpeg)。计算结果`ϕ`≈0.069,这个结果很小,因此吃比萨对是否变身成松鼠显然没有太大影响。 因此我们以比萨表为例除法线上方的部分被除数为1×769×4=40而除法线下面的部分除数则是10×80×5×85的平方根也就是![](../Images/00127.jpeg)。计算结果`ϕ`≈0.069,这个结果很小,因此吃比萨对是否变身成松鼠显然没有太大影响。
### 计算关联性 ### 计算关联性
我们可以用包含 4 个元素的数组(`[76941]`)来表示一张 2 乘 2 的表格。我们也可以使用其他表示方式,比如包含两个数组的数组,每个子数组又包含两个元素(`[[769][41]]`)。也可以使用一个对象,它包含一些属性,名为`"11"``"01"`。但是,一维数组更为简单,也容易进行操作。我们可以将数组索引看成包含两个二进制位的数字,左边的(高位)数字表示变量“是否变成松鼠”,右边的(低位)数字表示事件变量。例如,若二进制数字为 10表示雅克变成了松鼠但事件并未发生比如说吃比萨。这种情况发生了 4 次。由于二进制数字 10 的十进制是 2因此我们将其存储到数组中索引为 2 的位置上。 我们可以用包含 4 个元素的数组(`[76941]`)来表示一张 2 乘 2 的表格。我们也可以使用其他表示方式,比如包含两个数组的数组,每个子数组又包含两个元素(`[[769][41]]`)。也可以使用一个对象,它包含一些属性,名为`"11"``"01"`。但是,一维数组更为简单,也容易进行操作。我们可以将数组索引看成包含两个二进制位的数字,左边的(高位)数字表示绑定“是否变成松鼠”,右边的(低位)数字表示事件绑定。例如,若二进制数字为 10表示雅克变成了松鼠但事件并未发生比如说吃比萨。这种情况发生了 4 次。由于二进制数字 10 的十进制是 2因此我们将其存储到数组中索引为 2 的位置上。
下面这个函数用于计算数组的系数`ϕ` 下面这个函数用于计算数组的系数`ϕ`
@ -296,7 +296,7 @@ console.log(phi([76, 9, 4, 1]));
这将`φ`公式直接翻译成 JavaScript。 `Math.sqrt`是平方根函数,由标准 JavaScript 环境中的`Math`对象提供。 我们必须在表格中添加两个字段来获取字段,例如`n1`因为行和或者列和不直接存储在我们的数据结构中。 这将`φ`公式直接翻译成 JavaScript。 `Math.sqrt`是平方根函数,由标准 JavaScript 环境中的`Math`对象提供。 我们必须在表格中添加两个字段来获取字段,例如`n1`因为行和或者列和不直接存储在我们的数据结构中。
雅克花了三个月的时间记录日志。在本章的代码沙箱([http://eloquentjavascript.net/code/](http://eloquentjavascript.net/code/))的下载文件中,用`JOURNAL`变量存储了该结果数据集合。 雅克花了三个月的时间记录日志。在本章的代码沙箱([http://eloquentjavascript.net/code/](http://eloquentjavascript.net/code/))的下载文件中,用`JOURNAL`绑定存储了该结果数据集合。
若要从这篇记录中提取出某个特定事件的2乘2表格我们首先需要循环遍历整个记录并计算出与变身成松鼠相关事件发生的次数。 若要从这篇记录中提取出某个特定事件的2乘2表格我们首先需要循环遍历整个记录并计算出与变身成松鼠相关事件发生的次数。
@ -347,7 +347,7 @@ for (let entry of JOURNAL) {
} }
``` ```
`for`循环看起来像这样,在变量定义之后用`of`这个词时,它会遍历`of`之后的给定值的元素。 这不仅适用于数组,而且适用于字符串和其他数据结构。 我们将在第 6 章中讨论它的工作原理。 `for`循环看起来像这样,在绑定定义之后用`of`这个词时,它会遍历`of`之后的给定值的元素。 这不仅适用于数组,而且适用于字符串和其他数据结构。 我们将在第 6 章中讨论它的工作原理。
### 分析结果 ### 分析结果
@ -596,17 +596,17 @@ console.log(["will", ...words, "understand"]);
正如我们所看到的那样Math对象中包含了许多与数字相关的工具函数比如Math.max求最大值、Math.min求最小值和Math.sqrt求平方根 正如我们所看到的那样Math对象中包含了许多与数字相关的工具函数比如Math.max求最大值、Math.min求最小值和Math.sqrt求平方根
Math对象简单地把一组相关的功能打包成一个对象供用户使用。全局只有一个Math对象其对象本身没有什么实际用途。Math对象其实提供了一个“命名空间”封装了所有的数学运算函数和值确保这些元素不会变成全局变量 `Math`对象被用作一个容器来分组一堆相关的功能。 只有一个`Math`对象,它作为一个值几乎没有用处。 相反,它提供了一个命名空间,使所有这些函数和值不必是全局绑定
过多的全局变量会对命名空间造成“污染”。全局变量越多就越有可能一不小心把某些变量的值覆盖掉。比如我们可能想在程序中使用名为max的变量由于JavaScript将内置的max函数安全地放置在Math对象中因此不必担心max的值会被覆盖。 过多的全局绑定会“污染”命名空间。全局绑定越多就越有可能一不小心把某些绑定的值覆盖掉。比如我们可能想在程序中使用名为max的绑定由于JavaScript将内置的max函数安全地放置在Math对象中因此不必担心max的值会被覆盖。
当你去定义一个已经被使用的变量名的时候对于很多编程语言来说都会阻止你这么做至少会对这种行为发出警告。但是JavaScript不会因此要小心这些陷阱。 当你去定义一个已经被使用的绑定名的时候对于很多编程语言来说都会阻止你这么做至少会对这种行为发出警告。但是JavaScript不会因此要小心这些陷阱。
让我们来继续了解Math对象。如果需要做三角运算Math对象可以帮助到你它包含cos余弦、sin正弦、tan正切和各自的反函数acos、asin和atan。Math.PI则表示数字πpi或至少是JavaScript中的数字近似值在传统的程序设计当中,常量均以大写来标注 让我们来继续了解Math对象。如果需要做三角运算Math对象可以帮助到你它包含cos余弦、sin正弦、tan正切和各自的反函数acos、asin和atan。Math.PI则表示数字πpi或至少是JavaScript中的数字近似值在传统的程序设计当中,常量均以大写来标注。
``` ```
function randomPointOnCircle(radius) { function randomPointOnCircle(radius) {
var angle = Math.random() * 2 * Math.PI; let angle = Math.random() * 2 * Math.PI;
return {x: radius * Math.cos(angle), return {x: radius * Math.cos(angle),
y: radius * Math.sin(angle)}; y: radius * Math.sin(angle)};
} }
@ -627,7 +627,7 @@ console.log(Math.random());
// → 0.40180766698904335 // → 0.40180766698904335
``` ```
虽然计算机的一切行为都是预先设定好的,只要提供相同的输入,就会得到相同的输出结果。但我们仍然可以通过计算机来产生伪随机数。要产生一个随机数,计算机会在其内部状态中维护一个数字(或一组数字)。接着,每当我们要产生一个随机数时,计算机会根据其内部维护的状态,执行一系列复杂的预定义计算过程,然后返回计算的部分结果作为随机数。在返回计算结果的同时,计算机也会根据结果来改变其内部维护的状态,以便下次产生伪随机数的时候产生不同的结果 虽然计算机是确定性的机器,但如果给定相同的输入,它们总是以相同的方式作出反应 - 让它们产生随机显示的数字是可能的。 为此,机器会维护一些隐藏的值,并且每当您请求一个新的随机数时,它都会对该隐藏值执行复杂的计算来创建一个新值。 它存储一个新值并返回从中派生的一些数字。 这样,它可以以随机的方式产生新的,难以预测的数字
如果我们想获取一个随机的整数而非小数可以使用Math.floor向下取整到与当前数字最接近的整数来处理Math.random的结果。 如果我们想获取一个随机的整数而非小数可以使用Math.floor向下取整到与当前数字最接近的整数来处理Math.random的结果。
@ -638,33 +638,87 @@ console.log(Math.floor(Math.random() * 10));
将随机数乘以10可以得到一个在0~10之间的数字。由于Math.floor是向下取整因此该函数会等概率地取到0~9中的任何一个数字。 将随机数乘以10可以得到一个在0~10之间的数字。由于Math.floor是向下取整因此该函数会等概率地取到0~9中的任何一个数字。
还有两个函数分别是Math.ceil向上取整和Math.round四舍五入 还有两个函数分别是Math.ceil向上取整和Math.round四舍五入以及`Math.abs`,它取数字的绝对值,这意味着它反转了负值,但保留了正值。
### 全局对象 ### 解构
JavaScript全局作用域中有许多全局变量都可以通过全局对象进行访问。每一个全局变量作为一个属性存储在全局对象当中。在浏览器中全局对象存储在window变量当中。 让我们暂时回顾`phi`函数:
``` ```
var myVar = 10; function phi(table) {
console.log("myVar" in window); return (table[3] * table[0] - table[2] * table[1]) /
// → true Math.sqrt((table[2] + table[3]) *
console.log(window.myVar); (table[0] + table[1]) *
// → 10 (table[1] + table[3]) *
(table[0] + table[2]));
}
```
这个函数难以阅读的原因之一,是我们有一个指向数组的绑定,但我们更愿意拥有数组的元素的绑定,即`let n00 = table [0]`以及其他。 幸运的是,有一种简洁的方法可以在 JavaScript 中执行此操作。
+```
function phi([n00, n01, n10, n11]) {
return (n11 * n00 - n10 * n01) /
Math.sqrt((n10 + n11) * (n00 + n01) *
(n01 + n11) * (n00 + n10));
}
```
这也适用于由`let``var``const`创建的绑定。 如果您知道要绑定的值是一个数组,则可以使用方括号来“向内查看”该值,并绑定其内容。
类似的技巧适用于对象,使用大括号代替方括号。
```
let {name} = {name: "Faraji", age: 23};
console.log(name);
// → Faraji
```
请注意,如果尝试解构`null``undefined`,则会出现错误,就像直接尝试访问这些值的属性一样。
## JSON
因为属性只是捕获了它们的值,而不是包含它们,对象和数组在计算机的内存中储存为字节序列,存放它们的内容的地址(内存中的位置)。 因此,包含另一个数组的数组,(至少)由两个内存区域组成,一个用于内部数组,另一个用于外部数组,(除了其它东西之外)其中包含表示内部数组位置的二进制数。
如果您想稍后将数据保存到文件中,或者通过网络将其发送到另一台计算机,则必须以某种方式,将这些内存地址的线团转换为可以存储或发送的描述。 我想你应该把你的整个计算机内存,连同你感兴趣的值的地址一起发送,但这似乎并不是最好的方法。
我们可以做的是序列化数据。 这意味着它被转换为扁平的描述。 流行的序列化格式称为 JSON发音为“Jason”它代表 JavaScript Object NotationJavaScript 对象表示法)。 它被广泛用作 Web 上的数据存储和通信格式,即使在 JavaScript 以外的语言中也是如此。
JSON 看起来像 JavaScript 的数组和对象的表示方式,但有一些限制。 所有属性名都必须用双引号括起来,并且只允许使用简单的数据表达式 - 没有函数调用,绑定或任何涉及实际计算的内容。 JSON 中不允许注释。
表示为 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"]
``` ```
### 本章小结 ### 本章小结
对象和数组(一种特殊对象)可以将几个值组合起来形成一个新的值。理论上说,我们可以将一组相关的元素打包成一个对象,并通过这个对象来访问这些元素,以避免管理那些支离破碎的元素。 对象和数组(一种特殊对象)可以将几个值组合起来形成一个新的值。理论上说,我们可以将一组相关的元素打包成一个对象,并通过这个对象来访问这些元素,以避免管理那些支离破碎的元素。
在JavaScript中除了null和undefined以外绝大多数的值都含有属性。我们可以用value.propName或value["propName"]的方式来访问属性。对象使用名称来定义和存储一定数量的属性。另外数组中通常会包含不同数量的值并使用数字从0开始作为这些值的属性。 在JavaScript中除了null和undefined以外绝大多数的值都含有属性。我们可以用value.prop或value["prop"]的方式来访问属性。对象使用名称来定义和存储一定数量的属性。另外数组中通常会包含不同数量的值并使用数字从0开始作为这些值的属性。
在数组中有一些具名属性比如length和一些方法。方法是作为属性存在的函数常常作用于其所属的值。 在数组中有一些具名属性比如length和一些方法。方法是作为属性存在的函数常常作用于其所属的值。
对象可以用来当作映射表将名称与值关联起来。我们可以使用in运算符确定对象中是否包含特定名称的属性。我们同样可以在for循环中forvar name in object使用关键字in来遍历对象中包含的属性。 您可以使用特殊类型的`for`循环`for (let element of array)`来迭代数组
### 习题 ### 习题
#### 特定范围数字求和 #### 范围的
在本书的前言中,提到过一种很好的计算固定范围内数字之和的方法: 在本书的前言中,提到过一种很好的计算固定范围内数字之和的方法:
@ -674,7 +728,7 @@ console.log(sum(range(1, 10)));
编写一个range函数接受两个参数start和end然后返回包含start到end包括end之间的所有数字。 编写一个range函数接受两个参数start和end然后返回包含start到end包括end之间的所有数字。
接着编写一个sum函数接受一个数字数组并返回所有数字之和。运行上面的程序检查一下结果是不是55。 接着编写一个sum函数接受一个数字数组并返回所有数字之和。运行示例程序检查一下结果是不是55。
附加题是修改range函数接受第3个可选参数指定构建数组时的步数step。如果没有指定步数构建数组时每步按1增长和旧函数行为一致。调用函数range1102应该返回[13579]。另外确保步数值为负数时也可以正常工作因此range52-1应该产生[5432]。 附加题是修改range函数接受第3个可选参数指定构建数组时的步数step。如果没有指定步数构建数组时每步按1增长和旧函数行为一致。调用函数range1102应该返回[13579]。另外确保步数值为负数时也可以正常工作因此range52-1应该产生[5432]。
@ -693,14 +747,14 @@ console.log(sum(range(1, 10)));
数组有一个reverse方法它可以逆转数组中元素的次序。在本题中编写两个函数reverseArray和reverseArrayInPlace。第一个函数reverseArray接受一个数组作为参数返回一个新数组并逆转新数组中的元素次序。第二个函数reverseArrayInPlace与第一个函数的功能相同但是直接将数组作为参数进行修改来逆转数组中的元素次序。两者都不能使用标准的reverse方法。 数组有一个reverse方法它可以逆转数组中元素的次序。在本题中编写两个函数reverseArray和reverseArrayInPlace。第一个函数reverseArray接受一个数组作为参数返回一个新数组并逆转新数组中的元素次序。第二个函数reverseArrayInPlace与第一个函数的功能相同但是直接将数组作为参数进行修改来逆转数组中的元素次序。两者都不能使用标准的reverse方法。
回想一下,在上一章中关于副作用和纯函数的讨论,哪个函数的写法的应用场景更广?哪个的执行效率会更高 回想一下,在上一章中关于副作用和纯函数的讨论,哪个函数的写法的应用场景更广?哪个执行得更快
``` ```
// Your code here. // Your code here.
console.log(reverseArray(["A", "B", "C"])); console.log(reverseArray(["A", "B", "C"]));
// → ["C", "B", "A"]; // → ["C", "B", "A"];
var arrayValue = [1, 2, 3, 4, 5]; let arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue); reverseArrayInPlace(arrayValue);
console.log(arrayValue); console.log(arrayValue);
// → [5, 4, 3, 2, 1] // → [5, 4, 3, 2, 1]
@ -711,7 +765,7 @@ console.log(arrayValue);
对象作为一个值的容器它可以用来构建各种各样的数据结构。有一种通用的数据结构叫作列表list不要与数组混淆。列表是一种嵌套对象集合第一个对象拥有第二个对象的引用而第二个对象有第三个对象的引用依此类推。 对象作为一个值的容器它可以用来构建各种各样的数据结构。有一种通用的数据结构叫作列表list不要与数组混淆。列表是一种嵌套对象集合第一个对象拥有第二个对象的引用而第二个对象有第三个对象的引用依此类推。
``` ```
var list = { let list = {
value: 1, value: 1,
rest: { rest: {
value: 2, value: 2,
@ -727,9 +781,9 @@ var list = {
![](../Images/00158.jpeg) ![](../Images/00158.jpeg)
使用列表的一个好处是,它们之间可以共享相同的子列表。举个例子,如果我们新建了两个值:{value0resultlist}和{value-1resultlist}list引用了我们前面定义的变量。这是两个独立的列表但它们之间却共享了同一个数据结构该数据结构包含列表末尾的三个元素。而且我们前面定义的list仍然是包含三个元素的列表。 使用列表的一个好处是,它们之间可以共享相同的子列表。举个例子,如果我们新建了两个值:{value0resultlist}和{value-1resultlist}list引用了我们前面定义的绑定。这是两个独立的列表但它们之间却共享了同一个数据结构该数据结构包含列表末尾的三个元素。而且我们前面定义的list仍然是包含三个元素的列表。
编写一个函数arrayToList当给定参数[123]时,建立一个和前面示例相似的数据结构。然后编写一个listToArray函数将列表转换成数组。再编写一个工具函数prepend接受一个元素和一个列表然后创建一个新的列表将元素添加到输入列表的开头。最后编写一个函数nth接受一个列表和一个数并返回列表中指定位置的元素如果该元素不存在则返回undefined。 编写一个函数arrayToList当给定参数[123]时建立一个和示例相似的数据结构。然后编写一个listToArray函数将列表转换成数组。再编写一个工具函数prepend接受一个元素和一个列表然后创建一个新的列表将元素添加到输入列表的开头。最后编写一个函数nth接受一个列表和一个数并返回列表中指定位置的元素如果该元素不存在则返回undefined。
如果你觉得这都不是什么难题那么编写一个递归版本的nth函数。 如果你觉得这都不是什么难题那么编写一个递归版本的nth函数。
@ -746,18 +800,20 @@ console.log(nth(arrayToList([10, 20, 30]), 1));
// → 20 // → 20
``` ```
#### 深比较 #### 深比较
==运算符可以判断对象是否相等。但有些时候,你希望比较的是对象中实际属性的值。 ==运算符可以判断对象是否相等。但有些时候,你希望比较的是对象中实际属性的值。
编写一个函数deepEqual接受两个参数若两个对象是同一个值或两个对象中有相同属性且使用deepEqual比较属性值均返回true时返回true。 编写一个函数deepEqual接受两个参数若两个对象是同一个值或两个对象中有相同属性且使用deepEqual比较属性值均返回true时返回true。
为了通过类型(使用===运算符或其属性比较出两个值是否完全相同可以使用typeof运算符。如果对两个值使用typeof均返回“object”则说明你应该进行深度比较。但需要考虑一个例外的情况由于历史原因typeof null也会返回“object”。 为了弄清楚通过身份(使用===运算符还是其属性比较两个值可以使用typeof运算符。如果对两个值使用typeof均返回“object”则说明你应该进行深层比较。但需要考虑一个例外的情况由于历史原因typeof null也会返回“object”。
当您需要查看对象的属性来进行比较时,`Object.keys`函数将非常有用。
``` ```
// Your code here. // Your code here.
var obj = {here: {is: "an"}, object: 2}; let obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj)); console.log(deepEqual(obj, obj));
// → true // → true
console.log(deepEqual(obj, {here: 1, object: 2})); console.log(deepEqual(obj, {here: 1, object: 2}));