国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript 編程精解 中文第三版 四、數據結構:對象和數組

kamushin233 / 3367人閱讀

摘要:本章將介紹基本的數據結構。松鼠人一般在晚上八點到十點之間,雅克就會變身成為一只毛茸茸的松鼠,尾巴上的毛十分濃密。我們將雅克的日記表示為對象數組。

來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項目

原文:Data Structures: Objects and Arrays

譯者:飛龍

協議: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)

數字,布爾和字符串是構建數據結構的原子。 不過,許多類型的信息都需要多個原子。 對象允許我們將值(包括其他對象)放到一起,來構建更復雜的結構。

我們迄今為止構建的程序,受到一個事實的限制,它們僅在簡單數據類型上運行。 本章將介紹基本的數據結構。 到最后,你會知道足夠多的東西,開始編寫有用的程序。

本章將著手于一個或多或少的實際編程示例,當概念適用于手頭問題時引入它們。 示例代碼通常基于本文前面介紹的函數和綁定。

松鼠人

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

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

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

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

他需要的第一個東西,是存儲這些信息的數據結構。

數據集

為了處理大量的數字數據,我們首先必須找到一種方法,將其在我們的機器內存中表示。 舉例來說,我們想要表示一組數字 2, 3, 5, 7 和 11。

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

幸運的是,JavaScript提供了一種數據類型,專門用于存儲一系列的值。我們將這種數據類型稱為數組,將一連串的值寫在方括號當中,值之間使用逗號(,)分隔。

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

我們同樣使用方括號來獲取數組當中的值。在表達式后緊跟一對方括號,并在方括號中填寫表達式,這將會在左側表達式里查找方括號中給定的索引所對應的值,并返回結果。

數組的第一個索引是零,而不是一。 所以第一個元素用listOfNumbers[0]獲取。 基于零的計數在技術上有著悠久的傳統,并且在某些方面意義很大,但需要一些時間來習慣。 將索引看作要跳過的項目數量,從數組的開頭計數。

屬性

在之前的章節(jié)中,我們已經看到了一些可疑的表達式,例如myString.length(獲取字符串的長度)和Math.max(最大值函數)。 這些表達式可以訪問某個值的屬性。 在第一個中,我們訪問myString中的length屬性。 第二個中,我們訪問Math對象(它是數學相關常量和函數的集合)中的名為max的屬性。

在 JavaScript 中,幾乎所有的值都有屬性。但nullundefined沒有。如果你嘗試訪問nullundefined的屬性,會得到一個錯誤提示。

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

在JavaScript中訪問屬性的兩種主要方式是點(.)和方括號([])。 value.xvalue [x]都可以訪問value屬性,但不一定是同一個屬性。 區(qū)別在于如何解釋x。 使用點時,點后面的單詞是該屬性的字面名稱。 使用方括號時,會求解括號內的表達式來獲取屬性名稱。 鑒于value.x獲取value的名為x的屬性,value [x]嘗試求解表達式x,并將結果轉換為字符串作為屬性名稱。

所以如果你知道你感興趣的屬性叫做color,那么你會寫value.color。 如果你想提取屬性由綁定i中保存的值命名,你可以寫value [i]。 屬性名稱是字符串。 它們可以是任何字符串,但點符號僅適用于看起來像有效綁定名的名稱。 所以如果你想訪問名為2John Doe的屬性,你必須使用方括號:value[2]value["John Doe"]

數組中的元素以數組屬性的形式存儲,使用數字作為屬性名稱。 因為你不能用點號來表示數字,并且通常想要使用一個保存索引的綁定,所以你必須使用括號來表達它們。

數組的length屬性告訴我們它有多少個元素。 這個屬性名是一個有效的綁定名,我們事先知道它的名字,所以為了得到一個數組的長度,通常寫array.length,因為它比array["length"]更容易編寫。

方法

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

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

每個字符串都有toUpperCase屬性。 調用時,它將返回所有字母轉換為大寫字符串的副本。 另外還有toLowerCase

有趣的是,雖然我們沒有在調用toUpperCase時傳遞任何參數,但該函數訪問了字符串"Doh",即被調用的屬性所屬的值。我們會在第 6 章中闡述這其中的原理。

我們通常將包含函數的屬性稱為某個值的方法。比如說,toUpperCase是字符串的一個方法。

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

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方法則相反,刪除數組中的最后一個值并將其返回。

這些有點愚蠢的名字是棧的傳統術語。 編程中的棧是一種數據結構,它允許你將值推入并按相反順序再次彈出,最后添加的內容首先被移除。 這些在編程中很常見 - 你可能還記得前一章中的函數調用棧,它是同一個想法的實例。

對象

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

對象類型的值是任意的屬性集合。 創(chuàng)建對象的一種方法是使用大括號作為表達式。

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"
};

這意味著大括號在 JavaScript 中有兩個含義。 在語句的開頭,他們起始了一個語句塊。 在任何其他位置,他們描述一個對象。 幸運的是,語句很少以花括號對象開始,因此這兩者之間的不明確性并不是什么大問題。

讀取一個不存在的屬性就會產生undefined

我們可以使用=運算符來給一個屬性表達式賦值。如果該屬性已經存在,那么這項操作就會替換原有的值。如果該屬性不存在,則會在目標對象中新建一個屬性。

簡要回顧我們的綁定的觸手模型 - 屬性綁定也類似。 他們捕獲值,但其他綁定和屬性可能會持有這些相同的值。 你可以將對象想象成有任意數量觸手的章魚,每個觸手上都有一個名字的紋身。

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

當應用于字符串和對象時,二元in運算符會告訴你該對象是否具有名稱為它的屬性。 將屬性設置為undefined,和實際刪除它的區(qū)別在于,在第一種情況下,對象仍然具有屬性(它只是沒有有意義的值),而在第二種情況下屬性不再存在,in會返回false

為了找出對象具有的屬性,可以使用Object.keys函數。 你給它一個對象,它返回一個字符串數組 - 對象的屬性名稱。

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

Object.assign函數可以將一個對象的所有屬性復制到另一個對象中。

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

然后,數組只是一種對象,專門用于存儲對象序列。 如果你求解typeof [],它會產生object。 你可以看到它們是長而平坦的章魚,它們的觸手整齊排列,并以數字標記。

我們將雅克的日記表示為對象數組。

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

我們現在即將開始真正的編程。 首先還有一個理論要理解。

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

對象的工作方式不同。你可以更改其屬性,使單個對象值在不同時間具有不同的內容。

當我們有兩個數字,120 和 120 時,我們可以將它們看作完全相同的數字,不管它們是否指向相同的物理位。 使用對象時,擁有同一個對象的兩個引用,和擁有包含相同屬性的兩個不同的對象,是有區(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綁定持有相同對象,這就是為什么改變object1會改變object2的值。 據說他們具有相同的身份。 綁定object3指向一個不同的對象,它最初包含的屬性與object1相同,但過著多帶帶的生活。

綁定可以是可變的或不變的,但這與它們的值的行為方式是分開的。 即使數值不變,你也可以使用let綁定來跟蹤一個變化的數字,通過修改綁定所指向的值。與之類似,雖然對象的const綁定本身不可改變,并且始終指向相同對象,該對象的內容可能會改變。

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

當你用 JavaScript 的==運算符比較對象時,它按照身份進行比較:僅當兩個對象的值嚴格相同時才產生true。 比較不同的對象會返回false,即使它們屬性相同。 JavaScript 中沒有內置的“深層”比較操作,它按照內容比較對象,但可以自己編寫它(這是本章末尾的一個練習)。

松鼠人的記錄

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

let journal = [];

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

請注意添加到日記中的對象看起來有點奇怪。 它不像events:events那樣聲明屬性,只是提供屬性名稱。 這是一個簡寫,意思一樣 - 如果大括號中的屬性名后面沒有值,它的值來自相同名稱的綁定。

那么,在每天晚上十點 -- 或者有時候是下一天的早晨,從它的書架頂部爬下來之后 -- 雅克記錄了這一天。

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

一旦他有了足夠的數據點,他打算使用統計學來找出哪些事件可能與變成松鼠有關。

關聯性是統計綁定之間的獨立性的度量。 統計綁定與編程綁定不完全相同。 在統計學中,你通常會有一組度量,并且每個綁定都根據每個度量來測量。 綁定之間的相關性通常表示為從 -1 到 1 的值。 相關性為零意味著綁定不相關。 相關性為一表明兩者完全相關 - 如果你知道一個,你也知道另一個。 負一意味著它們是完全相關的,但它們是相反的 - 當一個是真的時,另一個是假的。

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

我們可以將吃比薩的事件放在這樣的頻率表中,每個數字表示我們的度量中的組合的出現次數。

如果我們將那個表格稱為n,我們可以用下列公式自己算?

(如果你現在把這本書放下,專注于十年級數學課的可怕的再現,堅持住!我不打算用無休止的神秘符號折磨你 - 現在只有這一個公式。我們所做的就是把它變成 JavaScript。)

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

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

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

計算關聯性

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

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

這將?公式直接翻譯成 JavaScript。 Math.sqrt是平方根函數,由標準 JavaScript 環(huán)境中的Math對象提供。 我們必須在表格中添加兩個字段來獲取字段,例如n1因為行和或者列和不直接存儲在我們的數據結構中。

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

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

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]

數組擁有includes方法,檢查給定值是否存在于數組中。 該函數使用它來確定,對于某一天,感興趣的事件名稱是否在事件列表中。

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

我們現在有了我們計算個體相關性的所需工具。 剩下的唯一一步,就是為記錄的每種類型的事件找到關聯,看看是否有什么明顯之處。

數組循環(huán)

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

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

這種循環(huán)在經典的 JavaScript 中很常見 - 遍歷數組,一次一個元素會很常見,為此,你需要在數組長度上維護一個計數器,并依次選取每個元素。

在現代 JavaScript 中有一個更簡單的方法來編寫這樣的循環(huán)。

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

for循環(huán)看起來像這樣,在綁定定義之后用of這個詞時,它會遍歷of之后的給定值的元素。 這不僅適用于數組,而且適用于字符串和其他數據結構。 我們將在第 6 章中討論它的工作原理。

分析結果

我們需要計算數據集中發(fā)生的每種類型事件的相關性。 為此,我們首先需要尋找每種類型的事件。

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

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

使用它,我們可以看到所有的相關性。

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

絕大多數相關系數都趨近于 0。顯然,攝入胡蘿卜、面包或布丁并不會導致變身成松鼠。但是似乎在周末變身成松鼠的概率更高。讓我們過濾結果,來僅僅顯示大于 0.1 或小于 -0.1 的相關性。

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

這是一個強有力的結果。 這種現象正好發(fā)生在雅克吃花生并且沒有刷牙時。 如果他只是不注意口腔衛(wèi)生,他從來沒有注意到他的病痛。

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

幾年來,雅克過得越來越好。 但是在某個時候他失去了工作。 因為他生活在一個糟糕的國家,沒有工作就意味著沒有醫(yī)療服務,所以他被迫在一個馬戲團就業(yè),在那里他扮演的是不可思議的松鼠人,在每場演出前都用花生醬塞滿了它的嘴。

數組詳解

在完成本章之前,我想向你介紹幾個對象相關的概念。 我將首先介紹一些通常實用的數組方法。

我們在本章的前面已經了解了pushpop方法,分別用于在數組末尾添加或刪除元素。相應地,在數組的開頭添加或刪除元素的方法分別是unshiftshift

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

這個程序管理任務隊列。 你通過調用remember("groceries"),將任務添加到隊列的末尾,并且當你準備好執(zhí)行某些操作時,可以調用getTask()從隊列中獲取(并刪除)第一個項目。 rememberUrgently函數也添加任務,但將其添加到隊列的前面而不是隊列的后面。

有一個與indexOf方法類似的方法,叫lastIndexOf,只不過indexOf從數組第一個元素向后搜索,而lastIndexOf從最后一個元素向前搜索。

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

indexOflastIndexOf方法都有一個可選參數,可以用來指定搜索的起始位置。

另一個基本方法是slice,該方法接受一個起始索引和一個結束索引,然后返回數組中兩個索引范圍內的元素。起始索引元素包含在返回結果中,但結束索引元素不會包含在返回結果中。

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

如果沒有指定結束索引,slice會返回從起始位置之后的所有元素。你也可以省略起始索引來復制整個數組。

concat方法可用于將數組粘在一起,來創(chuàng)建一個新數組,類似于+運算符對字符串所做的操作。

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

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傳遞給一個不是數組的參數,該值將被添加到新數組中,就像它是單個元素的數組一樣。

字符串及其屬性

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

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

字符串、數字和布爾類型的值并不是對象,因此當你向這些值中添加屬性時 JavaScript 并不會報錯,但實際上你并沒有將這些屬性添加進去。前面說過,這些值是不變的,不能改變。

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

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

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

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

trim方法用于刪除字符串中開頭和結尾的空白符號(空格、換行符和制表符等符號)。

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

上一章中的zeroPad函數也作為方法存在。 它被稱為padStart,接受所需的長度和填充字符作為參數。

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

你可以使用split,在另一個字符串的每個出現位置分割一個字符串,然后再用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方法重復一個字符串,該方法創(chuàng)建一個新字符串,包含原始字符串的多個副本,并將其粘在一起。

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

我們已經看到了字符串類型的length屬性。 訪問字符串中的單個字符,看起來像訪問數組元素(有一個警告,我們將在第 5 章中討論)。

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

一個函數可以接受任意數量的參數。 例如,Math.max計算提供給它的參數的最大值。

為了編寫這樣一個函數,你需要在函數的最后一個參數之前放三個點,如下所示:

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

當這樣的函數被調用時,剩余參數綁定一個數組,包含所有其它參數。 如果之前有其他參數,它們的值不是該數組的一部分。 當它是唯一的參數時,如max中那樣,它將保存所有參數。

你可以使用類似的三點表示法,來使用參數數組調用函數。

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

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

方括號的數組表示法,同樣允許三點運算符將另一個數組展開到新數組中:

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

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

Math對象被用作一個容器來分組一堆相關的功能。 只有一個Math對象,它作為一個值幾乎沒有用處。 相反,它提供了一個命名空間,使所有這些函數和值不必是全局綁定。

過多的全局綁定會“污染”命名空間。全局綁定越多,就越有可能一不小心把某些綁定的值覆蓋掉。比如,我們可能想在程序中使用名為max的綁定,由于 JavaScript 將內置的max函數安全地放置在Math對象中,因此不必擔心max的值會被覆蓋。

當你去定義一個已經被使用的綁定名的時候,對于很多編程語言來說,都會阻止你這么做,至少會對這種行為發(fā)出警告。但是 JavaScript 不會,因此要小心這些陷阱。

讓我們來繼續(xù)了解Math對象。如果需要做三角運算,Math對象可以幫助到你,它包含cos(余弦)、sin(正弦)、tan(正切)和各自的反函數(acosasinatan)。Math.PI則表示數字π,或至少是 JavaScript 中的數字近似值。在傳統的程序設計當中,常量均以大寫來標注。

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}

如果你對正弦或余弦不大熟悉,不必擔心。我們會在第 13 章用到它們時,再做進一步解釋。

在上面的示例代碼中使用了Math.random。每次調用該函數時,會返回一個偽隨機數,范圍在 0(包括)到 1(不包括)之間。

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

雖然計算機是確定性的機器,但如果給定相同的輸入,它們總是以相同的方式作出反應 - 讓它們產生隨機顯示的數字是可能的。 為此,機器會維護一些隱藏的值,并且每當你請求一個新的隨機數時,它都會對該隱藏值執(zhí)行復雜的計算來創(chuàng)建一個新值。 它存儲一個新值并返回從中派生的一些數字。 這樣,它可以以隨機的方式產生新的,難以預測的數字。

如果我們想獲取一個隨機的整數而非小數,可以使用Math.floor(向下取整到與當前數字最接近的整數)來處理Math.random的結果。

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

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

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

解構

讓我們暫時回顧phi函數:

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 中執(zhí)行此操作。

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

這也適用于由letvarconst創(chuàng)建的綁定。 如果你知道要綁定的值是一個數組,則可以使用方括號來“向內查看”該值,并綁定其內容。

類似的技巧適用于對象,使用大括號代替方括號。

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

請注意,如果嘗試解構nullundefined,則會出現錯誤,就像直接嘗試訪問這些值的屬性一樣。

JSON

因為屬性只是捕獲了它們的值,而不是包含它們,對象和數組在計算機的內存中儲存為字節(jié)序列,存放它們的內容的地址(內存中的位置)。 因此,包含另一個數組的數組,(至少)由兩個內存區(qū)域組成,一個用于內部數組,另一個用于外部數組,(除了其它東西之外)其中包含表示內部數組位置的二進制數。

如果你想稍后將數據保存到文件中,或者通過網絡將其發(fā)送到另一臺計算機,則必須以某種方式,將這些內存地址的線團轉換為可以存儲或發(fā)送的描述。 我想你應該把你的整個計算機內存,連同你感興趣的值的地址一起發(fā)送,但這似乎并不是最好的方法。

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

JSON 看起來像 JavaScript 的數組和對象的表示方式,但有一些限制。 所有屬性名都必須用雙引號括起來,并且只允許使用簡單的數據表達式 - 沒有函數調用,綁定或任何涉及實際計算的內容。 JSON 中不允許注釋。

表示為 JSON 數據時,日記條目可能看起來像這樣

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

JavaScript 為我們提供了函數JSON.stringifyJSON.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 中,除了nullundefined以外,絕大多數的值都含有屬性。我們可以用value.propvalue["prop"]來訪問屬性。對象使用名稱來定義和存儲一定數量的屬性。另外,數組中通常會包含不同數量的值,并使用數字(從 0 開始)作為這些值的屬性。

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

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

習題 范圍的和

在本書的前言中,提到過一種很好的計算固定范圍內數字之和的方法:

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

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

接著,編寫一個sum函數,接受一個數字數組,并返回所有數字之和。運行示例程序,檢查一下結果是不是 55。

附加題是修改range函數,接受第 3 個可選參數,指定構建數組時的步長(step)。如果沒有指定步長,構建數組時,每步增長 1,和舊函數行為一致。調用函數range(1, 10, 2),應該返回[1, 3, 5, 7, 9]。另外確保步數值為負數時也可以正常工作,因此range(5, 2, -1)應該產生[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
逆轉數組

數組有一個reverse方法,它可以逆轉數組中元素的次序。在本題中,編寫兩個函數,reverseArrayreverseArrayInPlace。第一個函數reverseArray接受一個數組作為參數,返回一個新數組,并逆轉新數組中的元素次序。第二個函數reverseArrayInPlace與第一個函數的功能相同,但是直接將數組作為參數進行修改來,逆轉數組中的元素次序。兩者都不能使用標準的reverse方法。

回想一下,在上一章中關于副作用和純函數的討論,哪個函數的寫法的應用場景更廣?哪個執(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]
實現列表

對象作為一個值的容器,它可以用來構建各種各樣的數據結構。有一種通用的數據結構叫作列表(list)(不要與數組混淆)。列表是一種嵌套對象集合,第一個對象擁有第二個對象的引用,而第二個對象有第三個對象的引用,依此類推。

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

最后產生的對象形成了一條鏈,如下圖所示:

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

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

如果你覺得這都不是什么難題,那么編寫一個遞歸版本的nth函數。

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

==運算符可以判斷對象是否相等。但有些時候,你希望比較的是對象中實際屬性的值。

編寫一個函數deepEqual,接受兩個參數,若兩個對象是同一個值或兩個對象中有相同屬性,且使用deepEqual比較屬性值均返回true時,返回true

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

當你需要查看對象的屬性來進行比較時,Object.keys函數將非常有用。

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

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

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105025.html

相關文章

  • JavaScript 編程精解 中文三版 十九、項目:像素藝術編輯器

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

    Meils 評論0 收藏0
  • JavaScript 編程精解 中文三版 十二、項目:編程語言

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

    Near_Li 評論0 收藏0
  • JavaScript 編程精解 中文三版 五、高階函數

    摘要:高階函數如果一個函數操作其他函數,即將其他函數作為參數或將函數作為返回值,那么我們可以將其稱為高階函數。我們可以使用高階函數對一系列操作和值進行抽象。高階函數有多種表現形式。腳本數據集數據處理是高階函數表現突出的一個領域。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項目原文:Higher-Order Functions 譯者:飛龍 協議:CC BY-NC-...

    blastz 評論0 收藏0
  • JavaScript 編程精解 中文三版 七、項目:機器人

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

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

    摘要:來源編程精解中文第三版翻譯項目原文譯者飛龍協議自豪地采用谷歌翻譯部分參考了編程精解第版,這是一本關于指導電腦的書。在可控的范圍內編寫程序是編程過程中首要解決的問題。我們可以用中文來描述這些指令將數字存儲在內存地址中的位置。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項目原文:Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 自豪地...

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

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

    zhiwei 評論0 收藏0

發(fā)表評論

0條評論

kamushin233

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<