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

資訊專(zhuān)欄INFORMATION COLUMN

JavaScript 編程精解 中文第三版 六、對(duì)象的秘密

ddongjian0000 / 3040人閱讀

摘要:在編程文化中,我們有一個(gè)名為面向?qū)ο缶幊痰臇|西,這是一組技術(shù),使用對(duì)象和相關(guān)概念作為程序組織的中心原則。這是構(gòu)造器函數(shù)的作用。因此,上面的類(lèi)聲明等同于上一節(jié)中的構(gòu)造器定義。

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

原文:The Secret Life of Objects

譯者:飛龍

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

自豪地采用谷歌翻譯

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

抽象數(shù)據(jù)類(lèi)型是通過(guò)編寫(xiě)一種特殊的程序來(lái)實(shí)現(xiàn)的,該程序根據(jù)可在其上執(zhí)行的操作來(lái)定義類(lèi)型。

Barbara Liskov,《Programming with Abstract Data Types》

第 4 章介紹了 JavaScript 的對(duì)象(object)。 在編程文化中,我們有一個(gè)名為面向?qū)ο缶幊蹋∣OP)的東西,這是一組技術(shù),使用對(duì)象(和相關(guān)概念)作為程序組織的中心原則。

雖然沒(méi)有人真正同意其精確定義,但面向?qū)ο缶幊桃呀?jīng)成為了許多編程語(yǔ)言的設(shè)計(jì),包括 JavaScript 在內(nèi)。 本章將描述這些想法在 JavaScript 中的應(yīng)用方式。

封裝

面向?qū)ο缶幊痰暮诵乃枷胧菍⒊绦蚍殖尚⌒推?,并讓每個(gè)片段負(fù)責(zé)管理自己的狀態(tài)。

通過(guò)這種方式,一些程序片段的工作方式的知識(shí)可以局部保留。 從事其他方面的工作的人,不必記住甚至不知道這些知識(shí)。 無(wú)論什么時(shí)候這些局部細(xì)節(jié)發(fā)生變化,只需要直接更新其周?chē)拇a。

這種程序的不同片段通過(guò)接口(interface),函數(shù)或綁定的有限集合交互,它以更抽象的級(jí)別提供有用的功能,并隱藏它的精確實(shí)現(xiàn)。

這些程序片段使用對(duì)象建模。 它們的接口由一組特定的方法(method)和屬性(property)組成。 接口的一部分的屬性稱(chēng)為公共的(public)。 其他外部代碼不應(yīng)該接觸屬性的稱(chēng)為私有的(private)。

許多語(yǔ)言提供了區(qū)分公共和私有屬性的方法,并且完全防止外部代碼訪問(wèn)私有屬性。 JavaScript 再次采用極簡(jiǎn)主義的方式,沒(méi)有。 至少目前還沒(méi)有 - 有個(gè)正在開(kāi)展的工作,將其添加到該語(yǔ)言中。

即使這種語(yǔ)言沒(méi)有內(nèi)置這種區(qū)別,JavaScript 程序員也成功地使用了這種想法。 通常,可用的接口在文檔或數(shù)字一中描述。 在屬性名稱(chēng)的的開(kāi)頭經(jīng)常會(huì)放置一個(gè)下劃線(_)字符,來(lái)表明這些屬性是私有的。

將接口與實(shí)現(xiàn)分離是一個(gè)好主意。 它通常被稱(chēng)為封裝(encapsulation)。

方法

方法不過(guò)是持有函數(shù)值的屬性。 這是一個(gè)簡(jiǎn)單的方法:

let rabbit = {};
rabbit.speak = function(line) {
  console.log(`The rabbit says "${line}"`);
};

rabbit.speak("I"m alive.");
// → The rabbit says "I"m alive."

方法通常會(huì)在對(duì)象被調(diào)用時(shí)執(zhí)行一些操作。將函數(shù)作為對(duì)象的方法調(diào)用時(shí),會(huì)找到對(duì)象中對(duì)應(yīng)的屬性并直接調(diào)用。當(dāng)函數(shù)作為方法調(diào)用時(shí),函數(shù)體內(nèi)叫做this的綁定自動(dòng)指向在它上面調(diào)用的對(duì)象。

function speak(line) {
  console.log(`The ${this.type} rabbit says "${line}"`);
}
let whiteRabbit = {type: "white", speak: speak};
let fatRabbit = {type: "fat", speak: speak};

whiteRabbit.speak("Oh my ears and whiskers, " +
                  "how late it"s getting!");
// → The white rabbit says "Oh my ears and whiskers, how
//   late it"s getting!"
hungryRabbit.speak("I could use a carrot right now.");
// → The hungry rabbit says "I could use a carrot right now."

你可以把this看作是以不同方式傳遞的額外參數(shù)。 如果你想顯式傳遞它,你可以使用函數(shù)的call方法,它接受this值作為第一個(gè)參數(shù),并將其它處理為看做普通參數(shù)。

speak.call(hungryRabbit, "Burp!");
// → The hungry rabbit says "Burp!"

這段代碼使用了關(guān)鍵字this來(lái)輸出正在說(shuō)話的兔子的種類(lèi)。我們回想一下applybind方法,這兩個(gè)方法接受的第一個(gè)參數(shù)可以用來(lái)模擬對(duì)象中方法的調(diào)用。這兩個(gè)方法會(huì)把第一個(gè)參數(shù)復(fù)制給this

由于每個(gè)函數(shù)都有自己的this綁定,它的值依賴(lài)于它的調(diào)用方式,所以在用function關(guān)鍵字定義的常規(guī)函數(shù)中,不能引用外層作用域的this。

箭頭函數(shù)是不同的 - 它們不綁定他們自己的this,但可以看到他們周?chē)ǘx位置)作用域的this綁定。 因此,你可以像下面的代碼那樣,在局部函數(shù)中引用this

function normalize() {
  console.log(this.coords.map(n => n / this.length));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → [0, 0.4, 0.6]

如果我使用function關(guān)鍵字將參數(shù)寫(xiě)入map,則代碼將不起作用。

原型

我們來(lái)仔細(xì)看看以下這段代碼。

let empty = {};
console.log(empty.toString);
// → function toString(){…}
console.log(empty.toString());
// → [object Object]

我從一個(gè)空對(duì)象中取出了一個(gè)屬性。 好神奇!

實(shí)際上并非如此。我只是掩蓋了一些 JavaScript 對(duì)象的內(nèi)部工作細(xì)節(jié)罷了。每個(gè)對(duì)象除了擁有自己的屬性外,都包含一個(gè)原型(prototype)。原型是另一個(gè)對(duì)象,是對(duì)象的一個(gè)屬性來(lái)源。當(dāng)開(kāi)發(fā)人員訪問(wèn)一個(gè)對(duì)象不包含的屬性時(shí),就會(huì)從對(duì)象原型中搜索屬性,接著是原型的原型,依此類(lèi)推。

那么空對(duì)象的原型是什么呢?是Object.prototype,它是所有對(duì)象中原型的父原型。

console.log(Object.getPrototypeOf({}) ==
            Object.prototype);
// → true
console.log(Object.getPrototypeOf(Object.prototype));
// → null

正如你的猜測(cè),Object.getPrototypeOf返回一個(gè)對(duì)象的原型。

JavaScript 對(duì)象原型的關(guān)系是一種樹(shù)形結(jié)構(gòu),整個(gè)樹(shù)形結(jié)構(gòu)的根部就是Object.prototype。Object.prototype提供了一些可以在所有對(duì)象中使用的方法。比如說(shuō),toString方法可以將一個(gè)對(duì)象轉(zhuǎn)換成其字符串表示形式。

許多對(duì)象并不直接將Object.prototype作為其原型,而會(huì)使用另一個(gè)原型對(duì)象,用于提供一系列不同的默認(rèn)屬性。函數(shù)繼承自Function.prototype,而數(shù)組繼承自Array.prototype

console.log(Object.getPrototypeOf(Math.max) ==
            Function.prototype);
// → true
console.log(Object.getPrototypeOf([]) ==
            Array.prototype);
// → true

對(duì)于這樣的原型對(duì)象來(lái)說(shuō),其自身也包含了一個(gè)原型對(duì)象,通常情況下是Object.prototype,所以說(shuō),這些原型對(duì)象可以間接提供toString這樣的方法。

你可以使用Object.create來(lái)創(chuàng)建一個(gè)具有特定原型的對(duì)象。

let protoRabbit = {
  speak(line) {
    console.log(`The ${this.type} rabbit says "${line}"`);
  }
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!");
// → The killer rabbit says "SKREEEE!"

像對(duì)象表達(dá)式中的speak(line)這樣的屬性是定義方法的簡(jiǎn)寫(xiě)。 它創(chuàng)建了一個(gè)名為speak的屬性,并向其提供函數(shù)作為它的值。

原型對(duì)象protoRabbit是一個(gè)容器,用于包含所有兔子對(duì)象的公有屬性。每個(gè)獨(dú)立的兔子對(duì)象(比如killerRabbit)可以包含其自身屬性(比如本例中的type屬性),也可以派生其原型對(duì)象中公有的屬性。

類(lèi)

JavaScript 的原型系統(tǒng)可以解釋為對(duì)一種面向?qū)ο蟮母拍睿ǚQ(chēng)為類(lèi)(class))的某種非正式實(shí)現(xiàn)。 類(lèi)定義了對(duì)象的類(lèi)型的形狀 - 它具有什么方法和屬性。 這樣的對(duì)象被稱(chēng)為類(lèi)的實(shí)例(instance)。

原型對(duì)于屬性來(lái)說(shuō)很實(shí)用。一個(gè)類(lèi)的所有實(shí)例共享相同的屬性值,例如方法。 每個(gè)實(shí)例上的不同屬性,比如我們的兔子的type屬性,需要直接存儲(chǔ)在對(duì)象本身中。

所以為了創(chuàng)建一個(gè)給定類(lèi)的實(shí)例,你必須使對(duì)象從正確的原型派生,但是你也必須確保,它本身具有這個(gè)類(lèi)的實(shí)例應(yīng)該具有的屬性。 這是構(gòu)造器(constructor)函數(shù)的作用。

function makeRabbit(type) {
  let rabbit = Object.create(protoRabbit);
  rabbit.type = type;
  return rabbit;
}

JavaScript 提供了一種方法,來(lái)使得更容易定義這種類(lèi)型的功能。 如果將關(guān)鍵字new放在函數(shù)調(diào)用之前,則該函數(shù)將被視為構(gòu)造器。 這意味著具有正確原型的對(duì)象會(huì)自動(dòng)創(chuàng)建,綁定到函數(shù)中的this,并在函數(shù)結(jié)束時(shí)返回。

構(gòu)造對(duì)象時(shí)使用的原型對(duì)象,可以通過(guò)構(gòu)造器的prototype屬性來(lái)查找。

function Rabbit(type) {
  this.type = type;
}

Rabbit.prototype.speak = function(line) {
  console.log(`The ${this.type} rabbit says "${line}"`);
};
let weirdRabbit = new Rabbit("weird");

構(gòu)造器(實(shí)際上是所有函數(shù))都會(huì)自動(dòng)獲得一個(gè)名為prototype的屬性,默認(rèn)情況下它包含一個(gè)普通的,來(lái)自Object.prototype的空對(duì)象。 如果需要,可以用新對(duì)象覆蓋它。 或者,你可以將屬性添加到現(xiàn)有對(duì)象,如示例所示。

按照慣例,構(gòu)造器的名字是大寫(xiě)的,這樣它們可以很容易地與其他函數(shù)區(qū)分開(kāi)來(lái)。

重要的是,理解原型與構(gòu)造器關(guān)聯(lián)的方式(通過(guò)其prototype屬性),與對(duì)象擁有原型(可以通過(guò)Object.getPrototypeOf查找)的方式之間的區(qū)別。 構(gòu)造器的實(shí)際原型是Function.prototype,因?yàn)闃?gòu)造器是函數(shù)。 它的prototype屬性擁有原型,用于通過(guò)它創(chuàng)建的實(shí)例。

console.log(Object.getPrototypeOf(Rabbit) ==
            Function.prototype);
// → true
console.log(Object.getPrototypeOf(weirdRabbit) ==
            Rabbit.prototype);
// → true
類(lèi)的表示法

所以 JavaScript 類(lèi)是帶有原型屬性的構(gòu)造器。 這就是他們的工作方式,直到 2015 年,這就是你編寫(xiě)他們的方式。 最近,我們有了一個(gè)不太笨拙的表示法。

class Rabbit {
  constructor(type) {
    this.type = type;
  }
  speak(line) {
    console.log(`The ${this.type} rabbit says "${line}"`);
  }
}

let killerRabbit = new Rabbit("killer");
let blackRabbit = new Rabbit("black");

class關(guān)鍵字是類(lèi)聲明的開(kāi)始,它允許我們?cè)谝粋€(gè)地方定義一個(gè)構(gòu)造器和一組方法。 可以在聲明的大括號(hào)內(nèi)寫(xiě)入任意數(shù)量的方法。 一個(gè)名為constructor的對(duì)象受到特別處理。 它提供了實(shí)際的構(gòu)造器,它將綁定到名稱(chēng)"Rabbit"。 其他函數(shù)被打包到該構(gòu)造器的原型中。 因此,上面的類(lèi)聲明等同于上一節(jié)中的構(gòu)造器定義。 它看起來(lái)更好。

類(lèi)聲明目前只允許方法 - 持有函數(shù)的屬性 - 添加到原型中。 當(dāng)你想在那里保存一個(gè)非函數(shù)值時(shí),這可能會(huì)有點(diǎn)不方便。 該語(yǔ)言的下一個(gè)版本可能會(huì)改善這一點(diǎn)。 現(xiàn)在,你可以在定義該類(lèi)后直接操作原型來(lái)創(chuàng)建這些屬性。

function一樣,class可以在語(yǔ)句和表達(dá)式中使用。 當(dāng)用作表達(dá)式時(shí),它沒(méi)有定義綁定,而只是將構(gòu)造器作為一個(gè)值生成。 你可以在類(lèi)表達(dá)式中省略類(lèi)名稱(chēng)。

let object = new class { getWord() { return "hello"; } };
console.log(object.getWord());
// → hello
覆蓋派生的屬性

將屬性添加到對(duì)象時(shí),無(wú)論它是否存在于原型中,該屬性都會(huì)添加到對(duì)象本身中。 如果原型中已經(jīng)有一個(gè)同名的屬性,該屬性將不再影響對(duì)象,因?yàn)樗F(xiàn)在隱藏在對(duì)象自己的屬性后面。

Rabbit.prototype.teeth = "small";
console.log(killerRabbit.teeth);
// → small
killerRabbit.teeth = "long, sharp, and bloody";
console.log(killerRabbit.teeth);
// → long, sharp, and bloody
console.log(blackRabbit.teeth);
// → small
console.log(Rabbit.prototype.teeth);
// → small

下圖簡(jiǎn)單地描述了代碼執(zhí)行后的情況。其中RabbitObject原型畫(huà)在了killerRabbit之下,我們可以從原型中找到對(duì)象中沒(méi)有的屬性。

覆蓋原型中存在的屬性是很有用的特性。就像示例展示的那樣,我們覆蓋了killerRabbitteeth屬性,這可以用來(lái)描述實(shí)例(對(duì)象中更為泛化的類(lèi)的實(shí)例)的特殊屬性,同時(shí)又可以讓簡(jiǎn)單對(duì)象從原型中獲取標(biāo)準(zhǔn)的值。

覆蓋也用于向標(biāo)準(zhǔn)函數(shù)和數(shù)組原型提供toString方法,與基本對(duì)象的原型不同。

console.log(Array.prototype.toString ==
            Object.prototype.toString);
// → false
console.log([1, 2].toString());
// → 1,2

調(diào)用數(shù)組的toString方法后得到的結(jié)果與調(diào)用.join(",")的結(jié)果十分類(lèi)似,即在數(shù)組的每個(gè)值之間插入一個(gè)逗號(hào)。而直接使用數(shù)組調(diào)用Object.prototype.toString則會(huì)產(chǎn)生一個(gè)完全不同的字符串。由于Object原型提供的toString方法并不了解數(shù)組結(jié)構(gòu),因此只會(huì)簡(jiǎn)單地輸出一對(duì)方括號(hào),并在方括號(hào)中間輸出單詞"object"和類(lèi)型的名稱(chēng)。

console.log(Object.prototype.toString.call([1, 2]));
// → [object Array]
映射

我們?cè)谏弦徽轮锌吹搅擞成洌╩ap)這個(gè)詞,用于一個(gè)操作,通過(guò)對(duì)元素應(yīng)用函數(shù)來(lái)轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu)。 令人困惑的是,在編程時(shí),同一個(gè)詞也被用于相關(guān)而不同的事物。

映射(名詞)是將值(鍵)與其他值相關(guān)聯(lián)的數(shù)據(jù)結(jié)構(gòu)。 例如,你可能想要將姓名映射到年齡。 為此可以使用對(duì)象。

let ages = {
  Boris: 39,
  Liang: 22,
  Júlia: 62
};

console.log(`Júlia is ${ages["Júlia"]}`);
// → Júlia is 62
console.log("Is Jack"s age known?", "Jack" in ages);
// → Is Jack"s age known? false
console.log("Is toString"s age known?", "toString" in ages);
// → Is toString"s age known? true

在這里,對(duì)象的屬性名稱(chēng)是人們的姓名,并且該屬性的值為他們的年齡。 但是我們當(dāng)然沒(méi)有在我們的映射中列出任何名為toString的人。 似的,因?yàn)楹?jiǎn)單對(duì)象是從Object.prototype派生的,所以它看起來(lái)就像擁有這個(gè)屬性。

因此,使用簡(jiǎn)單對(duì)象作為映射是危險(xiǎn)的。 有幾種可能的方法來(lái)避免這個(gè)問(wèn)題。 首先,可以使用null原型創(chuàng)建對(duì)象。 如果將null傳遞給Object.create,那么所得到的對(duì)象將不會(huì)從Object.prototype派生,并且可以安全地用作映射。

console.log("toString" in Object.create(null));
// → false

對(duì)象屬性名稱(chēng)必須是字符串。 如果你需要一個(gè)映射,它的鍵不能輕易轉(zhuǎn)換為字符串 - 比如對(duì)象 - 你不能使用對(duì)象作為你的映射。

幸運(yùn)的是,JavaScript 帶有一個(gè)叫做Map的類(lèi),它正是為了這個(gè)目的而編寫(xiě)。 它存儲(chǔ)映射并允許任何類(lèi)型的鍵。

let ages = new Map();
ages.set("Boris", 39);
ages.set("Liang", 22);
ages.set("Júlia", 62);
console.log(`Júlia is ${ages.get("Júlia")}`);
// → Júlia is 62
console.log("Is Jack"s age known?", ages.has("Jack"));
// → Is Jack"s age known? false
console.log(ages.has("toString"));
 // → false

set,gethas方法是Map對(duì)象的接口的一部分。 編寫(xiě)一個(gè)可以快速更新和搜索大量值的數(shù)據(jù)結(jié)構(gòu)并不容易,但我們不必?fù)?dān)心這一點(diǎn)。 其他人為我們實(shí)現(xiàn),我們可以通過(guò)這個(gè)簡(jiǎn)單的接口來(lái)使用他們的工作。

如果你確實(shí)有一個(gè)簡(jiǎn)單對(duì)象,出于某種原因需要將它視為一個(gè)映射,那么了解Object.keys只返回對(duì)象的自己的鍵,而不是原型中的那些鍵,會(huì)很有用。 作為in運(yùn)算符的替代方法,你可以使用hasOwnProperty方法,該方法會(huì)忽略對(duì)象的原型。

console.log({x: 1}.hasOwnProperty("x"));
// → true
console.log({x: 1}.hasOwnProperty("toString"));
// → false
多態(tài)

當(dāng)你調(diào)用一個(gè)對(duì)象的String函數(shù)(將一個(gè)值轉(zhuǎn)換為一個(gè)字符串)時(shí),它會(huì)調(diào)用該對(duì)象的toString方法來(lái)嘗試從它創(chuàng)建一個(gè)有意義的字符串。 我提到一些標(biāo)準(zhǔn)原型定義了自己的toString版本,因此它們可以創(chuàng)建一個(gè)包含比"[object Object]"有用信息更多的字符串。 你也可以自己實(shí)現(xiàn)。

Rabbit.prototype.toString = function() {
  return `a ${this.type} rabbit`;
};

console.log(String(blackRabbit));
// → a black rabbit

這是一個(gè)強(qiáng)大的想法的簡(jiǎn)單實(shí)例。 當(dāng)一段代碼為了與某些對(duì)象協(xié)作而編寫(xiě),這些對(duì)象具有特定接口時(shí)(在本例中為toString方法),任何類(lèi)型的支持此接口的對(duì)象都可以插入到代碼中,并且它將正常工作。

這種技術(shù)被稱(chēng)為多態(tài)(polymorphism)。 多態(tài)代碼可以處理不同形狀的值,只要它們支持它所期望的接口即可。

我在第四章中提到for/of循環(huán)可以遍歷幾種數(shù)據(jù)結(jié)構(gòu)。 這是多態(tài)性的另一種情況 - 這樣的循環(huán)期望數(shù)據(jù)結(jié)構(gòu)公開(kāi)的特定接口,數(shù)組和字符串是這樣。 你也可以將這個(gè)接口添加到你自己的對(duì)象中! 但在我們實(shí)現(xiàn)它之前,我們需要知道什么是符號(hào)。

符號(hào)

多個(gè)接口可能為不同的事物使用相同的屬性名稱(chēng)。 例如,我可以定義一個(gè)接口,其中toString方法應(yīng)該將對(duì)象轉(zhuǎn)換為一段紗線。 一個(gè)對(duì)象不可能同時(shí)滿足這個(gè)接口和toString的標(biāo)準(zhǔn)用法。

這是一個(gè)壞主意,這個(gè)問(wèn)題并不常見(jiàn)。 大多數(shù) JavaScript 程序員根本就不會(huì)去想它。 但是,語(yǔ)言設(shè)計(jì)師們正在思考這個(gè)問(wèn)題,無(wú)論如何都為我們提供了解決方案。

當(dāng)我聲稱(chēng)屬性名稱(chēng)是字符串時(shí),這并不完全準(zhǔn)確。 他們通常是,但他們也可以是符號(hào)(symbol)。 符號(hào)是使用Symbol函數(shù)創(chuàng)建的值。 與字符串不同,新創(chuàng)建的符號(hào)是唯一的 - 你不能兩次創(chuàng)建相同的符號(hào)。

let sym = Symbol("name");
console.log(sym == Symbol("name"));
// → false
Rabbit.prototype[sym] = 55;
console.log(blackRabbit[sym]);
// → 55

Symbol轉(zhuǎn)換為字符串時(shí),會(huì)得到傳遞給它的字符串,例如,在控制臺(tái)中顯示時(shí),符號(hào)可以更容易識(shí)別。 但除此之外沒(méi)有任何意義 - 多個(gè)符號(hào)可能具有相同的名稱(chēng)。

由于符號(hào)既獨(dú)特又可用于屬性名稱(chēng),因此符號(hào)適合定義可以和其他屬性共生的接口,無(wú)論它們的名稱(chēng)是什么。

const toStringSymbol = Symbol("toString");
Array.prototype[toStringSymbol] = function() {
  return `${this.length} cm of blue yarn`;
};
console.log([1, 2].toString());
// → 1,2
console.log([1, 2][toStringSymbol]());
// → 2 cm of blue yarn

通過(guò)在屬性名稱(chēng)周?chē)褂梅嚼ㄌ?hào),可以在對(duì)象表達(dá)式和類(lèi)中包含符號(hào)屬性。 這會(huì)導(dǎo)致屬性名稱(chēng)的求值,就像方括號(hào)屬性訪問(wèn)表示法一樣,這允許我們引用一個(gè)持有該符號(hào)的綁定。

let stringObject = {
  [toStringSymbol]() { return "a jute rope"; }
};
console.log(stringObject[toStringSymbol]());
// → a jute rope
迭代器接口

提供給for/of循環(huán)的對(duì)象預(yù)計(jì)為可迭代對(duì)象(iterable)。 這意味著它有一個(gè)以Symbol.iterator符號(hào)命名的方法(由語(yǔ)言定義的符號(hào)值,存儲(chǔ)為Symbol符號(hào)的一個(gè)屬性)。

當(dāng)被調(diào)用時(shí),該方法應(yīng)該返回一個(gè)對(duì)象,它提供第二個(gè)接口迭代器(iterator)。 這是執(zhí)行迭代的實(shí)際事物。 它擁有返回下一個(gè)結(jié)果的next方法。 這個(gè)結(jié)果應(yīng)該是一個(gè)對(duì)象,如果有下一個(gè)值,value屬性會(huì)提供它;沒(méi)有更多結(jié)果時(shí),done屬性應(yīng)該為true,否則為false。

請(qǐng)注意,next,valuedone屬性名稱(chēng)是純字符串,而不是符號(hào)。 只有Symbol.iterator是一個(gè)實(shí)際的符號(hào),它可能被添加到不同的大量對(duì)象中。

我們可以直接使用這個(gè)接口。

let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// → {value: "O", done: false}
console.log(okIterator.next());
// → {value: "K", done: false}
console.log(okIterator.next());
// → {value: undefined, done: true}

我們來(lái)實(shí)現(xiàn)一個(gè)可迭代的數(shù)據(jù)結(jié)構(gòu)。 我們將構(gòu)建一個(gè)matrix類(lèi),充當(dāng)一個(gè)二維數(shù)組。

class Matrix {
  constructor(width, height, element = (x, y) => undefined) {
    this.width = width;
    this.height = height;
    this.content = [];
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        this.content[y * width + x] = element(x, y);
      }
    }
  }
  get(x, y) {
    return this.content[y * this.width + x];
  }
  set(x, y, value) {
    this.content[y * this.width + x] = value;
  }
}

該類(lèi)將其內(nèi)容存儲(chǔ)在width × height個(gè)元素的單個(gè)數(shù)組中。 元素是按行存儲(chǔ)的,因此,例如,第五行中的第三個(gè)元素存儲(chǔ)在位置4 × width + 2中(使用基于零的索引)。

構(gòu)造器需要寬度,高度和一個(gè)可選的內(nèi)容函數(shù),用來(lái)填充初始值。 getset方法用于檢索和更新矩陣中的元素。

遍歷矩陣時(shí),通常對(duì)元素的位置以及元素本身感興趣,所以我們會(huì)讓迭代器產(chǎn)生具有x,yvalue屬性的對(duì)象。

class MatrixIterator {
  constructor(matrix) {
    this.x = 0;
    this.y = 0;
    this.matrix = matrix;
  }
  next() {
    if (this.y == this.matrix.height) return {done: true};

    let value = {x: this.x,
                 y: this.y,
                 value: this.matrix.get(this.x, this.y)};
    this.x++;
    if (this.x == this.matrix.width) {
      this.x = 0;
      this.y++;
    }
    return {value, done: false};
  }
}

這個(gè)類(lèi)在其xy屬性中跟蹤遍歷矩陣的進(jìn)度。 next方法最開(kāi)始檢查是否到達(dá)矩陣的底部。 如果沒(méi)有,則首先創(chuàng)建保存當(dāng)前值的對(duì)象,之后更新其位置,如有必要?jiǎng)t移至下一行。

讓我們使Matrix類(lèi)可迭代。 在本書(shū)中,我會(huì)偶爾使用事后的原型操作來(lái)為類(lèi)添加方法,以便單個(gè)代碼段保持較小且獨(dú)立。 在一個(gè)正常的程序中,不需要將代碼分成小塊,而是直接在class中聲明這些方法。

Matrix.prototype[Symbol.iterator] = function() {
  return new MatrixIterator(this);
};

現(xiàn)在我們可以用for/of來(lái)遍歷一個(gè)矩陣。

let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
for (let {x, y, value} of matrix) {
  console.log(x, y, value);
}
// → 0 0 value 0,0
// → 1 0 value 1,0
// → 0 1 value 0,1
// → 1 1 value 1,1
讀寫(xiě)器和靜態(tài)

接口通常主要由方法組成,但也可以持有非函數(shù)值的屬性。 例如,Map對(duì)象有size屬性,告訴你有多少個(gè)鍵存儲(chǔ)在它們中。

這樣的對(duì)象甚至不需要直接在實(shí)例中計(jì)算和存儲(chǔ)這樣的屬性。 即使直接訪問(wèn)的屬性也可能隱藏了方法調(diào)用。 這種方法稱(chēng)為讀取器(getter),它們通過(guò)在方法名稱(chēng)前面編寫(xiě)get來(lái)定義。

let varyingSize = {
  get size() {
    return Math.floor(Math.random() * 100);
  }
};
console.log(varyingSize.size);
// → 73
console.log(varyingSize.size);
// → 49

每當(dāng)有人讀取此對(duì)象的size屬性時(shí),就會(huì)調(diào)用相關(guān)的方法。 當(dāng)使用寫(xiě)入器(setter)寫(xiě)入屬性時(shí),可以做類(lèi)似的事情。

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  get fahrenheit() {
    return this.celsius * 1.8 + 32;
  }
  set fahrenheit(value) {
    this.celsius = (value - 32) / 1.8;
  }

  static fromFahrenheit(value) {
    return new Temperature((value - 32) / 1.8);
  }
}
let temp = new Temperature(22);
console.log(temp.fahrenheit);
// → 71.6
temp.fahrenheit = 86;
console.log(temp.celsius);
// → 30

Temperature類(lèi)允許你以攝氏度或華氏度讀取和寫(xiě)入溫度,但內(nèi)部?jī)H存儲(chǔ)攝氏度,并在fahrenheit讀寫(xiě)器中自動(dòng)轉(zhuǎn)換為攝氏度。

有時(shí)候你想直接向你的構(gòu)造器附加一些屬性,而不是原型。 這樣的方法將無(wú)法訪問(wèn)類(lèi)實(shí)例,但可以用來(lái)提供額外方法來(lái)創(chuàng)建實(shí)例。

在類(lèi)聲明內(nèi)部,名稱(chēng)前面寫(xiě)有static的方法,存儲(chǔ)在構(gòu)造器中。 所以Temperature類(lèi)可以讓你寫(xiě)出Temperature.fromFahrenheit(100),來(lái)使用華氏溫度創(chuàng)建一個(gè)溫度。

繼承

已知一些矩陣是對(duì)稱(chēng)的。 如果沿左上角到右下角的對(duì)角線翻轉(zhuǎn)對(duì)稱(chēng)矩陣,它保持不變。 換句話說(shuō),存儲(chǔ)在x,y的值總是與y,x相同。

想象一下,我們需要一個(gè)像Matrix這樣的數(shù)據(jù)結(jié)構(gòu),但是它必需保證一個(gè)事實(shí),矩陣是對(duì)稱(chēng)的。 我們可以從頭開(kāi)始編寫(xiě)它,但這需要重復(fù)一些代碼,與我們已經(jīng)寫(xiě)過(guò)的代碼很相似。

JavaScript 的原型系統(tǒng)可以創(chuàng)建一個(gè)新類(lèi),就像舊類(lèi)一樣,但是它的一些屬性有了新的定義。 新類(lèi)派生自舊類(lèi)的原型,但為set方法增加了一個(gè)新的定義。

在面向?qū)ο蟮木幊绦g(shù)語(yǔ)中,這稱(chēng)為繼承(inheritance)。 新類(lèi)繼承舊類(lèi)的屬性和行為。

class SymmetricMatrix extends Matrix {
  constructor(size, element = (x, y) => undefined) {
    super(size, size, (x, y) => {
      if (x < y) return element(y, x);
      else return element(x, y);
    });
  }

  set(x, y, value) {
    super.set(x, y, value);
    if (x != y) {
      super.set(y, x, value);
    }
  }
}
let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
console.log(matrix.get(2, 3));
// → 3,2

extends這個(gè)詞用于表示,這個(gè)類(lèi)不應(yīng)該直接基于默認(rèn)的Object原型,而應(yīng)該基于其他類(lèi)。 這被稱(chēng)為超類(lèi)(superclass)。 派生類(lèi)是子類(lèi)(subclass)。

為了初始化SymmetricMatrix實(shí)例,構(gòu)造器通過(guò)super關(guān)鍵字調(diào)用其超類(lèi)的構(gòu)造器。 這是必要的,因?yàn)槿绻@個(gè)新對(duì)象的行為(大致)像Matrix,它需要矩陣具有的實(shí)例屬性。 為了確保矩陣是對(duì)稱(chēng)的,構(gòu)造器包裝了content方法,來(lái)交換對(duì)角線以下的值的坐標(biāo)。

set方法再次使用super,但這次不是調(diào)用構(gòu)造器,而是從超類(lèi)的一組方法中調(diào)用特定的方法。 我們正在重新定義set,但是想要使用原來(lái)的行為。 因?yàn)?b>this.set引用新的set方法,所以調(diào)用這個(gè)方法是行不通的。 在類(lèi)方法內(nèi)部,super提供了一種方法,來(lái)調(diào)用超類(lèi)中定義的方法。

繼承允許我們用相對(duì)較少的工作,從現(xiàn)有數(shù)據(jù)類(lèi)型構(gòu)建稍微不同的數(shù)據(jù)類(lèi)型。 它是面向?qū)ο髠鹘y(tǒng)的基礎(chǔ)部分,與封裝和多態(tài)一樣。 盡管后兩者現(xiàn)在普遍被認(rèn)為是偉大的想法,但繼承更具爭(zhēng)議性。

盡管封裝和多態(tài)可用于將代碼彼此分離,從而減少整個(gè)程序的耦合,但繼承從根本上將類(lèi)連接在一起,從而產(chǎn)生更多的耦合。 繼承一個(gè)類(lèi)時(shí),比起單純使用它,你通常必須更加了解它如何工作。 繼承可能是一個(gè)有用的工具,并且我現(xiàn)在在自己的程序中使用它,但它不應(yīng)該成為你的第一個(gè)工具,你可能不應(yīng)該積極尋找機(jī)會(huì)來(lái)構(gòu)建類(lèi)層次結(jié)構(gòu)(類(lèi)的家族樹(shù))。

instanceof運(yùn)算符

在有些時(shí)候,了解某個(gè)對(duì)象是否繼承自某個(gè)特定類(lèi),也是十分有用的。JavaScript 為此提供了一個(gè)二元運(yùn)算符,名為instanceof。

console.log(
  new SymmetricMatrix(2) instanceof SymmetricMatrix);
// → true
console.log(new SymmetricMatrix(2) instanceof Matrix);
// → true
console.log(new Matrix(2, 2) instanceof SymmetricMatrix);
// → false
console.log([1] instanceof Array);
// → true

該運(yùn)算符會(huì)瀏覽所有繼承類(lèi)型。所以SymmetricMatrixMatrix的一個(gè)實(shí)例。 該運(yùn)算符也可以應(yīng)用于像Array這樣的標(biāo)準(zhǔn)構(gòu)造器。 幾乎每個(gè)對(duì)象都是Object的一個(gè)實(shí)例。

本章小結(jié)

對(duì)象不僅僅持有它們自己的屬性。對(duì)象中有另一個(gè)對(duì)象:原型,只要原型中包含了屬性,那么根據(jù)原型構(gòu)造出來(lái)的對(duì)象也就可以看成包含了相應(yīng)的屬性。簡(jiǎn)單對(duì)象直接以Object.prototype作為原型。

構(gòu)造器是名稱(chēng)通常以大寫(xiě)字母開(kāi)頭的函數(shù),可以與new運(yùn)算符一起使用來(lái)創(chuàng)建新對(duì)象。 新對(duì)象的原型是構(gòu)造器的prototype屬性中的對(duì)象。 通過(guò)將屬性放到它們的原型中,可以充分利用這一點(diǎn),給定類(lèi)型的所有值在原型中分享它們的屬性。 class表示法提供了一個(gè)顯式方法,來(lái)定義一個(gè)構(gòu)造器及其原型。

你可以定義讀寫(xiě)器,在每次訪問(wèn)對(duì)象的屬性時(shí)秘密地調(diào)用方法。 靜態(tài)方法是存儲(chǔ)在類(lèi)的構(gòu)造器,而不是其原型中的方法。

給定一個(gè)對(duì)象和一個(gè)構(gòu)造器,instanceof運(yùn)算符可以告訴你該對(duì)象是否是該構(gòu)造器的一個(gè)實(shí)例。

可以使用對(duì)象的來(lái)做一個(gè)有用的事情是,為它們指定一個(gè)接口,告訴每個(gè)人他們只能通過(guò)該接口與對(duì)象通信。 構(gòu)成對(duì)象的其余細(xì)節(jié),現(xiàn)在被封裝在接口后面。

不止一種類(lèi)型可以實(shí)現(xiàn)相同的接口。 為使用接口而編寫(xiě)的代碼,自動(dòng)知道如何使用提供接口的任意數(shù)量的不同對(duì)象。 這被稱(chēng)為多態(tài)。

實(shí)現(xiàn)多個(gè)類(lèi),它們僅在一些細(xì)節(jié)上有所不同的時(shí),將新類(lèi)編寫(xiě)為現(xiàn)有類(lèi)的子類(lèi),繼承其一部分行為會(huì)很有幫助。

習(xí)題 向量類(lèi)型

編寫(xiě)一個(gè)構(gòu)造器Vec,在二維空間中表示數(shù)組。該函數(shù)接受兩個(gè)數(shù)字參數(shù)xy,并將其保存到對(duì)象的同名屬性中。

Vec原型添加兩個(gè)方法:plusminus,它們接受另一個(gè)向量作為參數(shù),分別返回兩個(gè)向量(一個(gè)是this,另一個(gè)是參數(shù))的和向量與差向量。

向原型添加一個(gè)getter屬性length,用于計(jì)算向量長(zhǎng)度,即點(diǎn)(x,y)與原點(diǎn)(0,0)之間的距離。

// Your code here.

console.log(new Vec(1, 2).plus(new Vec(2, 3)));
// → Vec{x: 3, y: 5}
console.log(new Vec(1, 2).minus(new Vec(2, 3)));
// → Vec{x: -1, y: -1}
console.log(new Vec(3, 4).length);
// → 5
分組

標(biāo)準(zhǔn)的 JavaScript 環(huán)境提供了另一個(gè)名為Set的數(shù)據(jù)結(jié)構(gòu)。 像Map的實(shí)例一樣,集合包含一組值。 與Map不同,它不會(huì)將其他值與這些值相關(guān)聯(lián) - 它只會(huì)跟蹤哪些值是該集合的一部分。 一個(gè)值只能是一個(gè)集合的一部分 - 再次添加它沒(méi)有任何作用。

寫(xiě)一個(gè)名為Group的類(lèi)(因?yàn)?b>Set已被占用)。 像Set一樣,它具有adddeletehas方法。 它的構(gòu)造器創(chuàng)建一個(gè)空的分組,add給分組添加一個(gè)值(但僅當(dāng)它不是成員時(shí)),delete從組中刪除它的參數(shù)(如果它是成員),has 返回一個(gè)布爾值,表明其參數(shù)是否為分組的成員。

使用===運(yùn)算符或類(lèi)似于indexOf的東西來(lái)確定兩個(gè)值是否相同。

為該類(lèi)提供一個(gè)靜態(tài)的from方法,該方法接受一個(gè)可迭代的對(duì)象作為參數(shù),并創(chuàng)建一個(gè)分組,包含遍歷它產(chǎn)生的所有值。

// Your code here.

class Group {
  // Your code here.
}
let group = Group.from([10, 20]);
console.log(group.has(10));
// → true
console.log(group.has(30));
// → false
group.add(10);
group.delete(10);
console.log(group.has(10));
// → false
可迭代分組

使上一個(gè)練習(xí)中的Group類(lèi)可迭代。 如果你不清楚接口的確切形式,請(qǐng)參閱本章前面迭代器接口的章節(jié)。

如果你使用數(shù)組來(lái)表示分組的成員,則不要僅僅通過(guò)調(diào)用數(shù)組中的Symbol.iterator方法來(lái)返回迭代器。 這會(huì)起作用,但它會(huì)破壞這個(gè)練習(xí)的目的。

如果分組被修改時(shí),你的迭代器在迭代過(guò)程中出現(xiàn)奇怪的行為,那也沒(méi)問(wèn)題。

// Your code here (and the code from the previous exercise)

for (let value of Group.from(["a", "b", "c"])) {
  console.log(value);
}
// → a
// → b
// → c
借鑒方法

在本章前面我提到,當(dāng)你想忽略原型的屬性時(shí),對(duì)象的hasOwnProperty可以用作in運(yùn)算符的更強(qiáng)大的替代方法。 但是如果你的映射需要包含hasOwnProperty這個(gè)詞呢? 你將無(wú)法再調(diào)用該方法,因?yàn)閷?duì)象的屬性隱藏了方法值。

你能想到一種方法,對(duì)擁有自己的同名屬性的對(duì)象,調(diào)用hasOwnProperty嗎?

let map = {one: true, two: true, hasOwnProperty: true};
// Fix this call
console.log(map.hasOwnProperty("one"));
// → true

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

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

相關(guān)文章

  • 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
  • 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 編程精解 中文三版 零、前言

    摘要:來(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 編程精解 中文三版 十二、項(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 編程精解 中文三版 十四、文檔對(duì)象模型

    摘要:在其沙箱中提供了將文本轉(zhuǎn)換成文檔對(duì)象模型的功能。瀏覽器使用與該形狀對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)來(lái)表示文檔。我們將這種表示方式稱(chēng)為文檔對(duì)象模型,或簡(jiǎn)稱(chēng)。樹(shù)回想一下第章中提到的語(yǔ)法樹(shù)。語(yǔ)言的語(yǔ)法樹(shù)有標(biāo)識(shí)符值和應(yīng)用節(jié)點(diǎn)。元素表示標(biāo)簽的節(jié)點(diǎn)用于確定文檔結(jié)構(gòu)。 來(lái)源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:The Document Object Model 譯者:飛龍 協(xié)議...

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

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

0條評(píng)論

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