亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

JavaScript 編程精解 中文第三版 四、數(shù)據(jù)結(jié)構(gòu):對(duì)象和數(shù)組

kamushin233 / 3433人閱讀

摘要:本章將介紹基本的數(shù)據(jù)結(jié)構(gòu)。松鼠人一般在晚上八點(diǎn)到十點(diǎn)之間,雅克就會(huì)變身成為一只毛茸茸的松鼠,尾巴上的毛十分濃密。我們將雅克的日記表示為對(duì)象數(shù)組。

來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目

原文:Data Structures: Objects and Arrays

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

部分參考了《JavaScript 編程精解(第 2 版)》

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)

數(shù)字,布爾和字符串是構(gòu)建數(shù)據(jù)結(jié)構(gòu)的原子。 不過(guò),許多類(lèi)型的信息都需要多個(gè)原子。 對(duì)象允許我們將值(包括其他對(duì)象)放到一起,來(lái)構(gòu)建更復(fù)雜的結(jié)構(gòu)。

我們迄今為止構(gòu)建的程序,受到一個(gè)事實(shí)的限制,它們僅在簡(jiǎn)單數(shù)據(jù)類(lèi)型上運(yùn)行。 本章將介紹基本的數(shù)據(jù)結(jié)構(gòu)。 到最后,你會(huì)知道足夠多的東西,開(kāi)始編寫(xiě)有用的程序。

本章將著手于一個(gè)或多或少的實(shí)際編程示例,當(dāng)概念適用于手頭問(wèn)題時(shí)引入它們。 示例代碼通?;诒疚那懊娼榻B的函數(shù)和綁定。

松鼠人

一般在晚上八點(diǎn)到十點(diǎn)之間,雅克就會(huì)變身成為一只毛茸茸的松鼠,尾巴上的毛十分濃密。

一方面,雅克非常高興他沒(méi)有變成經(jīng)典的狼人。 與變成狼相比,變成松鼠的確會(huì)產(chǎn)生更少的問(wèn)題。 他不必?fù)?dān)心偶然吃掉鄰居(那會(huì)很尷尬),而是擔(dān)心被鄰居的貓吃掉。 他在橡木樹(shù)冠上的一個(gè)薄薄的樹(shù)枝上醒來(lái),赤身裸體并迷失方向。在這兩次偶然之后,他在晚上鎖上了房間的門(mén)窗,并在地板上放了幾個(gè)核桃,來(lái)使自己忙起來(lái)。

這就解決了貓和樹(shù)的問(wèn)題。 但雅克寧愿完全擺脫他的狀況。 不規(guī)律發(fā)生的變身使他懷疑,它們可能會(huì)由某種東西觸發(fā)。 有一段時(shí)間,他相信只有在他靠近橡樹(shù)的日子里才會(huì)發(fā)生。 但是避開(kāi)橡樹(shù)不能阻止這個(gè)問(wèn)題。

雅克切換到了更科學(xué)的方法,開(kāi)始每天記錄他在某一天所做的每件事,以及他是否變身。 有了這些數(shù)據(jù),他希望能夠縮小觸發(fā)變身的條件。

他需要的第一個(gè)東西,是存儲(chǔ)這些信息的數(shù)據(jù)結(jié)構(gòu)。

數(shù)據(jù)集

為了處理大量的數(shù)字?jǐn)?shù)據(jù),我們首先必須找到一種方法,將其在我們的機(jī)器內(nèi)存中表示。 舉例來(lái)說(shuō),我們想要表示一組數(shù)字 2, 3, 5, 7 和 11。

我們可以用字符串來(lái)創(chuàng)建 - 畢竟,字符串可以有任意長(zhǎng)度,所以我們可以把大量數(shù)據(jù)放入它們中,并使用"2 3 5 7 11"作為我們的表示。 但這很笨拙。 你必須以某種方式提取數(shù)字,并將它們轉(zhuǎn)換回?cái)?shù)字才能訪問(wèn)它們。

幸運(yùn)的是,JavaScript提供了一種數(shù)據(jù)類(lèi)型,專(zhuān)門(mén)用于存儲(chǔ)一系列的值。我們將這種數(shù)據(jù)類(lèi)型稱(chēng)為數(shù)組,將一連串的值寫(xiě)在方括號(hào)當(dāng)中,值之間使用逗號(hào)(,)分隔。

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

我們同樣使用方括號(hào)來(lái)獲取數(shù)組當(dāng)中的值。在表達(dá)式后緊跟一對(duì)方括號(hào),并在方括號(hào)中填寫(xiě)表達(dá)式,這將會(huì)在左側(cè)表達(dá)式里查找方括號(hào)中給定的索引所對(duì)應(yīng)的值,并返回結(jié)果。

數(shù)組的第一個(gè)索引是零,而不是一。 所以第一個(gè)元素用listOfNumbers[0]獲取。 基于零的計(jì)數(shù)在技術(shù)上有著悠久的傳統(tǒng),并且在某些方面意義很大,但需要一些時(shí)間來(lái)習(xí)慣。 將索引看作要跳過(guò)的項(xiàng)目數(shù)量,從數(shù)組的開(kāi)頭計(jì)數(shù)。

屬性

在之前的章節(jié)中,我們已經(jīng)看到了一些可疑的表達(dá)式,例如myString.length(獲取字符串的長(zhǎng)度)和Math.max(最大值函數(shù))。 這些表達(dá)式可以訪問(wèn)某個(gè)值的屬性。 在第一個(gè)中,我們?cè)L問(wèn)myString中的length屬性。 第二個(gè)中,我們?cè)L問(wèn)Math對(duì)象(它是數(shù)學(xué)相關(guān)常量和函數(shù)的集合)中的名為max的屬性。

在 JavaScript 中,幾乎所有的值都有屬性。但nullundefined沒(méi)有。如果你嘗試訪問(wèn)nullundefined的屬性,會(huì)得到一個(gè)錯(cuò)誤提示。

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

在JavaScript中訪問(wèn)屬性的兩種主要方式是點(diǎn)(.)和方括號(hào)([])。 value.xvalue [x]都可以訪問(wèn)value屬性,但不一定是同一個(gè)屬性。 區(qū)別在于如何解釋x。 使用點(diǎn)時(shí),點(diǎn)后面的單詞是該屬性的字面名稱(chēng)。 使用方括號(hào)時(shí),會(huì)求解括號(hào)內(nèi)的表達(dá)式來(lái)獲取屬性名稱(chēng)。 鑒于value.x獲取value的名為x的屬性,value [x]嘗試求解表達(dá)式x,并將結(jié)果轉(zhuǎn)換為字符串作為屬性名稱(chēng)。

所以如果你知道你感興趣的屬性叫做color,那么你會(huì)寫(xiě)value.color。 如果你想提取屬性由綁定i中保存的值命名,你可以寫(xiě)value [i]。 屬性名稱(chēng)是字符串。 它們可以是任何字符串,但點(diǎn)符號(hào)僅適用于看起來(lái)像有效綁定名的名稱(chēng)。 所以如果你想訪問(wèn)名為2John Doe的屬性,你必須使用方括號(hào):value[2]value["John Doe"]。

數(shù)組中的元素以數(shù)組屬性的形式存儲(chǔ),使用數(shù)字作為屬性名稱(chēng)。 因?yàn)槟悴荒苡命c(diǎn)號(hào)來(lái)表示數(shù)字,并且通常想要使用一個(gè)保存索引的綁定,所以你必須使用括號(hào)來(lái)表達(dá)它們。

數(shù)組的length屬性告訴我們它有多少個(gè)元素。 這個(gè)屬性名是一個(gè)有效的綁定名,我們事先知道它的名字,所以為了得到一個(gè)數(shù)組的長(zhǎng)度,通常寫(xiě)array.length,因?yàn)樗?b>array["length"]更容易編寫(xiě)。

方法

length屬性之外,字符串和數(shù)組對(duì)象都包含一些持有函數(shù)值的屬性。

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

每個(gè)字符串都有toUpperCase屬性。 調(diào)用時(shí),它將返回所有字母轉(zhuǎn)換為大寫(xiě)字符串的副本。 另外還有toLowerCase。

有趣的是,雖然我們沒(méi)有在調(diào)用toUpperCase時(shí)傳遞任何參數(shù),但該函數(shù)訪問(wèn)了字符串"Doh",即被調(diào)用的屬性所屬的值。我們會(huì)在第 6 章中闡述這其中的原理。

我們通常將包含函數(shù)的屬性稱(chēng)為某個(gè)值的方法。比如說(shuō),toUpperCase是字符串的一個(gè)方法。

此示例演示了兩種方法,可用于操作數(shù)組:

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方法將值添加到數(shù)組的末尾,而pop方法則相反,刪除數(shù)組中的最后一個(gè)值并將其返回。

這些有點(diǎn)愚蠢的名字是棧的傳統(tǒng)術(shù)語(yǔ)。 編程中的棧是一種數(shù)據(jù)結(jié)構(gòu),它允許你將值推入并按相反順序再次彈出,最后添加的內(nèi)容首先被移除。 這些在編程中很常見(jiàn) - 你可能還記得前一章中的函數(shù)調(diào)用棧,它是同一個(gè)想法的實(shí)例。

對(duì)象

回到松鼠人的示例。 一組每日的日志條目可以表示為一個(gè)數(shù)組。 但是這些條目并不僅僅由一個(gè)數(shù)字或一個(gè)字符串組成 - 每個(gè)條目需要存儲(chǔ)一系列活動(dòng)和一個(gè)布爾值,表明雅克是否變成了松鼠。 理想情況下,我們希望將它們組合成一個(gè)值,然后將這些分組的值放入日志條目的數(shù)組中。

對(duì)象類(lèi)型的值是任意的屬性集合。 創(chuàng)建對(duì)象的一種方法是使用大括號(hào)作為表達(dá)式。

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

大括號(hào)內(nèi)有一列用逗號(hào)分隔的屬性。 每個(gè)屬性都有一個(gè)名字,后跟一個(gè)冒號(hào)和一個(gè)值。 當(dāng)一個(gè)對(duì)象寫(xiě)為多行時(shí),像這個(gè)例子那樣,對(duì)它進(jìn)行縮進(jìn)有助于提高可讀性。 名稱(chēng)不是有效綁定名稱(chēng)或有效數(shù)字的屬性必須加引號(hào)。

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

這意味著大括號(hào)在 JavaScript 中有兩個(gè)含義。 在語(yǔ)句的開(kāi)頭,他們起始了一個(gè)語(yǔ)句塊。 在任何其他位置,他們描述一個(gè)對(duì)象。 幸運(yùn)的是,語(yǔ)句很少以花括號(hào)對(duì)象開(kāi)始,因此這兩者之間的不明確性并不是什么大問(wèn)題。

讀取一個(gè)不存在的屬性就會(huì)產(chǎn)生undefined。

我們可以使用=運(yùn)算符來(lái)給一個(gè)屬性表達(dá)式賦值。如果該屬性已經(jīng)存在,那么這項(xiàng)操作就會(huì)替換原有的值。如果該屬性不存在,則會(huì)在目標(biāo)對(duì)象中新建一個(gè)屬性。

簡(jiǎn)要回顧我們的綁定的觸手模型 - 屬性綁定也類(lèi)似。 他們捕獲值,但其他綁定和屬性可能會(huì)持有這些相同的值。 你可以將對(duì)象想象成有任意數(shù)量觸手的章魚(yú),每個(gè)觸手上都有一個(gè)名字的紋身。

delete運(yùn)算符切斷章魚(yú)的觸手。 這是一個(gè)一元運(yùn)算符,當(dāng)應(yīng)用于對(duì)象屬性時(shí),將從對(duì)象中刪除指定的屬性。 這不是一件常見(jiàn)的事情,但它是可能的。

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

當(dāng)應(yīng)用于字符串和對(duì)象時(shí),二元in運(yùn)算符會(huì)告訴你該對(duì)象是否具有名稱(chēng)為它的屬性。 將屬性設(shè)置為undefined,和實(shí)際刪除它的區(qū)別在于,在第一種情況下,對(duì)象仍然具有屬性(它只是沒(méi)有有意義的值),而在第二種情況下屬性不再存在,in會(huì)返回false

為了找出對(duì)象具有的屬性,可以使用Object.keys函數(shù)。 你給它一個(gè)對(duì)象,它返回一個(gè)字符串?dāng)?shù)組 - 對(duì)象的屬性名稱(chēng)。

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

Object.assign函數(shù)可以將一個(gè)對(duì)象的所有屬性復(fù)制到另一個(gè)對(duì)象中。

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

然后,數(shù)組只是一種對(duì)象,專(zhuān)門(mén)用于存儲(chǔ)對(duì)象序列。 如果你求解typeof [],它會(huì)產(chǎn)生object。 你可以看到它們是長(zhǎng)而平坦的章魚(yú),它們的觸手整齊排列,并以數(shù)字標(biāo)記。

我們將雅克的日記表示為對(duì)象數(shù)組。

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... */
];
可變性

我們現(xiàn)在即將開(kāi)始真正的編程。 首先還有一個(gè)理論要理解。

我們看到對(duì)象值可以修改。 千米你的章節(jié)討論的值的類(lèi)型(如數(shù)字,字符串和布爾值)都是不可變的 -- 這些類(lèi)型的值不可能修改。 你可以將它們組合起來(lái)并從它們派生新的值,但是當(dāng)你采用特定的字符串值時(shí),該值將始終保持不變。 里面的文字不能改變。 如果你有一個(gè)包含"cat"的字符串,其他代碼不可能修改你的字符串中的一個(gè)字符,來(lái)使它變成"rat"。

對(duì)象的工作方式不同。你可以更改其屬性,使單個(gè)對(duì)象值在不同時(shí)間具有不同的內(nèi)容。

當(dāng)我們有兩個(gè)數(shù)字,120 和 120 時(shí),我們可以將它們看作完全相同的數(shù)字,不管它們是否指向相同的物理位。 使用對(duì)象時(shí),擁有同一個(gè)對(duì)象的兩個(gè)引用,和擁有包含相同屬性的兩個(gè)不同的對(duì)象,是有區(qū)別的。 考慮下面的代碼:

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

object1object2綁定持有相同對(duì)象,這就是為什么改變object1會(huì)改變object2的值。 據(jù)說(shuō)他們具有相同的身份。 綁定object3指向一個(gè)不同的對(duì)象,它最初包含的屬性與object1相同,但過(guò)著多帶帶的生活。

綁定可以是可變的或不變的,但這與它們的值的行為方式是分開(kāi)的。 即使數(shù)值不變,你也可以使用let綁定來(lái)跟蹤一個(gè)變化的數(shù)字,通過(guò)修改綁定所指向的值。與之類(lèi)似,雖然對(duì)象的const綁定本身不可改變,并且始終指向相同對(duì)象,該對(duì)象的內(nèi)容可能會(huì)改變。

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

當(dāng)你用 JavaScript 的==運(yùn)算符比較對(duì)象時(shí),它按照身份進(jìn)行比較:僅當(dāng)兩個(gè)對(duì)象的值嚴(yán)格相同時(shí)才產(chǎn)生true。 比較不同的對(duì)象會(huì)返回false,即使它們屬性相同。 JavaScript 中沒(méi)有內(nèi)置的“深層”比較操作,它按照內(nèi)容比較對(duì)象,但可以自己編寫(xiě)它(這是本章末尾的一個(gè)練習(xí))。

松鼠人的記錄

于是,雅克開(kāi)始了他的 JavaScript 之旅,并搭建了用于保存每天記錄的一套開(kāi)發(fā)環(huán)境。

let journal = [];

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

請(qǐng)注意添加到日記中的對(duì)象看起來(lái)有點(diǎn)奇怪。 它不像events:events那樣聲明屬性,只是提供屬性名稱(chēng)。 這是一個(gè)簡(jiǎn)寫(xiě),意思一樣 - 如果大括號(hào)中的屬性名后面沒(méi)有值,它的值來(lái)自相同名稱(chēng)的綁定。

那么,在每天晚上十點(diǎn) -- 或者有時(shí)候是下一天的早晨,從它的書(shū)架頂部爬下來(lái)之后 -- 雅克記錄了這一天。

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);

一旦他有了足夠的數(shù)據(jù)點(diǎn),他打算使用統(tǒng)計(jì)學(xué)來(lái)找出哪些事件可能與變成松鼠有關(guān)。

關(guān)聯(lián)性是統(tǒng)計(jì)綁定之間的獨(dú)立性的度量。 統(tǒng)計(jì)綁定與編程綁定不完全相同。 在統(tǒng)計(jì)學(xué)中,你通常會(huì)有一組度量,并且每個(gè)綁定都根據(jù)每個(gè)度量來(lái)測(cè)量。 綁定之間的相關(guān)性通常表示為從 -1 到 1 的值。 相關(guān)性為零意味著綁定不相關(guān)。 相關(guān)性為一表明兩者完全相關(guān) - 如果你知道一個(gè),你也知道另一個(gè)。 負(fù)一意味著它們是完全相關(guān)的,但它們是相反的 - 當(dāng)一個(gè)是真的時(shí),另一個(gè)是假的。

為了計(jì)算兩個(gè)布爾綁定之間的相關(guān)性度量,我們可以使用 phi 系數(shù)(?)。 這是一個(gè)公式,輸入為一個(gè)頻率表格,包含觀測(cè)綁定的不同組合的次數(shù)。 公式的輸出是 -1 和 1 之間的數(shù)字。

我們可以將吃比薩的事件放在這樣的頻率表中,每個(gè)數(shù)字表示我們的度量中的組合的出現(xiàn)次數(shù)。

如果我們將那個(gè)表格稱(chēng)為n,我們可以用下列公式自己算?

(如果你現(xiàn)在把這本書(shū)放下,專(zhuān)注于十年級(jí)數(shù)學(xué)課的可怕的再現(xiàn),堅(jiān)持??!我不打算用無(wú)休止的神秘符號(hào)折磨你 - 現(xiàn)在只有這一個(gè)公式。我們所做的就是把它變成 JavaScript。)

符號(hào)n01表明, 第一個(gè)綁定(松鼠)為假(0)時(shí),第二個(gè)綁定(披薩)為真(1)。 在披薩表中,n01是 9。

n1表示所有度量之和,其中第一個(gè)綁定為true,在示例表中為 5。 同樣,n0表示所有度量之和,其中第二個(gè)綁定為假。

因此,我們以比薩表為例,除法線上方的部分(被除數(shù))為1×76–9×4=40,而除法線下面的部分(除數(shù))則是10×80×5×85的平方根,也就是√340000。計(jì)算結(jié)果為?≈0.069,這個(gè)結(jié)果很小,因此吃比薩對(duì)是否變身成松鼠顯然沒(méi)有太大影響。

計(jì)算關(guān)聯(lián)性

我們可以用包含 4 個(gè)元素的數(shù)組([76,9,4,1])來(lái)表示一張 2 乘 2 的表格。我們也可以使用其他表示方式,比如包含兩個(gè)數(shù)組的數(shù)組,每個(gè)子數(shù)組又包含兩個(gè)元素([[76,9],[4,1]])。也可以使用一個(gè)對(duì)象,它包含一些屬性,名為"11""01"。但是,一維數(shù)組更為簡(jiǎn)單,也容易進(jìn)行操作。我們可以將數(shù)組索引看成包含兩個(gè)二進(jìn)制位的數(shù)字,左邊的(高位)數(shù)字表示綁定“是否變成松鼠”,右邊的(低位)數(shù)字表示事件綁定。例如,若二進(jìn)制數(shù)字為 10,表示雅克變成了松鼠,但事件并未發(fā)生(比如說(shuō)吃比薩)。這種情況發(fā)生了 4 次。由于二進(jìn)制數(shù)字 10 的十進(jìn)制是 2,因此我們將其存儲(chǔ)到數(shù)組中索引為 2 的位置上。

下面這個(gè)函數(shù)用于計(jì)算數(shù)組的系數(shù)?

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

這將?公式直接翻譯成 JavaScript。 Math.sqrt是平方根函數(shù),由標(biāo)準(zhǔn) JavaScript 環(huán)境中的Math對(duì)象提供。 我們必須在表格中添加兩個(gè)字段來(lái)獲取字段,例如n1因?yàn)樾泻突蛘吡泻筒恢苯哟鎯?chǔ)在我們的數(shù)據(jù)結(jié)構(gòu)中。

雅克花了三個(gè)月的時(shí)間記錄日志。在本章的代碼沙箱(http://eloquentjavascript.net/code/)的下載文件中,用JOURNAL綁定存儲(chǔ)了該結(jié)果數(shù)據(jù)集合。

若要從這篇記錄中提取出某個(gè)特定事件的 2 乘 2 表格,我們首先需要循環(huán)遍歷整個(gè)記錄,并計(jì)算出與變身成松鼠相關(guān)事件發(fā)生的次數(shù)。

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]

數(shù)組擁有includes方法,檢查給定值是否存在于數(shù)組中。 該函數(shù)使用它來(lái)確定,對(duì)于某一天,感興趣的事件名稱(chēng)是否在事件列表中。

tableFor中的循環(huán)體通過(guò)檢查列表是否包含它感興趣的特定事件,以及該事件是否與松鼠事件一起發(fā)生,來(lái)計(jì)算每個(gè)日記條目在表格中的哪個(gè)盒子。 然后循環(huán)對(duì)表中的正確盒子加一。

我們現(xiàn)在有了我們計(jì)算個(gè)體相關(guān)性的所需工具。 剩下的唯一一步,就是為記錄的每種類(lèi)型的事件找到關(guān)聯(lián),看看是否有什么明顯之處。

數(shù)組循環(huán)

tableFor函數(shù)中,有一個(gè)這樣的循環(huán):

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

這種循環(huán)在經(jīng)典的 JavaScript 中很常見(jiàn) - 遍歷數(shù)組,一次一個(gè)元素會(huì)很常見(jiàn),為此,你需要在數(shù)組長(zhǎng)度上維護(hù)一個(gè)計(jì)數(shù)器,并依次選取每個(gè)元素。

在現(xiàn)代 JavaScript 中有一個(gè)更簡(jiǎn)單的方法來(lái)編寫(xiě)這樣的循環(huán)。

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

當(dāng)for循環(huán)看起來(lái)像這樣,在綁定定義之后用of這個(gè)詞時(shí),它會(huì)遍歷of之后的給定值的元素。 這不僅適用于數(shù)組,而且適用于字符串和其他數(shù)據(jù)結(jié)構(gòu)。 我們將在第 6 章中討論它的工作原理。

分析結(jié)果

我們需要計(jì)算數(shù)據(jù)集中發(fā)生的每種類(lèi)型事件的相關(guān)性。 為此,我們首先需要尋找每種類(lèi)型的事件。

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", …]

通過(guò)遍歷所有事件,并將那些不在里面的事件添加到events數(shù)組中,該函數(shù)收集每種事件。

使用它,我們可以看到所有的相關(guān)性。

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...

絕大多數(shù)相關(guān)系數(shù)都趨近于 0。顯然,攝入胡蘿卜、面包或布丁并不會(huì)導(dǎo)致變身成松鼠。但是似乎在周末變身成松鼠的概率更高。讓我們過(guò)濾結(jié)果,來(lái)僅僅顯示大于 0.1 或小于 -0.1 的相關(guān)性。

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

啊哈!這里有兩個(gè)因素,其相關(guān)性明顯強(qiáng)于其他因素。 吃花生對(duì)變成松鼠的幾率有強(qiáng)烈的積極影響,而刷牙有顯著的負(fù)面影響。

這太有意思了。讓我們?cè)僮屑?xì)看看這些數(shù)據(jù)。

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

這是一個(gè)強(qiáng)有力的結(jié)果。 這種現(xiàn)象正好發(fā)生在雅克吃花生并且沒(méi)有刷牙時(shí)。 如果他只是不注意口腔衛(wèi)生,他從來(lái)沒(méi)有注意到他的病痛。

知道這些之后,雅克完全停止吃花生,發(fā)現(xiàn)他的變身消失了。

幾年來(lái),雅克過(guò)得越來(lái)越好。 但是在某個(gè)時(shí)候他失去了工作。 因?yàn)樗钤谝粋€(gè)糟糕的國(guó)家,沒(méi)有工作就意味著沒(méi)有醫(yī)療服務(wù),所以他被迫在一個(gè)馬戲團(tuán)就業(yè),在那里他扮演的是不可思議的松鼠人,在每場(chǎng)演出前都用花生醬塞滿了它的嘴。

數(shù)組詳解

在完成本章之前,我想向你介紹幾個(gè)對(duì)象相關(guān)的概念。 我將首先介紹一些通常實(shí)用的數(shù)組方法。

我們?cè)诒菊碌那懊嬉呀?jīng)了解了pushpop方法,分別用于在數(shù)組末尾添加或刪除元素。相應(yīng)地,在數(shù)組的開(kāi)頭添加或刪除元素的方法分別是unshiftshift。

let todoList = [];
function remember(task) {
  todoList.push(task);
}
function getTask() {
  return todoList.shift();
}
function rememberUrgently(task) {
  todoList.unshift(task);
}

這個(gè)程序管理任務(wù)隊(duì)列。 你通過(guò)調(diào)用remember("groceries"),將任務(wù)添加到隊(duì)列的末尾,并且當(dāng)你準(zhǔn)備好執(zhí)行某些操作時(shí),可以調(diào)用getTask()從隊(duì)列中獲?。úh除)第一個(gè)項(xiàng)目。 rememberUrgently函數(shù)也添加任務(wù),但將其添加到隊(duì)列的前面而不是隊(duì)列的后面。

有一個(gè)與indexOf方法類(lèi)似的方法,叫lastIndexOf,只不過(guò)indexOf從數(shù)組第一個(gè)元素向后搜索,而lastIndexOf從最后一個(gè)元素向前搜索。

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

indexOflastIndexOf方法都有一個(gè)可選參數(shù),可以用來(lái)指定搜索的起始位置。

另一個(gè)基本方法是slice,該方法接受一個(gè)起始索引和一個(gè)結(jié)束索引,然后返回?cái)?shù)組中兩個(gè)索引范圍內(nèi)的元素。起始索引元素包含在返回結(jié)果中,但結(jié)束索引元素不會(huì)包含在返回結(jié)果中。

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

如果沒(méi)有指定結(jié)束索引,slice會(huì)返回從起始位置之后的所有元素。你也可以省略起始索引來(lái)復(fù)制整個(gè)數(shù)組。

concat方法可用于將數(shù)組粘在一起,來(lái)創(chuàng)建一個(gè)新數(shù)組,類(lèi)似于+運(yùn)算符對(duì)字符串所做的操作。

以下示例展示了concatslice的作用。 它接受一個(gè)數(shù)組和一個(gè)索引,然后它返回一個(gè)新數(shù)組,該數(shù)組是原數(shù)組的副本,并且刪除了給定索引處的元素:

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"]

如果你將concat傳遞給一個(gè)不是數(shù)組的參數(shù),該值將被添加到新數(shù)組中,就像它是單個(gè)元素的數(shù)組一樣。

字符串及其屬性

我們可以調(diào)用字符串的lengthtoUpperCase這樣的屬性,但不能向字符串中添加任何新的屬性。

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

字符串、數(shù)字和布爾類(lèi)型的值并不是對(duì)象,因此當(dāng)你向這些值中添加屬性時(shí) JavaScript 并不會(huì)報(bào)錯(cuò),但實(shí)際上你并沒(méi)有將這些屬性添加進(jìn)去。前面說(shuō)過(guò),這些值是不變的,不能改變。

但這些類(lèi)型包含一些內(nèi)置屬性。每個(gè)字符串中包含了若干方法供我們使用,最有用的方法可能就是sliceindexOf了,它們的功能與數(shù)組中的同名方法類(lèi)似。

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

一個(gè)區(qū)別是,字符串的indexOf可以搜索包含多個(gè)字符的字符串,而相應(yīng)的數(shù)組方法僅查找單個(gè)元素。

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

trim方法用于刪除字符串中開(kāi)頭和結(jié)尾的空白符號(hào)(空格、換行符和制表符等符號(hào))。

console.log("  okay 
 ".trim());
// → okay

上一章中的zeroPad函數(shù)也作為方法存在。 它被稱(chēng)為padStart,接受所需的長(zhǎng)度和填充字符作為參數(shù)。

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

你可以使用split,在另一個(gè)字符串的每個(gè)出現(xiàn)位置分割一個(gè)字符串,然后再用join把它連接在一起。

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

可以用repeat方法重復(fù)一個(gè)字符串,該方法創(chuàng)建一個(gè)新字符串,包含原始字符串的多個(gè)副本,并將其粘在一起。

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

我們已經(jīng)看到了字符串類(lèi)型的length屬性。 訪問(wèn)字符串中的單個(gè)字符,看起來(lái)像訪問(wèn)數(shù)組元素(有一個(gè)警告,我們將在第 5 章中討論)。

let string = "abc";
console.log(string.length);
// → 3
console.log(string[1]);
// → b
剩余參數(shù)

一個(gè)函數(shù)可以接受任意數(shù)量的參數(shù)。 例如,Math.max計(jì)算提供給它的參數(shù)的最大值。

為了編寫(xiě)這樣一個(gè)函數(shù),你需要在函數(shù)的最后一個(gè)參數(shù)之前放三個(gè)點(diǎn),如下所示:

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

當(dāng)這樣的函數(shù)被調(diào)用時(shí),剩余參數(shù)綁定一個(gè)數(shù)組,包含所有其它參數(shù)。 如果之前有其他參數(shù),它們的值不是該數(shù)組的一部分。 當(dāng)它是唯一的參數(shù)時(shí),如max中那樣,它將保存所有參數(shù)。

你可以使用類(lèi)似的三點(diǎn)表示法,來(lái)使用參數(shù)數(shù)組調(diào)用函數(shù)。

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

這在函數(shù)調(diào)用中“展開(kāi)”數(shù)組,并將其元素傳遞為多帶帶的參數(shù)。 像`max(9, ...numbers, 2)"那樣,可以包含像這樣的數(shù)組以及其他參數(shù)。

方括號(hào)的數(shù)組表示法,同樣允許三點(diǎn)運(yùn)算符將另一個(gè)數(shù)組展開(kāi)到新數(shù)組中:

let words = ["never", "fully"];
console.log(["will", ...words, "understand"]);
// → ["will", "never", "fully", "understand"]
Math對(duì)象

正如我們所看到的那樣,Math對(duì)象中包含了許多與數(shù)字相關(guān)的工具函數(shù),比如Math.max(求最大值)、Math.min(求最小值)和Math.sqrt(求平方根)。

Math對(duì)象被用作一個(gè)容器來(lái)分組一堆相關(guān)的功能。 只有一個(gè)Math對(duì)象,它作為一個(gè)值幾乎沒(méi)有用處。 相反,它提供了一個(gè)命名空間,使所有這些函數(shù)和值不必是全局綁定。

過(guò)多的全局綁定會(huì)“污染”命名空間。全局綁定越多,就越有可能一不小心把某些綁定的值覆蓋掉。比如,我們可能想在程序中使用名為max的綁定,由于 JavaScript 將內(nèi)置的max函數(shù)安全地放置在Math對(duì)象中,因此不必?fù)?dān)心max的值會(huì)被覆蓋。

當(dāng)你去定義一個(gè)已經(jīng)被使用的綁定名的時(shí)候,對(duì)于很多編程語(yǔ)言來(lái)說(shuō),都會(huì)阻止你這么做,至少會(huì)對(duì)這種行為發(fā)出警告。但是 JavaScript 不會(huì),因此要小心這些陷阱。

讓我們來(lái)繼續(xù)了解Math對(duì)象。如果需要做三角運(yùn)算,Math對(duì)象可以幫助到你,它包含cos(余弦)、sin(正弦)、tan(正切)和各自的反函數(shù)(acos、asinatan)。Math.PI則表示數(shù)字π,或至少是 JavaScript 中的數(shù)字近似值。在傳統(tǒng)的程序設(shè)計(jì)當(dāng)中,常量均以大寫(xiě)來(lái)標(biāo)注。

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}

如果你對(duì)正弦或余弦不大熟悉,不必?fù)?dān)心。我們會(huì)在第 13 章用到它們時(shí),再做進(jìn)一步解釋。

在上面的示例代碼中使用了Math.random。每次調(diào)用該函數(shù)時(shí),會(huì)返回一個(gè)偽隨機(jī)數(shù),范圍在 0(包括)到 1(不包括)之間。

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

雖然計(jì)算機(jī)是確定性的機(jī)器,但如果給定相同的輸入,它們總是以相同的方式作出反應(yīng) - 讓它們產(chǎn)生隨機(jī)顯示的數(shù)字是可能的。 為此,機(jī)器會(huì)維護(hù)一些隱藏的值,并且每當(dāng)你請(qǐng)求一個(gè)新的隨機(jī)數(shù)時(shí),它都會(huì)對(duì)該隱藏值執(zhí)行復(fù)雜的計(jì)算來(lái)創(chuàng)建一個(gè)新值。 它存儲(chǔ)一個(gè)新值并返回從中派生的一些數(shù)字。 這樣,它可以以隨機(jī)的方式產(chǎn)生新的,難以預(yù)測(cè)的數(shù)字。

如果我們想獲取一個(gè)隨機(jī)的整數(shù)而非小數(shù),可以使用Math.floor(向下取整到與當(dāng)前數(shù)字最接近的整數(shù))來(lái)處理Math.random的結(jié)果。

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

將隨機(jī)數(shù)乘以 10 可以得到一個(gè)在 0 到 10 之間的數(shù)字。由于Math.floor是向下取整,因此該函數(shù)會(huì)等概率地取到 0 到 9 中的任何一個(gè)數(shù)字。

還有兩個(gè)函數(shù),分別是Math.ceil(向上取整)和Math.round(四舍五入)。以及Math.abs,它取數(shù)字的絕對(duì)值,這意味著它反轉(zhuǎn)了負(fù)值,但保留了正值。

解構(gòu)

讓我們暫時(shí)回顧phi函數(shù):

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]));
}

這個(gè)函數(shù)難以閱讀的原因之一,是我們有一個(gè)指向數(shù)組的綁定,但我們更愿意擁有數(shù)組的元素的綁定,即let n00 = table [0]以及其他。 幸運(yùn)的是,有一種簡(jiǎn)潔的方法可以在 JavaScript 中執(zhí)行此操作。

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

這也適用于由let,varconst創(chuàng)建的綁定。 如果你知道要綁定的值是一個(gè)數(shù)組,則可以使用方括號(hào)來(lái)“向內(nèi)查看”該值,并綁定其內(nèi)容。

類(lèi)似的技巧適用于對(duì)象,使用大括號(hào)代替方括號(hào)。

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

請(qǐng)注意,如果嘗試解構(gòu)nullundefined,則會(huì)出現(xiàn)錯(cuò)誤,就像直接嘗試訪問(wèn)這些值的屬性一樣。

JSON

因?yàn)閷傩灾皇遣东@了它們的值,而不是包含它們,對(duì)象和數(shù)組在計(jì)算機(jī)的內(nèi)存中儲(chǔ)存為字節(jié)序列,存放它們的內(nèi)容的地址(內(nèi)存中的位置)。 因此,包含另一個(gè)數(shù)組的數(shù)組,(至少)由兩個(gè)內(nèi)存區(qū)域組成,一個(gè)用于內(nèi)部數(shù)組,另一個(gè)用于外部數(shù)組,(除了其它東西之外)其中包含表示內(nèi)部數(shù)組位置的二進(jìn)制數(shù)。

如果你想稍后將數(shù)據(jù)保存到文件中,或者通過(guò)網(wǎng)絡(luò)將其發(fā)送到另一臺(tái)計(jì)算機(jī),則必須以某種方式,將這些內(nèi)存地址的線團(tuán)轉(zhuǎn)換為可以存儲(chǔ)或發(fā)送的描述。 我想你應(yīng)該把你的整個(gè)計(jì)算機(jī)內(nèi)存,連同你感興趣的值的地址一起發(fā)送,但這似乎并不是最好的方法。

我們可以做的是序列化數(shù)據(jù)。 這意味著它被轉(zhuǎn)換為扁平的描述。 流行的序列化格式稱(chēng)為 JSON(發(fā)音為“Jason”),它代表 JavaScript Object Notation(JavaScript 對(duì)象表示法)。 它被廣泛用作 Web 上的數(shù)據(jù)存儲(chǔ)和通信格式,即使在 JavaScript 以外的語(yǔ)言中也是如此。

JSON 看起來(lái)像 JavaScript 的數(shù)組和對(duì)象的表示方式,但有一些限制。 所有屬性名都必須用雙引號(hào)括起來(lái),并且只允許使用簡(jiǎn)單的數(shù)據(jù)表達(dá)式 - 沒(méi)有函數(shù)調(diào)用,綁定或任何涉及實(shí)際計(jì)算的內(nèi)容。 JSON 中不允許注釋。

表示為 JSON 數(shù)據(jù)時(shí),日記條目可能看起來(lái)像這樣

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

JavaScript 為我們提供了函數(shù)JSON.stringifyJSON.parse,來(lái)將數(shù)據(jù)轉(zhuǎn)換為這種格式,以及從這種格式轉(zhuǎn)換。 第一個(gè)函數(shù)接受 JavaScript 值并返回 JSON 編碼的字符串。 第二個(gè)函數(shù)接受這樣的字符串并將其轉(zhuǎn)換為它編碼的值。

let string = JSON.stringify({squirrel: false,
                             events: ["weekend"]});
console.log(string);
// → {"squirrel":false,"events":["weekend"]}
console.log(JSON.parse(string).events);
// → ["weekend"]
本章小結(jié)

對(duì)象和數(shù)組(一種特殊對(duì)象)可以將幾個(gè)值組合起來(lái)形成一個(gè)新的值。理論上說(shuō),我們可以將一組相關(guān)的元素打包成一個(gè)對(duì)象,并通過(guò)這個(gè)對(duì)象來(lái)訪問(wèn)這些元素,以避免管理那些支離破碎的元素。

在 JavaScript 中,除了nullundefined以外,絕大多數(shù)的值都含有屬性。我們可以用value.propvalue["prop"]來(lái)訪問(wèn)屬性。對(duì)象使用名稱(chēng)來(lái)定義和存儲(chǔ)一定數(shù)量的屬性。另外,數(shù)組中通常會(huì)包含不同數(shù)量的值,并使用數(shù)字(從 0 開(kāi)始)作為這些值的屬性。

在數(shù)組中有一些具名屬性,比如length和一些方法。方法是作為屬性存在的函數(shù),常常作用于其所屬的值。

你可以使用特殊類(lèi)型的for循環(huán)for (let element of array)來(lái)迭代數(shù)組。

習(xí)題 范圍的和

在本書(shū)的前言中,提到過(guò)一種很好的計(jì)算固定范圍內(nèi)數(shù)字之和的方法:

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

編寫(xiě)一個(gè)range函數(shù),接受兩個(gè)參數(shù):startend,然后返回包含startend(包括end)之間的所有數(shù)字。

接著,編寫(xiě)一個(gè)sum函數(shù),接受一個(gè)數(shù)字?jǐn)?shù)組,并返回所有數(shù)字之和。運(yùn)行示例程序,檢查一下結(jié)果是不是 55。

附加題是修改range函數(shù),接受第 3 個(gè)可選參數(shù),指定構(gòu)建數(shù)組時(shí)的步長(zhǎng)(step)。如果沒(méi)有指定步長(zhǎng),構(gòu)建數(shù)組時(shí),每步增長(zhǎng) 1,和舊函數(shù)行為一致。調(diào)用函數(shù)range(1, 10, 2),應(yīng)該返回[1, 3, 5, 7, 9]。另外確保步數(shù)值為負(fù)數(shù)時(shí)也可以正常工作,因此range(5, 2, -1)應(yīng)該產(chǎn)生[5, 4, 3, 2]

// 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
逆轉(zhuǎn)數(shù)組

數(shù)組有一個(gè)reverse方法,它可以逆轉(zhuǎn)數(shù)組中元素的次序。在本題中,編寫(xiě)兩個(gè)函數(shù),reverseArrayreverseArrayInPlace。第一個(gè)函數(shù)reverseArray接受一個(gè)數(shù)組作為參數(shù),返回一個(gè)新數(shù)組,并逆轉(zhuǎn)新數(shù)組中的元素次序。第二個(gè)函數(shù)reverseArrayInPlace與第一個(gè)函數(shù)的功能相同,但是直接將數(shù)組作為參數(shù)進(jìn)行修改來(lái),逆轉(zhuǎn)數(shù)組中的元素次序。兩者都不能使用標(biāo)準(zhǔn)的reverse方法。

回想一下,在上一章中關(guān)于副作用和純函數(shù)的討論,哪個(gè)函數(shù)的寫(xiě)法的應(yīng)用場(chǎng)景更廣?哪個(gè)執(zhí)行得更快?

// 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]
實(shí)現(xiàn)列表

對(duì)象作為一個(gè)值的容器,它可以用來(lái)構(gòu)建各種各樣的數(shù)據(jù)結(jié)構(gòu)。有一種通用的數(shù)據(jù)結(jié)構(gòu)叫作列表(list)(不要與數(shù)組混淆)。列表是一種嵌套對(duì)象集合,第一個(gè)對(duì)象擁有第二個(gè)對(duì)象的引用,而第二個(gè)對(duì)象有第三個(gè)對(duì)象的引用,依此類(lèi)推。

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

最后產(chǎn)生的對(duì)象形成了一條鏈,如下圖所示:

使用列表的一個(gè)好處是,它們之間可以共享相同的子列表。舉個(gè)例子,如果我們新建了兩個(gè)值:{value: 0,result: list}{value: -1,result: list}(list引用了我們前面定義的綁定)。這是兩個(gè)獨(dú)立的列表,但它們之間卻共享了同一個(gè)數(shù)據(jù)結(jié)構(gòu),該數(shù)據(jù)結(jié)構(gòu)包含列表末尾的三個(gè)元素。而且我們前面定義的list仍然是包含三個(gè)元素的列表。

編寫(xiě)一個(gè)函數(shù)arrayToList,當(dāng)給定參數(shù)[1, 2, 3]時(shí),建立一個(gè)和示例相似的數(shù)據(jù)結(jié)構(gòu)。然后編寫(xiě)一個(gè)listToArray函數(shù),將列表轉(zhuǎn)換成數(shù)組。再編寫(xiě)一個(gè)工具函數(shù)prepend,接受一個(gè)元素和一個(gè)列表,然后創(chuàng)建一個(gè)新的列表,將元素添加到輸入列表的開(kāi)頭。最后編寫(xiě)一個(gè)函數(shù)nth,接受一個(gè)列表和一個(gè)數(shù),并返回列表中指定位置的元素,如果該元素不存在則返回undefined

如果你覺(jué)得這都不是什么難題,那么編寫(xiě)一個(gè)遞歸版本的nth函數(shù)。

// 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
深層比較

==運(yùn)算符可以判斷對(duì)象是否相等。但有些時(shí)候,你希望比較的是對(duì)象中實(shí)際屬性的值。

編寫(xiě)一個(gè)函數(shù)deepEqual,接受兩個(gè)參數(shù),若兩個(gè)對(duì)象是同一個(gè)值或兩個(gè)對(duì)象中有相同屬性,且使用deepEqual比較屬性值均返回true時(shí),返回true。

為了弄清楚通過(guò)身份(使用===運(yùn)算符)還是其屬性比較兩個(gè)值,可以使用typeof運(yùn)算符。如果對(duì)兩個(gè)值使用typeof均返回"object",則說(shuō)明你應(yīng)該進(jìn)行深層比較。但需要考慮一個(gè)例外的情況:由于歷史原因,typeof null也會(huì)返回"object"。

當(dāng)你需要查看對(duì)象的屬性來(lái)進(jìn)行比較時(shí),Object.keys函數(shù)將非常有用。

// 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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/105025.html

相關(guān)文章

  • JavaScript 編程精解 中文三版 十九、項(xiàng)目:像素藝術(shù)編輯器

    摘要:相反,當(dāng)響應(yīng)指針事件時(shí),它會(huì)調(diào)用創(chuàng)建它的代碼提供的回調(diào)函數(shù),該函數(shù)將處理應(yīng)用的特定部分?;卣{(diào)函數(shù)可能會(huì)返回另一個(gè)回調(diào)函數(shù),以便在按下按鈕并且將指針移動(dòng)到另一個(gè)像素時(shí)得到通知。它們?yōu)榻M件構(gòu)造器的數(shù)組而提供。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Pixel Art Editor 譯者:飛龍 協(xié)議:CC BY-NC-SA 4...

    Meils 評(píng)論0 收藏0
  • JavaScript 編程精解 中文三版 十二、項(xiàng)目:編程語(yǔ)言

    摘要:來(lái)源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版確定編程語(yǔ)言中的表達(dá)式含義的求值器只是另一個(gè)程序。若文本不是一個(gè)合法程序,解析器應(yīng)該指出錯(cuò)誤。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Programming Language 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用...

    Near_Li 評(píng)論0 收藏0
  • JavaScript 編程精解 中文三版 五、高階函數(shù)

    摘要:高階函數(shù)如果一個(gè)函數(shù)操作其他函數(shù),即將其他函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為返回值,那么我們可以將其稱(chēng)為高階函數(shù)。我們可以使用高階函數(shù)對(duì)一系列操作和值進(jìn)行抽象。高階函數(shù)有多種表現(xiàn)形式。腳本數(shù)據(jù)集數(shù)據(jù)處理是高階函數(shù)表現(xiàn)突出的一個(gè)領(lǐng)域。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Higher-Order Functions 譯者:飛龍 協(xié)議:CC BY-NC-...

    blastz 評(píng)論0 收藏0
  • JavaScript 編程精解 中文三版 七、項(xiàng)目:機(jī)器人

    摘要:來(lái)源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯置疑計(jì)算機(jī)能不能思考就相當(dāng)于置疑潛艇能不能游泳。這張圖將成為我們的機(jī)器人在其中移動(dòng)的世界。機(jī)器人在收到包裹時(shí)拾取包裹,并在抵達(dá)目的地時(shí)將其送達(dá)。這個(gè)機(jī)器人已經(jīng)快了很多。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Robot 譯者:飛龍 協(xié)議:CC BY-NC-S...

    jas0n 評(píng)論0 收藏0
  • JavaScript 編程精解 中文三版 零、前言

    摘要:來(lái)源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版,這是一本關(guān)于指導(dǎo)電腦的書(shū)。在可控的范圍內(nèi)編寫(xiě)程序是編程過(guò)程中首要解決的問(wèn)題。我們可以用中文來(lái)描述這些指令將數(shù)字存儲(chǔ)在內(nèi)存地址中的位置。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地...

    sanyang 評(píng)論0 收藏0
  • JavaScript 編程精解 中文三版 十三、瀏覽器中的 JavaScript

    摘要:在本例中,使用屬性指定鏈接的目標(biāo),其中表示超文本鏈接。您應(yīng)該認(rèn)為和元數(shù)據(jù)隱式出現(xiàn)在示例中,即使它們沒(méi)有實(shí)際顯示在文本中。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:JavaScript and the Browser 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《JavaScript 編程精解(第 2 版)》 ...

    zhiwei 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

kamushin233

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<