四耀里、數(shù)據(jù)結(jié)構(gòu):對象和數(shù)組
原文:Data Structures: Objects and Arrays
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
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)
https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/master/img/4-0.jpg
數(shù)字踩窖,布爾和字符串是構(gòu)建數(shù)據(jù)結(jié)構(gòu)的原子乓诽。 不過帜羊,許多類型的信息都需要多個原子。 對象允許我們將值(包括其他對象)放到一起鸠天,來構(gòu)建更復(fù)雜的結(jié)構(gòu)讼育。
我們迄今為止構(gòu)建的程序,受到一個事實的限制稠集,它們僅在簡單數(shù)據(jù)類型上運(yùn)行奶段。 本章將介紹基本的數(shù)據(jù)結(jié)構(gòu)。 到最后剥纷,你會知道足夠多的東西痹籍,開始編寫有用的程序。
本章將著手于一個或多或少的實際編程示例晦鞋,當(dāng)概念適用于手頭問題時引入它們蹲缠。 示例代碼通常基于本文前面介紹的函數(shù)和綁定鳖宾。
松鼠人
一般在晚上八點(diǎn)到十點(diǎn)之間吼砂,雅克就會變身成為一只毛茸茸的松鼠,尾巴上的毛十分濃密鼎文。
一方面,雅克非常高興他沒有變成經(jīng)典的狼人因俐。 與變成狼相比拇惋,變成松鼠的確會產(chǎn)生更少的問題。 他不必?fù)?dān)心偶然吃掉鄰居(那會很尷尬)抹剩,而是擔(dān)心被鄰居的貓吃掉撑帖。 他在橡木樹冠上的一個薄薄的樹枝上醒來,赤身裸體并迷失方向澳眷。在這兩次偶然之后胡嘿,他在晚上鎖上了房間的門窗,并在地板上放了幾個核桃钳踊,來使自己忙起來衷敌。
這就解決了貓和樹的問題勿侯。 但雅克寧愿完全擺脫他的狀況。 不規(guī)律發(fā)生的變身使他懷疑缴罗,它們可能會由某種東西觸發(fā)助琐。 有一段時間,他相信只有在他靠近橡樹的日子里才會發(fā)生面氓。 但是避開橡樹不能阻止這個問題兵钮。
雅克切換到了更科學(xué)的方法,開始每天記錄他在某一天所做的每件事舌界,以及他是否變身掘譬。 有了這些數(shù)據(jù),他希望能夠縮小觸發(fā)變身的條件呻拌。
他需要的第一個東西葱轩,是存儲這些信息的數(shù)據(jù)結(jié)構(gòu)。
數(shù)據(jù)集
為了處理大量的數(shù)字?jǐn)?shù)據(jù)柏锄,我們首先必須找到一種方法酿箭,將其在我們的機(jī)器內(nèi)存中表示。 舉例來說趾娃,我們想要表示一組數(shù)字 2, 3, 5, 7 和 11缭嫡。
我們可以用字符串來創(chuàng)建 - 畢竟,字符串可以有任意長度抬闷,所以我們可以把大量數(shù)據(jù)放入它們中妇蛀,并使用"2 3 5 7 11"
作為我們的表示。 但這很笨拙笤成。 你必須以某種方式提取數(shù)字评架,并將它們轉(zhuǎn)換回數(shù)字才能訪問它們。
幸運(yùn)的是炕泳,JavaScript提供了一種數(shù)據(jù)類型纵诞,專門用于存儲一系列的值。我們將這種數(shù)據(jù)類型稱為數(shù)組培遵,將一連串的值寫在方括號當(dāng)中浙芙,值之間使用逗號(,
)分隔。
let listOfNumbers = [2, 3, 5, 7, 11];
console.log(listOfNumbers[2]);
// → 5
console.log(listOfNumbers[0]);
// → 2
console.log(listOfNumbers[2 - 1]);
// → 3
我們同樣使用方括號來獲取數(shù)組當(dāng)中的值籽腕。在表達(dá)式后緊跟一對方括號嗡呼,并在方括號中填寫表達(dá)式,這將會在左側(cè)表達(dá)式里查找方括號中給定的索引所對應(yīng)的值皇耗,并返回結(jié)果南窗。
數(shù)組的第一個索引是零,而不是一。 所以第一個元素用listOfNumbers[0]
獲取万伤。 基于零的計數(shù)在技術(shù)上有著悠久的傳統(tǒng)窒悔,并且在某些方面意義很大,但需要一些時間來習(xí)慣壕翩。 將索引看作要跳過的項目數(shù)量蛉迹,從數(shù)組的開頭計數(shù)。
屬性
在之前的章節(jié)中放妈,我們已經(jīng)看到了一些可疑的表達(dá)式北救,例如myString.length
(獲取字符串的長度)和Math.max
(最大值函數(shù))。 這些表達(dá)式可以訪問某個值的屬性芜抒。 在第一個中珍策,我們訪問myString
中的length
屬性。 第二個中宅倒,我們訪問Math
對象(它是數(shù)學(xué)相關(guān)常量和函數(shù)的集合)中的名為max
的屬性攘宙。
在 JavaScript 中,幾乎所有的值都有屬性拐迁。但null
和undefined
沒有蹭劈。如果你嘗試訪問null
和undefined
的屬性,會得到一個錯誤提示线召。
null.length;
// → TypeError: null has no properties
在JavaScript中訪問屬性的兩種主要方式是點(diǎn)(.
)和方括號([]
)铺韧。 value.x
和value [x]
都可以訪問value
屬性,但不一定是同一個屬性缓淹。 區(qū)別在于如何解釋x
哈打。 使用點(diǎn)時,點(diǎn)后面的單詞是該屬性的字面名稱讯壶。 使用方括號時料仗,會求解括號內(nèi)的表達(dá)式來獲取屬性名稱。 鑒于value.x
獲取value
的名為x
的屬性伏蚊,value [x]
嘗試求解表達(dá)式x
立轧,并將結(jié)果轉(zhuǎn)換為字符串作為屬性名稱。
所以如果你知道你感興趣的屬性叫做color
躏吊,那么你會寫value.color
肺孵。 如果你想提取屬性由綁定i
中保存的值命名,你可以寫value [i]
颜阐。 屬性名稱是字符串。 它們可以是任何字符串吓肋,但點(diǎn)符號僅適用于看起來像有效綁定名的名稱凳怨。 所以如果你想訪問名為2
或John Doe
的屬性,你必須使用方括號:value[2]
或value["John Doe"]
。
數(shù)組中的元素以數(shù)組屬性的形式存儲肤舞,使用數(shù)字作為屬性名稱紫新。 因為你不能用點(diǎn)號來表示數(shù)字,并且通常想要使用一個保存索引的綁定李剖,所以你必須使用括號來表達(dá)它們芒率。
數(shù)組的length
屬性告訴我們它有多少個元素。 這個屬性名是一個有效的綁定名篙顺,我們事先知道它的名字偶芍,所以為了得到一個數(shù)組的長度,通常寫array.length
德玫,因為它比array["length"]
更容易編寫匪蟀。
方法
了length
屬性之外,字符串和數(shù)組對象都包含一些持有函數(shù)值的屬性宰僧。
let doh = "Doh";
console.log(typeof doh.toUpperCase);
// → function
console.log(doh.toUpperCase());
// → DOH
每個字符串都有toUpperCase
屬性材彪。 調(diào)用時,它將返回所有字母轉(zhuǎn)換為大寫字符串的副本琴儿。 另外還有toLowerCase
段化。
有趣的是,雖然我們沒有在調(diào)用toUpperCase
時傳遞任何參數(shù)造成,但該函數(shù)訪問了字符串"Doh"
显熏,即被調(diào)用的屬性所屬的值。我們會在第 6 章中闡述這其中的原理谜疤。
我們通常將包含函數(shù)的屬性稱為某個值的方法佃延。比如說,toUpperCase
是字符串的一個方法夷磕。
此示例演示了兩種方法履肃,可用于操作數(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ù)組中的最后一個值并將其返回尺棋。
這些有點(diǎn)愚蠢的名字是棧的傳統(tǒng)術(shù)語。 編程中的棧是一種數(shù)據(jù)結(jié)構(gòu)绵跷,它允許你將值推入并按相反順序再次彈出膘螟,最后添加的內(nèi)容首先被移除。 這些在編程中很常見 - 你可能還記得前一章中的函數(shù)調(diào)用棧碾局,它是同一個想法的實例荆残。
對象
回到松鼠人的示例。 一組每日的日志條目可以表示為一個數(shù)組净当。 但是這些條目并不僅僅由一個數(shù)字或一個字符串組成 - 每個條目需要存儲一系列活動和一個布爾值内斯,表明雅克是否變成了松鼠蕴潦。 理想情況下,我們希望將它們組合成一個值俘闯,然后將這些分組的值放入日志條目的數(shù)組中潭苞。
對象類型的值是任意的屬性集合。 創(chuàng)建對象的一種方法是使用大括號作為表達(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
大括號內(nèi)有一列用逗號分隔的屬性此疹。 每個屬性都有一個名字,后跟一個冒號和一個值遮婶。 當(dāng)一個對象寫為多行時蝗碎,像這個例子那樣,對它進(jìn)行縮進(jìn)有助于提高可讀性蹭睡。 名稱不是有效綁定名稱或有效數(shù)字的屬性必須加引號衍菱。
let descriptions = {
work: "Went to work",
"touched tree": "Touched a tree"
};
這意味著大括號在 JavaScript 中有兩個含義。 在語句的開頭肩豁,他們起始了一個語句塊脊串。 在任何其他位置,他們描述一個對象清钥。 幸運(yùn)的是琼锋,語句很少以花括號對象開始,因此這兩者之間的不明確性并不是什么大問題祟昭。
讀取一個不存在的屬性就會產(chǎn)生undefined
缕坎。
我們可以使用=運(yùn)算符來給一個屬性表達(dá)式賦值。如果該屬性已經(jīng)存在篡悟,那么這項操作就會替換原有的值谜叹。如果該屬性不存在,則會在目標(biāo)對象中新建一個屬性搬葬。
簡要回顧我們的綁定的觸手模型 - 屬性綁定也類似荷腊。 他們捕獲值,但其他綁定和屬性可能會持有這些相同的值急凰。 你可以將對象想象成有任意數(shù)量觸手的章魚女仰,每個觸手上都有一個名字的紋身。
delete
運(yùn)算符切斷章魚的觸手抡锈。 這是一個一元運(yùn)算符疾忍,當(dāng)應(yīng)用于對象屬性時,將從對象中刪除指定的屬性床三。 這不是一件常見的事情一罩,但它是可能的。
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)用于字符串和對象時撇簿,二元in
運(yùn)算符會告訴你該對象是否具有名稱為它的屬性擒抛。 將屬性設(shè)置為undefined
推汽,和實際刪除它的區(qū)別在于,在第一種情況下歧沪,對象仍然具有屬性(它只是沒有有意義的值),而在第二種情況下屬性不再存在莲组,in
會返回false
诊胞。
為了找出對象具有的屬性,可以使用Object.keys
函數(shù)锹杈。 你給它一個對象撵孤,它返回一個字符串?dāng)?shù)組 - 對象的屬性名稱。
console.log(Object.keys({x: 0, y: 0, z: 2}));
// → ["x", "y", "z"]
Object.assign
函數(shù)可以將一個對象的所有屬性復(fù)制到另一個對象中竭望。
let objectA = {a: 1, b: 2};
bject.assign(objectA, {b: 3, c: 4});
console.log(objectA);
// → {a: 1, b: 3, c: 4}
然后邪码,數(shù)組只是一種對象,專門用于存儲對象序列咬清。 如果你求解typeof []
闭专,它會產(chǎn)生object
。 你可以看到它們是長而平坦的章魚旧烧,它們的觸手整齊排列影钉,并以數(shù)字標(biāo)記。
我們將雅克的日記表示為對象數(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)在即將開始真正的編程平委。 首先還有一個理論要理解。
我們看到對象值可以修改夺谁。 千米你的章節(jié)討論的值的類型(如數(shù)字廉赔,字符串和布爾值)都是不可變的 -- 這些類型的值不可能修改。 你可以將它們組合起來并從它們派生新的值匾鸥,但是當(dāng)你采用特定的字符串值時蜡塌,該值將始終保持不變。 里面的文字不能改變扫腺。 如果你有一個包含"cat"
的字符串岗照,其他代碼不可能修改你的字符串中的一個字符,來使它變成"rat"
笆环。
對象的工作方式不同攒至。你可以更改其屬性,使單個對象值在不同時間具有不同的內(nèi)容躁劣。
當(dāng)我們有兩個數(shù)字迫吐,120 和 120 時,我們可以將它們看作完全相同的數(shù)字账忘,不管它們是否指向相同的物理位志膀。 使用對象時熙宇,擁有同一個對象的兩個引用,和擁有包含相同屬性的兩個不同的對象溉浙,是有區(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
object1
和object2
綁定持有相同對象,這就是為什么改變object1
會改變object2
的值戳稽。 據(jù)說他們具有相同的身份馆蠕。 綁定object3
指向一個不同的對象,它最初包含的屬性與object1
相同惊奇,但過著單獨(dú)的生活互躬。
綁定可以是可變的或不變的,但這與它們的值的行為方式是分開的颂郎。 即使數(shù)值不變吼渡,你也可以使用let
綁定來跟蹤一個變化的數(shù)字,通過修改綁定所指向的值乓序。與之類似寺酪,雖然對象的const
綁定本身不可改變,并且始終指向相同對象竭缝,該對象的內(nèi)容可能會改變房维。
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)算符比較對象時,它按照身份進(jìn)行比較:僅當(dāng)兩個對象的值嚴(yán)格相同時才產(chǎn)生true
抬纸。 比較不同的對象會返回false
咙俩,即使它們屬性相同。 JavaScript 中沒有內(nèi)置的“深層”比較操作湿故,它按照內(nèi)容比較對象阿趁,但可以自己編寫它(這是本章末尾的一個練習(xí))。
松鼠人的記錄
于是坛猪,雅克開始了他的 JavaScript 之旅脖阵,并搭建了用于保存每天記錄的一套開發(fā)環(huán)境。
let journal = [];
function addEntry(events, squirrel) {
journal.push({events, squirrel});
}
請注意添加到日記中的對象看起來有點(diǎn)奇怪墅茉。 它不像events:events
那樣聲明屬性命黔,只是提供屬性名稱。 這是一個簡寫就斤,意思一樣 - 如果大括號中的屬性名后面沒有值悍募,它的值來自相同名稱的綁定。
那么洋机,在每天晚上十點(diǎn) -- 或者有時候是下一天的早晨坠宴,從它的書架頂部爬下來之后 -- 雅克記錄了這一天。
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)計學(xué)來找出哪些事件可能與變成松鼠有關(guān)喜鼓。
關(guān)聯(lián)性是統(tǒng)計綁定之間的獨(dú)立性的度量副砍。 統(tǒng)計綁定與編程綁定不完全相同。 在統(tǒng)計學(xué)中庄岖,你通常會有一組度量豁翎,并且每個綁定都根據(jù)每個度量來測量。 綁定之間的相關(guān)性通常表示為從 -1 到 1 的值顿锰。 相關(guān)性為零意味著綁定不相關(guān)谨垃。 相關(guān)性為一表明兩者完全相關(guān) - 如果你知道一個,你也知道另一個硼控。 負(fù)一意味著它們是完全相關(guān)的,但它們是相反的 - 當(dāng)一個是真的時胳赌,另一個是假的牢撼。
為了計算兩個布爾綁定之間的相關(guān)性度量,我們可以使用 phi 系數(shù)(?
)疑苫。 這是一個公式熏版,輸入為一個頻率表格,包含觀測綁定的不同組合的次數(shù)捍掺。 公式的輸出是 -1 和 1 之間的數(shù)字撼短。
我們可以將吃比薩的事件放在這樣的頻率表中,每個數(shù)字表示我們的度量中的組合的出現(xiàn)次數(shù)挺勿。
https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/master/img/4-1.svg
如果我們將那個表格稱為n
曲横,我們可以用下列公式自己算?
:
https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/master/img/4-2.jpg
(如果你現(xiàn)在把這本書放下,專注于十年級數(shù)學(xué)課的可怕的再現(xiàn)不瓶,堅持缀碳怠!我不打算用無休止的神秘符號折磨你 - 現(xiàn)在只有這一個公式蚊丐。我們所做的就是把它變成 JavaScript熙参。)
符號n01
表明, 第一個綁定(松鼠)為假(0)時麦备,第二個綁定(披薩)為真(1)孽椰。 在披薩表中,n01
是 9凛篙。
值n1
表示所有度量之和黍匾,其中第一個綁定為true
,在示例表中為 5鞋诗。 同樣膀捷,n0
表示所有度量之和,其中第二個綁定為假削彬。
因此全庸,我們以比薩表為例秀仲,除法線上方的部分(被除數(shù))為1×76–9×4=40
,而除法線下面的部分(除數(shù))則是10×80×5×85
的平方根壶笼,也就是√340000
神僵。計算結(jié)果為?≈0.069
,這個結(jié)果很小覆劈,因此吃比薩對是否變身成松鼠顯然沒有太大影響保礼。
計算關(guān)聯(lián)性
我們可以用包含 4 個元素的數(shù)組([76,9责语,4炮障,1]
)來表示一張 2 乘 2 的表格。我們也可以使用其他表示方式坤候,比如包含兩個數(shù)組的數(shù)組胁赢,每個子數(shù)組又包含兩個元素([[76,9]白筹,[4智末,1]]
)。也可以使用一個對象徒河,它包含一些屬性系馆,名為"11"
和"01"
。但是顽照,一維數(shù)組更為簡單由蘑,也容易進(jìn)行操作。我們可以將數(shù)組索引看成包含兩個二進(jìn)制位的數(shù)字棒厘,左邊的(高位)數(shù)字表示綁定“是否變成松鼠”纵穿,右邊的(低位)數(shù)字表示事件綁定。例如奢人,若二進(jìn)制數(shù)字為 10谓媒,表示雅克變成了松鼠,但事件并未發(fā)生(比如說吃比薩)何乎。這種情況發(fā)生了 4 次句惯。由于二進(jìn)制數(shù)字 10 的十進(jìn)制是 2,因此我們將其存儲到數(shù)組中索引為 2 的位置上支救。
下面這個函數(shù)用于計算數(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
對象提供各墨。 我們必須在表格中添加兩個字段來獲取字段指孤,例如n1
因為行和或者列和不直接存儲在我們的數(shù)據(jù)結(jié)構(gòu)中。
雅克花了三個月的時間記錄日志。在本章的代碼沙箱(http://eloquentjavascript.net/code/)的下載文件中恃轩,用JOURNAL
綁定存儲了該結(jié)果數(shù)據(jù)集合结洼。
若要從這篇記錄中提取出某個特定事件的 2 乘 2 表格,我們首先需要循環(huán)遍歷整個記錄叉跛,并計算出與變身成松鼠相關(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ù)使用它來確定鸣峭,對于某一天,感興趣的事件名稱是否在事件列表中酥艳。
tableFor
中的循環(huán)體通過檢查列表是否包含它感興趣的特定事件摊溶,以及該事件是否與松鼠事件一起發(fā)生,來計算每個日記條目在表格中的哪個盒子充石。 然后循環(huán)對表中的正確盒子加一更扁。
我們現(xiàn)在有了我們計算個體相關(guān)性的所需工具。 剩下的唯一一步赫冬,就是為記錄的每種類型的事件找到關(guān)聯(lián),看看是否有什么明顯之處。
數(shù)組循環(huán)
在tableFor
函數(shù)中,有一個這樣的循環(huán):
for (let i = 0; i < JOURNAL.length; i++) {
let entry = JOURNAL[i];
// Do something with entry
}
這種循環(huán)在經(jīng)典的 JavaScript 中很常見 - 遍歷數(shù)組藕畔,一次一個元素會很常見琼富,為此,你需要在數(shù)組長度上維護(hù)一個計數(shù)器娜搂,并依次選取每個元素。
在現(xiàn)代 JavaScript 中有一個更簡單的方法來編寫這樣的循環(huán)。
for (let entry of JOURNAL) {
console.log(`${entry.events.length} events.`);
}
當(dāng)for
循環(huán)看起來像這樣风范,在綁定定義之后用of
這個詞時,它會遍歷of
之后的給定值的元素沪么。 這不僅適用于數(shù)組硼婿,而且適用于字符串和其他數(shù)據(jù)結(jié)構(gòu)。 我們將在第 6 章中討論它的工作原理禽车。
分析結(jié)果
我們需要計算數(shù)據(jù)集中發(fā)生的每種類型事件的相關(guān)性寇漫。 為此,我們首先需要尋找每種類型的事件殉摔。
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
數(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碗硬。顯然瓤湘,攝入胡蘿卜瓢颅、面包或布丁并不會導(dǎo)致變身成松鼠。但是似乎在周末變身成松鼠的概率更高岭粤。讓我們過濾結(jié)果惜索,來僅僅顯示大于 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
啊哈剃浇!這里有兩個因素巾兆,其相關(guān)性明顯強(qiáng)于其他因素。 吃花生對變成松鼠的幾率有強(qiáng)烈的積極影響虎囚,而刷牙有顯著的負(fù)面影響角塑。
這太有意思了。讓我們再仔細(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
這是一個強(qiáng)有力的結(jié)果圃伶。 這種現(xiàn)象正好發(fā)生在雅克吃花生并且沒有刷牙時。 如果他只是不注意口腔衛(wèi)生蒲列,他從來沒有注意到他的病痛窒朋。
知道這些之后,雅克完全停止吃花生蝗岖,發(fā)現(xiàn)他的變身消失了侥猩。
幾年來,雅克過得越來越好抵赢。 但是在某個時候他失去了工作欺劳。 因為他生活在一個糟糕的國家,沒有工作就意味著沒有醫(yī)療服務(wù)铅鲤,所以他被迫在一個馬戲團(tuán)就業(yè)划提,在那里他扮演的是不可思議的松鼠人,在每場演出前都用花生醬塞滿了它的嘴邢享。
數(shù)組詳解
在完成本章之前鹏往,我想向你介紹幾個對象相關(guān)的概念。 我將首先介紹一些通常實用的數(shù)組方法驼仪。
我們在本章的前面已經(jīng)了解了push
和pop
方法掸犬,分別用于在數(shù)組末尾添加或刪除元素。相應(yīng)地绪爸,在數(shù)組的開頭添加或刪除元素的方法分別是unshift
和shift
湾碎。
let todoList = [];
function remember(task) {
todoList.push(task);
}
function getTask() {
return todoList.shift();
}
function rememberUrgently(task) {
todoList.unshift(task);
}
這個程序管理任務(wù)隊列。 你通過調(diào)用remember("groceries")
奠货,將任務(wù)添加到隊列的末尾介褥,并且當(dāng)你準(zhǔn)備好執(zhí)行某些操作時,可以調(diào)用getTask()
從隊列中獲取(并刪除)第一個項目柔滔。 rememberUrgently
函數(shù)也添加任務(wù)溢陪,但將其添加到隊列的前面而不是隊列的后面。
有一個與indexOf
方法類似的方法睛廊,叫lastIndexOf
形真,只不過indexOf
從數(shù)組第一個元素向后搜索,而lastIndexOf
從最后一個元素向前搜索超全。
console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3
indexOf
和lastIndexOf
方法都有一個可選參數(shù)咆霜,可以用來指定搜索的起始位置。
另一個基本方法是slice
嘶朱,該方法接受一個起始索引和一個結(jié)束索引蛾坯,然后返回數(shù)組中兩個索引范圍內(nèi)的元素。起始索引元素包含在返回結(jié)果中疏遏,但結(jié)束索引元素不會包含在返回結(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]
如果沒有指定結(jié)束索引,slice
會返回從起始位置之后的所有元素财异。你也可以省略起始索引來復(fù)制整個數(shù)組倘零。
concat
方法可用于將數(shù)組粘在一起,來創(chuàng)建一個新數(shù)組戳寸,類似于+
運(yùn)算符對字符串所做的操作视事。
以下示例展示了concat
和slice
的作用。 它接受一個數(shù)組和一個索引庆揩,然后它返回一個新數(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
傳遞給一個不是數(shù)組的參數(shù)订晌,該值將被添加到新數(shù)組中,就像它是單個元素的數(shù)組一樣蚌吸。
字符串及其屬性
我們可以調(diào)用字符串的length
或toUpperCase
這樣的屬性锈拨,但不能向字符串中添加任何新的屬性。
let kim = "Kim";
kim.age = 88;
console.log(kim.age);
// → undefined
字符串羹唠、數(shù)字和布爾類型的值并不是對象奕枢,因此當(dāng)你向這些值中添加屬性時 JavaScript 并不會報錯,但實際上你并沒有將這些屬性添加進(jìn)去佩微。前面說過缝彬,這些值是不變的,不能改變哺眯。
但這些類型包含一些內(nèi)置屬性谷浅。每個字符串中包含了若干方法供我們使用,最有用的方法可能就是slice
和indexOf
了,它們的功能與數(shù)組中的同名方法類似一疯。
console.log("coconuts".slice(4, 7));
// → nut
console.log("coconut".indexOf("u"));
// → 5
一個區(qū)別是撼玄,字符串的indexOf
可以搜索包含多個字符的字符串,而相應(yīng)的數(shù)組方法僅查找單個元素墩邀。
console.log("one two three".indexOf("ee"));
// → 11
trim
方法用于刪除字符串中開頭和結(jié)尾的空白符號(空格掌猛、換行符和制表符等符號)。
console.log(" okay \n ".trim());
// → okay
上一章中的zeroPad
函數(shù)也作為方法存在眉睹。 它被稱為padStart
荔茬,接受所需的長度和填充字符作為參數(shù)。
console.log(String(6).padStart(3, "0"));
// → 006
你可以使用split
辣往,在另一個字符串的每個出現(xiàn)位置分割一個字符串兔院,然后再用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ù)一個字符串站削,該方法創(chuàng)建一個新字符串坊萝,包含原始字符串的多個副本,并將其粘在一起许起。
console.log("LA".repeat(3));
// → LALALA
我們已經(jīng)看到了字符串類型的length
屬性十偶。 訪問字符串中的單個字符,看起來像訪問數(shù)組元素(有一個警告园细,我們將在第 5 章中討論)惦积。
let string = "abc";
console.log(string.length);
// → 3
console.log(string[1]);
// → b
剩余參數(shù)
一個函數(shù)可以接受任意數(shù)量的參數(shù)。 例如猛频,Math.max
計算提供給它的參數(shù)的最大值狮崩。
為了編寫這樣一個函數(shù),你需要在函數(shù)的最后一個參數(shù)之前放三個點(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ù)組,包含所有其它參數(shù)毡熏。 如果之前有其他參數(shù)坦敌,它們的值不是該數(shù)組的一部分。 當(dāng)它是唯一的參數(shù)時痢法,如max
中那樣狱窘,它將保存所有參數(shù)。
你可以使用類似的三點(diǎn)表示法财搁,來使用參數(shù)數(shù)組調(diào)用函數(shù)蘸炸。
let numbers = [5, 1, 7];
console.log(max(...numbers));
// → 7
這在函數(shù)調(diào)用中“展開”數(shù)組,并將其元素傳遞為單獨(dú)的參數(shù)尖奔。 像`max(9, ...numbers, 2)'那樣幻馁,可以包含像這樣的數(shù)組以及其他參數(shù)洗鸵。
方括號的數(shù)組表示法,同樣允許三點(diǎn)運(yùn)算符將另一個數(shù)組展開到新數(shù)組中:
let words = ["never", "fully"];
console.log(["will", ...words, "understand"]);
// → ["will", "never", "fully", "understand"]
Math對象
正如我們所看到的那樣仗嗦,Math
對象中包含了許多與數(shù)字相關(guān)的工具函數(shù)膘滨,比如Math.max
(求最大值)、Math.min
(求最小值)和Math.sqrt
(求平方根)稀拐。
Math
對象被用作一個容器來分組一堆相關(guān)的功能火邓。 只有一個Math
對象,它作為一個值幾乎沒有用處德撬。 相反铲咨,它提供了一個命名空間,使所有這些函數(shù)和值不必是全局綁定蜓洪。
過多的全局綁定會“污染”命名空間纤勒。全局綁定越多,就越有可能一不小心把某些綁定的值覆蓋掉隆檀。比如摇天,我們可能想在程序中使用名為max
的綁定,由于 JavaScript 將內(nèi)置的max
函數(shù)安全地放置在Math
對象中恐仑,因此不必?fù)?dān)心max
的值會被覆蓋泉坐。
當(dāng)你去定義一個已經(jīng)被使用的綁定名的時候,對于很多編程語言來說裳仆,都會阻止你這么做腕让,至少會對這種行為發(fā)出警告。但是 JavaScript 不會歧斟,因此要小心這些陷阱纯丸。
讓我們來繼續(xù)了解Math
對象。如果需要做三角運(yùn)算静袖,Math
對象可以幫助到你液南,它包含cos
(余弦)、sin
(正弦)勾徽、tan
(正切)和各自的反函數(shù)(acos
、asin
和atan
)统扳。Math.PI
則表示數(shù)字π
喘帚,或至少是 JavaScript 中的數(shù)字近似值。在傳統(tǒng)的程序設(shè)計當(dāng)中咒钟,常量均以大寫來標(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}
如果你對正弦或余弦不大熟悉,不必?fù)?dān)心朱嘴。我們會在第 13 章用到它們時倾鲫,再做進(jìn)一步解釋粗合。
在上面的示例代碼中使用了Math.random
。每次調(diào)用該函數(shù)時乌昔,會返回一個偽隨機(jī)數(shù)隙疚,范圍在 0(包括)到 1(不包括)之間。
console.log(Math.random());
// → 0.36993729369714856
console.log(Math.random());
// → 0.727367032552138
console.log(Math.random());
// → 0.40180766698904335
雖然計算機(jī)是確定性的機(jī)器磕道,但如果給定相同的輸入供屉,它們總是以相同的方式作出反應(yīng) - 讓它們產(chǎn)生隨機(jī)顯示的數(shù)字是可能的。 為此溺蕉,機(jī)器會維護(hù)一些隱藏的值伶丐,并且每當(dāng)你請求一個新的隨機(jī)數(shù)時,它都會對該隱藏值執(zhí)行復(fù)雜的計算來創(chuàng)建一個新值疯特。 它存儲一個新值并返回從中派生的一些數(shù)字哗魂。 這樣,它可以以隨機(jī)的方式產(chǎn)生新的漓雅,難以預(yù)測的數(shù)字录别。
如果我們想獲取一個隨機(jī)的整數(shù)而非小數(shù),可以使用Math.floor
(向下取整到與當(dāng)前數(shù)字最接近的整數(shù))來處理Math.random
的結(jié)果故硅。
console.log(Math.floor(Math.random() * 10));
// → 2
將隨機(jī)數(shù)乘以 10 可以得到一個在 0 到 10 之間的數(shù)字庶灿。由于Math.floor
是向下取整,因此該函數(shù)會等概率地取到 0 到 9 中的任何一個數(shù)字吃衅。
還有兩個函數(shù)往踢,分別是Math.ceil
(向上取整)和Math.round
(四舍五入)。以及Math.abs
徘层,它取數(shù)字的絕對值峻呕,這意味著它反轉(zhuǎn)了負(fù)值,但保留了正值趣效。
解構(gòu)
讓我們暫時回顧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]));
}
這個函數(shù)難以閱讀的原因之一瘦癌,是我們有一個指向數(shù)組的綁定,但我們更愿意擁有數(shù)組的元素的綁定跷敬,即let n00 = table [0]
以及其他讯私。 幸運(yù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
斤寇,var
或const
創(chuàng)建的綁定。 如果你知道要綁定的值是一個數(shù)組拥褂,則可以使用方括號來“向內(nèi)查看”該值娘锁,并綁定其內(nèi)容。
類似的技巧適用于對象饺鹃,使用大括號代替方括號莫秆。
let {name} = {name: "Faraji", age: 23};
console.log(name);
// → Faraji
請注意间雀,如果嘗試解構(gòu)null
或undefined
,則會出現(xiàn)錯誤镊屎,就像直接嘗試訪問這些值的屬性一樣惹挟。
JSON
因為屬性只是捕獲了它們的值,而不是包含它們杯道,對象和數(shù)組在計算機(jī)的內(nèi)存中儲存為字節(jié)序列匪煌,存放它們的內(nèi)容的地址(內(nèi)存中的位置)。 因此党巾,包含另一個數(shù)組的數(shù)組萎庭,(至少)由兩個內(nèi)存區(qū)域組成,一個用于內(nèi)部數(shù)組齿拂,另一個用于外部數(shù)組驳规,(除了其它東西之外)其中包含表示內(nèi)部數(shù)組位置的二進(jìn)制數(shù)。
如果你想稍后將數(shù)據(jù)保存到文件中署海,或者通過網(wǎng)絡(luò)將其發(fā)送到另一臺計算機(jī)吗购,則必須以某種方式,將這些內(nèi)存地址的線團(tuán)轉(zhuǎn)換為可以存儲或發(fā)送的描述砸狞。 我想你應(yīng)該把你的整個計算機(jī)內(nèi)存捻勉,連同你感興趣的值的地址一起發(fā)送,但這似乎并不是最好的方法刀森。
我們可以做的是序列化數(shù)據(jù)踱启。 這意味著它被轉(zhuǎn)換為扁平的描述。 流行的序列化格式稱為 JSON(發(fā)音為“Jason”)研底,它代表 JavaScript Object Notation(JavaScript 對象表示法)埠偿。 它被廣泛用作 Web 上的數(shù)據(jù)存儲和通信格式,即使在 JavaScript 以外的語言中也是如此榜晦。
JSON 看起來像 JavaScript 的數(shù)組和對象的表示方式冠蒋,但有一些限制。 所有屬性名都必須用雙引號括起來乾胶,并且只允許使用簡單的數(shù)據(jù)表達(dá)式 - 沒有函數(shù)調(diào)用抖剿,綁定或任何涉及實際計算的內(nèi)容。 JSON 中不允許注釋识窿。
表示為 JSON 數(shù)據(jù)時斩郎,日記條目可能看起來像這樣
{
"squirrel": false,
"events": ["work", "touched tree", "pizza", "running"]
}
JavaScript 為我們提供了函數(shù)JSON.stringify
和JSON.parse
,來將數(shù)據(jù)轉(zhuǎn)換為這種格式腕扶,以及從這種格式轉(zhuǎn)換。 第一個函數(shù)接受 JavaScript 值并返回 JSON 編碼的字符串吨掌。 第二個函數(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é)
對象和數(shù)組(一種特殊對象)可以將幾個值組合起來形成一個新的值脓恕。理論上說,我們可以將一組相關(guān)的元素打包成一個對象窿侈,并通過這個對象來訪問這些元素炼幔,以避免管理那些支離破碎的元素。
在 JavaScript 中史简,除了null
和undefined
以外乃秀,絕大多數(shù)的值都含有屬性。我們可以用value.prop
或value["prop"]
來訪問屬性圆兵。對象使用名稱來定義和存儲一定數(shù)量的屬性跺讯。另外,數(shù)組中通常會包含不同數(shù)量的值殉农,并使用數(shù)字(從 0 開始)作為這些值的屬性刀脏。
在數(shù)組中有一些具名屬性,比如length
和一些方法超凳。方法是作為屬性存在的函數(shù)愈污,常常作用于其所屬的值。
你可以使用特殊類型的for
循環(huán)for (let element of array)
來迭代數(shù)組轮傍。
習(xí)題
范圍的和
在本書的前言中暂雹,提到過一種很好的計算固定范圍內(nèi)數(shù)字之和的方法:
console.log(sum(range(1, 10)));
編寫一個range
函數(shù),接受兩個參數(shù):start
和end
创夜,然后返回包含start
到end
(包括end
)之間的所有數(shù)字杭跪。
接著,編寫一個sum
函數(shù)挥下,接受一個數(shù)字?jǐn)?shù)組揍魂,并返回所有數(shù)字之和。運(yùn)行示例程序棚瘟,檢查一下結(jié)果是不是 55现斋。
附加題是修改range
函數(shù),接受第 3 個可選參數(shù)偎蘸,指定構(gòu)建數(shù)組時的步長(step
)庄蹋。如果沒有指定步長,構(gòu)建數(shù)組時迷雪,每步增長 1限书,和舊函數(shù)行為一致。調(diào)用函數(shù)range(1, 10, 2)
章咧,應(yīng)該返回[1, 3, 5, 7, 9]
倦西。另外確保步數(shù)值為負(fù)數(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ù)組有一個reverse
方法扰柠,它可以逆轉(zhuǎn)數(shù)組中元素的次序粉铐。在本題中,編寫兩個函數(shù)卤档,reverseArray
和reverseArrayInPlace
蝙泼。第一個函數(shù)reverseArray
接受一個數(shù)組作為參數(shù),返回一個新數(shù)組劝枣,并逆轉(zhuǎn)新數(shù)組中的元素次序汤踏。第二個函數(shù)reverseArrayInPlace
與第一個函數(shù)的功能相同,但是直接將數(shù)組作為參數(shù)進(jìn)行修改來舔腾,逆轉(zhuǎn)數(shù)組中的元素次序溪胶。兩者都不能使用標(biāo)準(zhǔn)的reverse
方法。
回想一下琢唾,在上一章中關(guān)于副作用和純函數(shù)的討論载荔,哪個函數(shù)的寫法的應(yīng)用場景更廣?哪個執(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]
實現(xiàn)列表
對象作為一個值的容器懒熙,它可以用來構(gòu)建各種各樣的數(shù)據(jù)結(jié)構(gòu)。有一種通用的數(shù)據(jù)結(jié)構(gòu)叫作列表(list)(不要與數(shù)組混淆)普办。列表是一種嵌套對象集合工扎,第一個對象擁有第二個對象的引用,而第二個對象有第三個對象的引用衔蹲,依此類推肢娘。
let list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
最后產(chǎn)生的對象形成了一條鏈,如下圖所示:
https://raw.githubusercontent.com/wizardforcel/eloquent-js-3e-zh/master/img/4-3.svg
使用列表的一個好處是舆驶,它們之間可以共享相同的子列表橱健。舉個例子,如果我們新建了兩個值:{value: 0沙廉,result: list}
和{value: -1拘荡,result: list
}(list
引用了我們前面定義的綁定)。這是兩個獨(dú)立的列表撬陵,但它們之間卻共享了同一個數(shù)據(jù)結(jié)構(gòu)珊皿,該數(shù)據(jù)結(jié)構(gòu)包含列表末尾的三個元素。而且我們前面定義的list
仍然是包含三個元素的列表巨税。
編寫一個函數(shù)arrayToList
蟋定,當(dāng)給定參數(shù)[1, 2, 3]
時,建立一個和示例相似的數(shù)據(jù)結(jié)構(gòu)草添。然后編寫一個listToArray
函數(shù)驶兜,將列表轉(zhuǎn)換成數(shù)組。再編寫一個工具函數(shù)prepend
,接受一個元素和一個列表抄淑,然后創(chuàng)建一個新的列表犀盟,將元素添加到輸入列表的開頭。最后編寫一個函數(shù)nth
蝇狼,接受一個列表和一個數(shù),并返回列表中指定位置的元素倡怎,如果該元素不存在則返回undefined
迅耘。
如果你覺得這都不是什么難題,那么編寫一個遞歸版本的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)算符可以判斷對象是否相等颤专。但有些時候,你希望比較的是對象中實際屬性的值钠乏。
編寫一個函數(shù)deepEqual
栖秕,接受兩個參數(shù),若兩個對象是同一個值或兩個對象中有相同屬性晓避,且使用deepEqual
比較屬性值均返回true
時簇捍,返回true
。
為了弄清楚通過身份(使用===
運(yùn)算符)還是其屬性比較兩個值俏拱,可以使用typeof
運(yùn)算符暑塑。如果對兩個值使用typeof
均返回"object"
,則說明你應(yīng)該進(jìn)行深層比較锅必。但需要考慮一個例外的情況:由于歷史原因事格,typeof null
也會返回"object"
。
當(dāng)你需要查看對象的屬性來進(jìn)行比較時搞隐,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