From 505443b4f5da902db0502975f13d09a448d395d7 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Tue, 1 May 2018 21:25:14 +0800 Subject: [PATCH] 4. --- 4.md | 122 +++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 33 deletions(-) diff --git a/4.md b/4.md index de70c66..b7c4022 100644 --- a/4.md +++ b/4.md @@ -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。) -符号_n_&lt; sub&gt; 01&lt; / sub&gt;表明, 第一个变量(松鼠)为假(0)时,第二个变量(披萨)为真(1)。 在披萨表中,_n_&lt; sub&gt; 01&lt; / sub&gt; 是 9。 +符号_n_&lt; sub&gt; 01&lt; / sub&gt;表明, 第一个绑定(松鼠)为假(0)时,第二个绑定(披萨)为真(1)。 在披萨表中,_n_&lt; sub&gt; 01&lt; / sub&gt; 是 9。 -值_n_&lt; sub&gt; 1&lt; / sub&gt; 表示所有度量之和,其中第一个变量为true,在示例表中为5。 同样,_n_&lt; sub&gt; 0&lt; / sub&gt; 表示所有度量之和,其中第二个变量为假。 +值_n_&lt; sub&gt; 1&lt; / sub&gt; 表示所有度量之和,其中第一个绑定为true,在示例表中为5。 同样,_n_&lt; sub&gt; 0&lt; / sub&gt; 表示所有度量之和,其中第二个绑定为假。 因此,我们以比萨表为例,除法线上方的部分(被除数)为1×76–9×4=40,而除法线下面的部分(除数)则是10×80×5×85的平方根,也就是![](../Images/00127.jpeg)。计算结果`ϕ`≈0.069,这个结果很小,因此吃比萨对是否变身成松鼠显然没有太大影响。 ### 计算关联性 -我们可以用包含 4 个元素的数组(`[76,9,4,1]`)来表示一张 2 乘 2 的表格。我们也可以使用其他表示方式,比如包含两个数组的数组,每个子数组又包含两个元素(`[[76,9],[4,1]]`)。也可以使用一个对象,它包含一些属性,名为`"11"`和`"01"`。但是,一维数组更为简单,也容易进行操作。我们可以将数组索引看成包含两个二进制位的数字,左边的(高位)数字表示变量“是否变成松鼠”,右边的(低位)数字表示事件变量。例如,若二进制数字为 10,表示雅克变成了松鼠,但事件并未发生(比如说吃比萨)。这种情况发生了 4 次。由于二进制数字 10 的十进制是 2,因此我们将其存储到数组中索引为 2 的位置上。 +我们可以用包含 4 个元素的数组(`[76,9,4,1]`)来表示一张 2 乘 2 的表格。我们也可以使用其他表示方式,比如包含两个数组的数组,每个子数组又包含两个元素(`[[76,9],[4,1]]`)。也可以使用一个对象,它包含一些属性,名为`"11"`和`"01"`。但是,一维数组更为简单,也容易进行操作。我们可以将数组索引看成包含两个二进制位的数字,左边的(高位)数字表示绑定“是否变成松鼠”,右边的(低位)数字表示事件绑定。例如,若二进制数字为 10,表示雅克变成了松鼠,但事件并未发生(比如说吃比萨)。这种情况发生了 4 次。由于二进制数字 10 的十进制是 2,因此我们将其存储到数组中索引为 2 的位置上。 下面这个函数用于计算数组的系数`ϕ`: @@ -296,7 +296,7 @@ console.log(phi([76, 9, 4, 1])); 这将`φ`公式直接翻译成 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表格,我们首先需要循环遍历整个记录,并计算出与变身成松鼠相关事件发生的次数。 @@ -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对象,其对象本身没有什么实际用途。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) { - var angle = Math.random() * 2 * Math.PI; + let angle = Math.random() * 2 * Math.PI; return {x: radius * Math.cos(angle), y: radius * Math.sin(angle)}; } @@ -627,7 +627,7 @@ console.log(Math.random()); // → 0.40180766698904335 ``` -虽然计算机的一切行为都是预先设定好的,只要提供相同的输入,就会得到相同的输出结果。但我们仍然可以通过计算机来产生伪随机数。要产生一个随机数,计算机会在其内部状态中维护一个数字(或一组数字)。接着,每当我们要产生一个随机数时,计算机会根据其内部维护的状态,执行一系列复杂的预定义计算过程,然后返回计算的部分结果作为随机数。在返回计算结果的同时,计算机也会根据结果来改变其内部维护的状态,以便下次产生伪随机数的时候产生不同的结果。 +虽然计算机是确定性的机器,但如果给定相同的输入,它们总是以相同的方式作出反应 - 让它们产生随机显示的数字是可能的。 为此,机器会维护一些隐藏的值,并且每当您请求一个新的随机数时,它都会对该隐藏值执行复杂的计算来创建一个新值。 它存储一个新值并返回从中派生的一些数字。 这样,它可以以随机的方式产生新的,难以预测的数字。 如果我们想获取一个随机的整数而非小数,可以使用Math.floor(向下取整到与当前数字最接近的整数)来处理Math.random的结果。 @@ -638,33 +638,87 @@ console.log(Math.floor(Math.random() * 10)); 将随机数乘以10可以得到一个在0~10之间的数字。由于Math.floor是向下取整,因此该函数会等概率地取到0~9中的任何一个数字。 -还有两个函数,分别是Math.ceil(向上取整)和Math.round(四舍五入)。 +还有两个函数,分别是Math.ceil(向上取整)和Math.round(四舍五入)。以及`Math.abs`,它取数字的绝对值,这意味着它反转了负值,但保留了正值。 -### 全局对象 +### 解构 -JavaScript全局作用域中有许多全局变量,都可以通过全局对象进行访问。每一个全局变量作为一个属性存储在全局对象当中。在浏览器中,全局对象存储在window变量当中。 +让我们暂时回顾`phi`函数: ``` -var myVar = 10; -console.log("myVar" in window); -// → true -console.log(window.myVar); -// → 10 +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])); +} +``` + +这个函数难以阅读的原因之一,是我们有一个指向数组的绑定,但我们更愿意拥有数组的元素的绑定,即`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 Notation(JavaScript 对象表示法)。 它被广泛用作 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和一些方法。方法是作为属性存在的函数,常常作用于其所属的值。 -对象可以用来当作映射表,将名称与值关联起来。我们可以使用in运算符确定对象中是否包含特定名称的属性。我们同样可以在for循环中(for(var 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)之间的所有数字。 -接着,编写一个sum函数,接受一个数字数组,并返回所有数字之和。运行上面的程序,检查一下结果是不是55。 +接着,编写一个sum函数,接受一个数字数组,并返回所有数字之和。运行示例程序,检查一下结果是不是55。 附加题是修改range函数,接受第3个可选参数,指定构建数组时的步数(step)。如果没有指定步数,构建数组时,每步按1增长,和旧函数行为一致。调用函数range(1,10,2),应该返回[1,3,5,7,9]。另外确保步数值为负数时也可以正常工作,因此range(5,2,-1)应该产生[5,4,3,2]。 @@ -693,14 +747,14 @@ console.log(sum(range(1, 10))); 数组有一个reverse方法,它可以逆转数组中元素的次序。在本题中,编写两个函数,reverseArray和reverseArrayInPlace。第一个函数reverseArray接受一个数组作为参数,返回一个新数组,并逆转新数组中的元素次序。第二个函数reverseArrayInPlace与第一个函数的功能相同,但是直接将数组作为参数进行修改来,逆转数组中的元素次序。两者都不能使用标准的reverse方法。 -回想一下,在上一章中关于副作用和纯函数的讨论,哪个函数的写法的应用场景更广?哪个的执行效率会更高? +回想一下,在上一章中关于副作用和纯函数的讨论,哪个函数的写法的应用场景更广?哪个执行得更快? ``` // Your code here. console.log(reverseArray(["A", "B", "C"])); // → ["C", "B", "A"]; -var arrayValue = [1, 2, 3, 4, 5]; +let arrayValue = [1, 2, 3, 4, 5]; reverseArrayInPlace(arrayValue); console.log(arrayValue); // → [5, 4, 3, 2, 1] @@ -711,7 +765,7 @@ console.log(arrayValue); 对象作为一个值的容器,它可以用来构建各种各样的数据结构。有一种通用的数据结构叫作列表(list)(不要与数组混淆)。列表是一种嵌套对象集合,第一个对象拥有第二个对象的引用,而第二个对象有第三个对象的引用,依此类推。 ``` -var list = { +let list = { value: 1, rest: { value: 2, @@ -727,9 +781,9 @@ var list = { ![](../Images/00158.jpeg) -使用列表的一个好处是,它们之间可以共享相同的子列表。举个例子,如果我们新建了两个值:{value:0,result:list}和{value:-1,result:list}(list引用了我们前面定义的变量)。这是两个独立的列表,但它们之间却共享了同一个数据结构,该数据结构包含列表末尾的三个元素。而且我们前面定义的list仍然是包含三个元素的列表。 +使用列表的一个好处是,它们之间可以共享相同的子列表。举个例子,如果我们新建了两个值:{value:0,result:list}和{value:-1,result:list}(list引用了我们前面定义的绑定)。这是两个独立的列表,但它们之间却共享了同一个数据结构,该数据结构包含列表末尾的三个元素。而且我们前面定义的list仍然是包含三个元素的列表。 -编写一个函数arrayToList,当给定参数[1,2,3]时,建立一个和前面示例相似的数据结构。然后编写一个listToArray函数,将列表转换成数组。再编写一个工具函数prepend,接受一个元素和一个列表,然后创建一个新的列表,将元素添加到输入列表的开头。最后编写一个函数nth,接受一个列表和一个数,并返回列表中指定位置的元素,如果该元素不存在则返回undefined。 +编写一个函数arrayToList,当给定参数[1,2,3]时,建立一个和示例相似的数据结构。然后编写一个listToArray函数,将列表转换成数组。再编写一个工具函数prepend,接受一个元素和一个列表,然后创建一个新的列表,将元素添加到输入列表的开头。最后编写一个函数nth,接受一个列表和一个数,并返回列表中指定位置的元素,如果该元素不存在则返回undefined。 如果你觉得这都不是什么难题,那么编写一个递归版本的nth函数。 @@ -746,18 +800,20 @@ console.log(nth(arrayToList([10, 20, 30]), 1)); // → 20 ``` -#### 深度比较 +#### 深层比较 ==运算符可以判断对象是否相等。但有些时候,你希望比较的是对象中实际属性的值。 编写一个函数deepEqual,接受两个参数,若两个对象是同一个值或两个对象中有相同属性,且使用deepEqual比较属性值均返回true时,返回true。 -为了通过类型(使用===运算符)或其属性比较出两个值是否完全相同,可以使用typeof运算符。如果对两个值使用typeof均返回“object”,则说明你应该进行深度比较。但需要考虑一个例外的情况:由于历史原因,typeof null也会返回“object”。 +为了弄清楚通过身份(使用===运算符)还是其属性比较两个值,可以使用typeof运算符。如果对两个值使用typeof均返回“object”,则说明你应该进行深层比较。但需要考虑一个例外的情况:由于历史原因,typeof null也会返回“object”。 + +当您需要查看对象的属性来进行比较时,`Object.keys`函数将非常有用。 ``` // Your code here. -var obj = {here: {is: "an"}, object: 2}; +let obj = {here: {is: "an"}, object: 2}; console.log(deepEqual(obj, obj)); // → true console.log(deepEqual(obj, {here: 1, object: 2}));