摘要:如何向構(gòu)造函數(shù)的原型添加方法。回顧一下我們的構(gòu)造函數(shù),最重要的兩個(gè)部分是創(chuàng)建對(duì)象并返回它。正常如下再次說(shuō)明,之所以這樣做,并且這個(gè)對(duì)象是為我們創(chuàng)建的,是因?yàn)槲覀冇藐P(guān)鍵字調(diào)用了構(gòu)造函數(shù)。
想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你!
不學(xué)會(huì)怎么處理對(duì)象,你在 JavaScript 道路就就走不了多遠(yuǎn)。它們幾乎是 JavaScript 編程語(yǔ)言每個(gè)方面的基礎(chǔ)。事實(shí)上,學(xué)習(xí)如何創(chuàng)建對(duì)象可能是你剛開(kāi)始學(xué)習(xí)的第一件事。
對(duì)象是鍵/值對(duì)。創(chuàng)建對(duì)象的最常用方法是使用花括號(hào){},并使用點(diǎn)表示法向?qū)ο筇砑訉傩院头椒ā?/p>
let animal = {} animal.name = "Leo" animal.energy = 10 animal.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } animal.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } animal.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length }
現(xiàn)在,在我們的應(yīng)用程序中,我們需要?jiǎng)?chuàng)建多個(gè) animal。 當(dāng)然,下一步是將邏輯封裝,當(dāng)我們需要?jiǎng)?chuàng)建新 animal 時(shí),只需調(diào)用函數(shù)即可,我們將這種模式稱(chēng)為函數(shù)的實(shí)例化(unctional Instantiation),我們將函數(shù)本身稱(chēng)為“構(gòu)造函數(shù)”,因?yàn)樗?fù)責(zé)“構(gòu)造”一個(gè)??新對(duì)象。
函數(shù)的實(shí)例化function Animal (name, energy) { let animal = {} animal.name = name animal.energy = energy animal.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } animal.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } animal.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } return animal } const leo = Animal("Leo", 7) const snoop = Animal("Snoop", 10)
現(xiàn)在,無(wú)論何時(shí)我們想要?jiǎng)?chuàng)建一個(gè)新 animal(或者更廣泛地說(shuō),創(chuàng)建一個(gè)新的“實(shí)例”),我們所要做的就是調(diào)用我們的 Animal 函數(shù),并傳入?yún)?shù):name 和 energy 。這很有用,而且非常簡(jiǎn)單。但是,你能說(shuō)這種模式的哪些缺點(diǎn)嗎?
最大的和我們?cè)噲D解決的問(wèn)題與函數(shù)里面的三個(gè)方法有關(guān) - eat,sleep 和 play。 這些方法中的每一種都不僅是動(dòng)態(tài)的,而且它們也是完全通用的。這意味著,我們沒(méi)有理由像現(xiàn)在一樣,在創(chuàng)造新animal的時(shí)候重新創(chuàng)建這些方法。我們只是在浪費(fèi)內(nèi)存,讓每一個(gè)新建的對(duì)象都比實(shí)際需要的還大。
你能想到一個(gè)解決方案嗎? 如果不是在每次創(chuàng)建新動(dòng)物時(shí)重新創(chuàng)建這些方法,我們將它們移動(dòng)到自己的對(duì)象然后我們可以讓每個(gè)動(dòng)物引用該對(duì)象,該怎么辦? 我們可以將此模式稱(chēng)為函數(shù)實(shí)例化與共享方法。
函數(shù)實(shí)例化與共享方法const animalMethods = { eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount }, sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length }, play(length) { console.log(`${this.name} is playing.`) this.energy -= length } } function Animal (name, energy) { let animal = {} animal.name = name animal.energy = energy animal.eat = animalMethods.eat animal.sleep = animalMethods.sleep animal.play = animalMethods.play return animal } const leo = Animal("Leo", 7) const snoop = Animal("Snoop", 10)
通過(guò)將共享方法移動(dòng)到它們自己的對(duì)象并在 Animal 函數(shù)中引用該對(duì)象,我們現(xiàn)在已經(jīng)解決了內(nèi)存浪費(fèi)和新對(duì)象體積過(guò)大的問(wèn)題。
Object.create讓我們?cè)俅问褂?Object.create 改進(jìn)我們的例子。 簡(jiǎn)單地說(shuō),Object.create 允許你創(chuàng)建一個(gè)對(duì)象,該對(duì)象將在失敗的查找中委托給另一個(gè)對(duì)象。 換句話說(shuō),Object.create 允許你創(chuàng)建一個(gè)對(duì)象,只要該對(duì)象上的屬性查找失敗,它就可以查詢(xún)另一個(gè)對(duì)象以查看該另一個(gè)對(duì)象是否具有該屬性。 我們來(lái)看一些代碼:
const parent = { name: "Stacey", age: 35, heritage: "Irish" } const child = Object.create(parent) child.name = "Ryan" child.age = 7 console.log(child.name) // Ryan console.log(child.age) // 7 console.log(child.heritage) // Irish
因此,在上面的示例中,由于 child 是用 object.create(parent) 創(chuàng)建的,所以每當(dāng)child 對(duì)象上的屬性查找失敗時(shí),JavaScript 就會(huì)將該查找委托給 parent 對(duì)象。這意味著即使 child 沒(méi)有屬性 heritage ,當(dāng)你打印 child.heritage 時(shí),它會(huì)從 parent 對(duì)象中找到對(duì)應(yīng) heritage 并打印出來(lái)。
現(xiàn)在如何使用 Object.create 來(lái)簡(jiǎn)化之前的 Animal代碼? 好吧,我們可以使用Object.create 來(lái)委托給animalMethods對(duì)象,而不是像我們現(xiàn)在一樣逐一向 animal 添加所有共享方法。 為了B 格一點(diǎn),就叫做 使用共享方法 和 Object.create 的函數(shù)實(shí)例化。
使用共享方法 和 Object.create 的函數(shù)實(shí)例化const animalMethods = { eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount }, sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length }, play(length) { console.log(`${this.name} is playing.`) this.energy -= length } } function Animal (name, energy) { let animal = Object.create(animalMethods) animal.name = name animal.energy = energy return animal } const leo = Animal("Leo", 7) const snoop = Animal("Snoop", 10) leo.eat(10) snoop.play(5)
所以現(xiàn)在當(dāng)我們調(diào)用 leo.eat 時(shí),JavaScript 將在 leo 對(duì)象上查找 eat 方法,因?yàn)?
leo 中沒(méi)有 eat 方法,所以查找將失敗,由于 Object.create,它將委托給animalMethods對(duì)象,所以會(huì)從 animalMethods 對(duì)象上找到 eat 方法。
到現(xiàn)在為止還挺好。盡管如此,我們?nèi)匀豢梢宰龀鲆恍└倪M(jìn)。為了跨實(shí)例共享方法,必須管理一個(gè)多帶帶的對(duì)象(animalMethods)似乎有點(diǎn)“傻哈”。我們希望這在語(yǔ)言本身中實(shí)現(xiàn)的一個(gè)常見(jiàn)特,所以就需要引出下一個(gè)屬性 - prototype。
那么究竟 JavaScript 中的 prototype 是什么? 好吧,簡(jiǎn)單地說(shuō),JavaScript 中的每個(gè)函數(shù)都有一個(gè)引用對(duì)象的prototype屬性。
function doThing () {} console.log(doThing.prototype) // {}
如果不是創(chuàng)建一個(gè)多帶帶的對(duì)象來(lái)管理我們的方法(如上例中 animalMethods),我們只是將每個(gè)方法放在 Animal 函數(shù)的 prototype 上,該怎么辦? 然后我們所要做的就是不使用Object.create 委托給animalMethods,我們可以用它來(lái)委托Animal.prototype。 我們將這種模式稱(chēng)為 原型實(shí)例化。
原型(prototype)實(shí)例化function Animal (name, energy) { let animal = Object.create(Animal.prototype) animal.name = name animal.energy = energy return animal } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } const leo = Animal("Leo", 7) const snoop = Animal("Snoop", 10) leo.eat(10) snoop.play(5)
同樣,prototype 只是 JavaScript 中的每個(gè)函數(shù)都具有的一個(gè)屬性,正如我們前面看到的,它允許我們跨函數(shù)的所有實(shí)例共享方法。我們所有的功能仍然是相同的,但是現(xiàn)在我們不必為所有的方法管理一個(gè)多帶帶的對(duì)象,我們只需要使用 Animal 函數(shù)本身內(nèi)置的另一個(gè)對(duì)象Animal.prototype。
更進(jìn)一步現(xiàn)在我們知道三個(gè)點(diǎn):
如何創(chuàng)建構(gòu)造函數(shù)。
如何向構(gòu)造函數(shù)的原型添加方法。
如何使用 Object.create 將失敗的查找委托給函數(shù)的原型。
這三個(gè)點(diǎn)對(duì)于任何編程語(yǔ)言來(lái)說(shuō)都是非常基礎(chǔ)的。JavaScript 真的有那么糟糕,以至于沒(méi)有更簡(jiǎn)單的方法來(lái)完成同樣的事情嗎?正如你可能已經(jīng)猜到的那樣,現(xiàn)在已經(jīng)有了,它是通過(guò)使用new關(guān)鍵字來(lái)實(shí)現(xiàn)的。
回顧一下我們的 Animal 構(gòu)造函數(shù),最重要的兩個(gè)部分是創(chuàng)建對(duì)象并返回它。 如果不使用Object.create創(chuàng)建對(duì)象,我們將無(wú)法在失敗的查找上委托函數(shù)的原型。 如果沒(méi)有return語(yǔ)句,我們將永遠(yuǎn)不會(huì)返回創(chuàng)建的對(duì)象。
function Animal (name, energy) { let animal = Object.create(Animal.prototype) // 1 animal.name = name animal.energy = energy return animal // 2 }
關(guān)于 new,有一件很酷的事情——當(dāng)你使用new關(guān)鍵字調(diào)用一個(gè)函數(shù)時(shí),以下編號(hào)為1和2兩行代碼將隱式地(在底層)為你完成,所創(chuàng)建的對(duì)象被稱(chēng)為this。
使用注釋來(lái)顯示底層發(fā)生了什么,并假設(shè)用new關(guān)鍵字調(diào)用了Animal構(gòu)造函數(shù),可以這樣重寫(xiě)它。
function Animal (name, energy) { // const this = Object.create(Animal.prototype) this.name = name this.energy = energy // return this } const leo = new Animal("Leo", 7) const snoop = new Animal("Snoop", 10)
正常如下:
function Animal (name, energy) { this.name = name this.energy = energy } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } const leo = new Animal("Leo", 7) const snoop = new Animal("Snoop", 10)
再次說(shuō)明,之所以這樣做,并且這個(gè)對(duì)象是為我們創(chuàng)建的,是因?yàn)槲覀冇?b>new關(guān)鍵字調(diào)用了構(gòu)造函數(shù)。如果在調(diào)用函數(shù)時(shí)省略new,則永遠(yuǎn)不會(huì)創(chuàng)建該對(duì)象,也不會(huì)隱式地返回該對(duì)象。我們可以在下面的例子中看到這個(gè)問(wèn)題。
function Animal (name, energy) { this.name = name this.energy = energy } const leo = Animal("Leo", 7) console.log(leo) // undefined
這種模式稱(chēng)為 偽類(lèi)實(shí)例化。
對(duì)于那些不熟悉的人,類(lèi)允許你為對(duì)象創(chuàng)建藍(lán)圖。 然后,每當(dāng)你創(chuàng)建該類(lèi)的實(shí)例時(shí),你可以訪問(wèn)這個(gè)對(duì)象中定義的屬性和方法。
聽(tīng)起來(lái)有點(diǎn)熟? 這基本上就是我們對(duì)上面的 Animal 構(gòu)造函數(shù)所做的。 但是,我們只使用常規(guī)的舊 JavaScript 函數(shù)來(lái)重新創(chuàng)建相同的功能,而不是使用class關(guān)鍵字。 當(dāng)然,它需要一些額外的工作以及了解一些 JavaScript “底層” 發(fā)生的事情,但結(jié)果是一樣的。
這是個(gè)好消息。 JavaScript 不是一種死語(yǔ)言。 TC-39委員會(huì)不斷改進(jìn)和補(bǔ)充。 這意味著即使JavaScript的初始版本不支持類(lèi),也沒(méi)有理由將它們添加到官方規(guī)范中。 事實(shí)上,這正是TC-39委員會(huì)所做的。 2015 年,發(fā)布了EcmaScript(官方JavaScript規(guī)范)6,支持類(lèi)和class關(guān)鍵字。 讓我們看看上面的Animal構(gòu)造函數(shù)如何使用新的類(lèi)語(yǔ)法。
class Animal { constructor(name, energy) { this.name = name this.energy = energy } eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount } sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length } play(length) { console.log(`${this.name} is playing.`) this.energy -= length } } const leo = new Animal("Leo", 7) const snoop = new Animal("Snoop", 10)
這個(gè)相對(duì)前面的例子,是相對(duì)簡(jiǎn)單明了的。
因此,如果這是創(chuàng)建類(lèi)的新方法,為什么我們花了這么多時(shí)間來(lái)復(fù)習(xí)舊的方式呢? 原因是因?yàn)樾路椒ǎㄊ褂?b>class關(guān)鍵字)主要只是我們稱(chēng)之為偽類(lèi)實(shí)例化模式現(xiàn)有方式的“語(yǔ)法糖”。 為了完全理解 ES6 類(lèi)的便捷語(yǔ)法,首先必須理解偽類(lèi)實(shí)例化模式。
至此,我們已經(jīng)介紹了 JavaScript 原型的基本原理。這篇文章的其余部分將致力于理解與之相關(guān)的其他好話題。在另一篇文章中,我們將研究如何利用這些基本原理,并使用它們來(lái)理解JavaScript中的繼承是如何工作的。
數(shù)組方法我們?cè)谏厦嫔钊胗懻摿巳绾卧谝粋€(gè)類(lèi)的實(shí)例之間共享方法,你應(yīng)該將這些方法放在類(lèi)(或函數(shù))原型上。 如果我們查看Array類(lèi),我們可以看到相同的模式。
const friends = []
以為是代替使用 new Array() 的一個(gè)語(yǔ)法糖。
const friendsWithSugar = [] const friendsWithoutSugar = new Array()
你可能從未想過(guò)的一件事是,數(shù)組的每個(gè)實(shí)例如何具有所有內(nèi)置方法 (splice, slice, pop 等)?
正如你現(xiàn)在所知,這是因?yàn)檫@些方法存在于 Array.prototype 上,當(dāng)你創(chuàng)建新的Array實(shí)例時(shí),你使用new關(guān)鍵字在失敗的查找中將該委托設(shè)置為 Array.prototype。
我們可以打印 Array.prototype 來(lái)查看有哪些方法:
console.log(Array.prototype) /* concat: ?n concat() constructor: ?n Array() copyWithin: ?n copyWithin() entries: ?n entries() every: ?n every() fill: ?n fill() filter: ?n filter() find: ?n find() findIndex: ?n findIndex() forEach: ?n forEach() includes: ?n includes() indexOf: ?n indexOf() join: ?n join() keys: ?n keys() lastIndexOf: ?n lastIndexOf() length: 0n map: ?n map() pop: ?n pop() push: ?n push() reduce: ?n reduce() reduceRight: ?n reduceRight() reverse: ?n reverse() shift: ?n shift() slice: ?n slice() some: ?n some() sort: ?n sort() splice: ?n splice() toLocaleString: ?n toLocaleString() toString: ?n toString() unshift: ?n unshift() values: ?n values() */
對(duì)象也存在完全相同的邏輯。所有的對(duì)象將在失敗的查找后委托給 Object.prototype,這就是所有對(duì)象都有 toString 和 hasOwnProperty 等方法的原因
靜態(tài)方法到目前為止,我們已經(jīng)討論了為什么以及如何在類(lèi)的實(shí)例之間共享方法。但是,如果我們有一個(gè)對(duì)類(lèi)很重要的方法,但是不需要在實(shí)例之間共享該方法怎么辦?例如,如果我們有一個(gè)函數(shù),它接收一系列 Animal 實(shí)例,并確定下一步需要喂養(yǎng)哪一個(gè)呢?我們這個(gè)方法叫做 nextToEat。
function nextToEat (animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy[0].name }
因?yàn)槲覀儾幌M谒袑?shí)例之間共享 nextToEat,所以在 Animal.prototype上使用nextToEat 是沒(méi)有意義的。 相反,我們可以將其視為輔助方法。
所以如果nextToEat不應(yīng)該存在于Animal.prototype中,我們應(yīng)該把它放在哪里? 顯而易見(jiàn)的答案是我們可以將nextToEat放在與我們的Animal類(lèi)相同的范圍內(nèi),然后像我們通常那樣在需要時(shí)引用它。
class Animal { constructor(name, energy) { this.name = name this.energy = energy } eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount } sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length } play(length) { console.log(`${this.name} is playing.`) this.energy -= length } } function nextToEat (animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy[0].name } const leo = new Animal("Leo", 7) const snoop = new Animal("Snoop", 10) console.log(nextToEat([leo, snoop])) // Leo
這是可行的,但是還有一個(gè)更好的方法。
只要有一個(gè)特定于類(lèi)本身的方法,但不需要在該類(lèi)的實(shí)例之間共享,就可以將其定義為類(lèi)的靜態(tài)屬性。
class Animal { constructor(name, energy) { this.name = name this.energy = energy } eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount } sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length } play(length) { console.log(`${this.name} is playing.`) this.energy -= length } static nextToEat(animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy[0].name } }
現(xiàn)在,因?yàn)槲覀冊(cè)陬?lèi)上添加了nextToEat作為靜態(tài)屬性,所以它存在于Animal類(lèi)本身(而不是它的原型)上,并且可以使用Animal.nextToEat進(jìn)行調(diào)用 。
const leo = new Animal("Leo", 7) const snoop = new Animal("Snoop", 10) console.log(Animal.nextToEat([leo, snoop])) // Leo
因?yàn)槲覀冊(cè)谶@篇文章中都遵循了類(lèi)似的模式,讓我們來(lái)看看如何使用 ES5 完成同樣的事情。 在上面的例子中,我們看到了如何使用 static 關(guān)鍵字將方法直接放在類(lèi)本身上。 使用 ES5,同樣的模式就像手動(dòng)將方法添加到函數(shù)對(duì)象一樣簡(jiǎn)單。
function Animal (name, energy) { this.name = name this.energy = energy } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } Animal.nextToEat = function (nextToEat) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy[0].name } const leo = new Animal("Leo", 7) const snoop = new Animal("Snoop", 10) console.log(Animal.nextToEat([leo, snoop])) // Leo獲取對(duì)象的原型
無(wú)論您使用哪種模式創(chuàng)建對(duì)象,都可以使用Object.getPrototypeOf方法完成獲取該對(duì)象的原型。
function Animal (name, energy) { this.name = name this.energy = energy } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } const leo = new Animal("Leo", 7) const proto = Object.getPrototypeOf(leo) console.log(proto ) // {constructor: ?, eat: ?, sleep: ?, play: ?} proto === Animal.prototype // true
上面的代碼有兩個(gè)重要的要點(diǎn)。
首先,你將注意到 proto 是一個(gè)具有 4 個(gè)方法的對(duì)象,constructor、eat、sleep和play。這是有意義的。我們使用getPrototypeOf傳遞實(shí)例,leo取回實(shí)例原型,這是我們所有方法的所在。
這也告訴了我們關(guān)于 prototype 的另一件事,我們還沒(méi)有討論過(guò)。默認(rèn)情況下,prototype對(duì)象將具有一個(gè) constructor 屬性,該屬性指向初始函數(shù)或創(chuàng)建實(shí)例的類(lèi)。這也意味著因?yàn)?JavaScript 默認(rèn)在原型上放置構(gòu)造函數(shù)屬性,所以任何實(shí)例都可以通過(guò)。
第二個(gè)重要的點(diǎn)是: Object.getPrototypeOf(leo) === Animal.prototype。 這也是有道理的。 Animal 構(gòu)造函數(shù)有一個(gè)prototype屬性,我們可以在所有實(shí)例之間共享方法,getPrototypeOf 允許我們查看實(shí)例本身的原型。
function Animal (name, energy) { this.name = name this.energy = energy } const leo = new Animal("Leo", 7) console.log(leo.constructor) // Logs the constructor function
為了配合我們之前使用 Object.create 所討論的內(nèi)容,其工作原理是因?yàn)槿魏?b>Animal實(shí)例都會(huì)在失敗的查找中委托給Animal.prototype。 因此,當(dāng)你嘗試訪問(wèn)leo.constructor時(shí),leo沒(méi)有 constructor 屬性,因此它會(huì)將該查找委托給 Animal.prototype,而Animal.prototype 確實(shí)具有構(gòu)造函數(shù)屬性。
你之前可能看過(guò)使用 __proto__ 用于獲取實(shí)例的原型,這是過(guò)去的遺物。 相反,如上所述使用 Object.getPrototypeOf(instance)判斷原型上是否包含某個(gè)屬性
在某些情況下,你需要知道屬性是否存在于實(shí)例本身上,還是存在于對(duì)象委托的原型上。 我們可以通過(guò)循環(huán)打印我們創(chuàng)建的leo對(duì)象來(lái)看到這一點(diǎn)。 使用for in 循環(huán)方式如下:
function Animal (name, energy) { this.name = name this.energy = energy } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } const leo = new Animal("Leo", 7) for(let key in leo) { console.log(`Key: ${key}. Value: ${leo[key]}`) }
我所期望的打印結(jié)果可能如下:
Key: name. Value: Leo Key: energy. Value: 7
然而,如果你運(yùn)行代碼,看到的是這樣的-
Key: name. Value: Leo Key: energy. Value: 7 Key: eat. Value: function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Key: sleep. Value: function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Key: play. Value: function (length) { console.log(`${this.name} is playing.`) this.energy -= length }
這是為什么? 對(duì)于for in循環(huán)來(lái)說(shuō),循環(huán)將遍歷對(duì)象本身以及它所委托的原型的所有可枚舉屬性。 因?yàn)槟J(rèn)情況下,你添加到函數(shù)原型的任何屬性都是可枚舉的,我們不僅會(huì)看到name 和energy,還會(huì)看到原型上的所有方法 -eat,sleep,play。
要解決這個(gè)問(wèn)題,我們需要指定所有原型方法都是不可枚舉的,或者只打印屬性位于 leo 對(duì)象本身而不是leo委托給失敗查找的原型。 這是 hasOwnProperty 可以幫助我們的地方。
... const leo = new Animal("Leo", 7) for(let key in leo) { if (leo.hasOwnProperty(key)) { console.log(`Key: ${key}. Value: ${leo[key]}`) } }
現(xiàn)在我們看到的只是leo對(duì)象本身的屬性,而不是leo委托的原型。
Key: name. Value: Leo Key: energy. Value: 7
果你仍然對(duì) hasOwnProperty 感到困惑,這里有一些代碼可以幫你更好的理清它。
function Animal (name, energy) { this.name = name this.energy = energy } Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } const leo = new Animal("Leo", 7) leo.hasOwnProperty("name") // true leo.hasOwnProperty("energy") // true leo.hasOwnProperty("eat") // false leo.hasOwnProperty("sleep") // false leo.hasOwnProperty("play") // false檢查對(duì)象是否是類(lèi)的實(shí)例
有時(shí)你想知道對(duì)象是否是特定類(lèi)的實(shí)例。 為此,你可以使用instanceof運(yùn)算符。 用例非常簡(jiǎn)單,但如果你以前從未見(jiàn)過(guò)它,實(shí)際的語(yǔ)法有點(diǎn)奇怪。 它的工作方式如下
object instanceof Class
如果 object 是Class的實(shí)例,則上面的語(yǔ)句將返回 true,否則返回 false。 回到我們的 Animal 示例,我們有類(lèi)似的東西:
function Animal (name, energy) { this.name = name this.energy = energy } function User () {} const leo = new Animal("Leo", 7) leo instanceof Animal // true leo instanceof User // false
instanceof 的工作方式是檢查對(duì)象原型鏈中是否存在 constructor.prototype。 在上面的例子中,leo instanceof Animal 為 true,因?yàn)?Object.getPrototypeOf(leo) === Animal.prototype。 另外,leo instanceof User 為 false,因?yàn)?b>Object.getPrototypeOf(leo) !== User.prototype。
創(chuàng)建新的不可知的構(gòu)造函數(shù)你能找出下面代碼中的錯(cuò)誤嗎
function Animal (name, energy) { this.name = name this.energy = energy } const leo = Animal("Leo", 7)
即使是經(jīng)驗(yàn)豐富的 JavaScript 開(kāi)發(fā)人員有時(shí)也會(huì)被上面的例子絆倒。因?yàn)槲覀兪褂玫氖乔懊鎸W(xué)過(guò)的偽類(lèi)實(shí)例模式,所以在調(diào)用Animal構(gòu)造函數(shù)時(shí),需要確保使用new關(guān)鍵字調(diào)用它。如果我們不這樣做,那么 this 關(guān)鍵字就不會(huì)被創(chuàng)建,它也不會(huì)隱式地返回。
作為復(fù)習(xí),注釋掉的行是在函數(shù)上使用new關(guān)鍵字時(shí)背后發(fā)生的事情。
function Animal (name, energy) { // const this = Object.create(Animal.prototype) this.name = name this.energy = energy // return this }
讓其他開(kāi)發(fā)人員記住,這似乎是一個(gè)非常重要的細(xì)節(jié)。 假設(shè)我們正在與其他開(kāi)發(fā)人員合作,我們是否有辦法確保始終使用new關(guān)鍵字調(diào)用我們的Animal構(gòu)造函數(shù)? 事實(shí)證明,可以通過(guò)使用我們之前學(xué)到的instanceof運(yùn)算符來(lái)實(shí)現(xiàn)的。
如果使用new關(guān)鍵字調(diào)用構(gòu)造函數(shù),那么構(gòu)造函數(shù)體的內(nèi)部 this 將是構(gòu)造函數(shù)本身的實(shí)例。
function Aniam (name, energy) { if (this instanceof Animal === false) { console.warn("Forgot to call Animal with the new keyword") } this.name = name this.energy = energy }
現(xiàn)在,如果我們重新調(diào)用函數(shù),但是這次使用 new 的關(guān)鍵字,而不是僅僅向函數(shù)的調(diào)用者打印一個(gè)警告呢?
function Animal (name, energy) { if (this instanceof Animal === false) { return new Animal(name, energy) } this.name = name this.energy = energy }
現(xiàn)在,不管是否使用new關(guān)鍵字調(diào)用Animal,它仍然可以正常工作。
重寫(xiě) Object.create在這篇文章中,我們非常依賴(lài)于Object.create來(lái)創(chuàng)建委托給構(gòu)造函數(shù)原型的對(duì)象。 此時(shí),你應(yīng)該知道如何在代碼中使用Object.create,但你可能沒(méi)有想到的一件事是Object.create實(shí)際上是如何工作的。 為了讓你真正了解Object.create的工作原理,我們將自己重新創(chuàng)建它。 首先,我們對(duì)Object.create的工作原理了解多少?
它接受一個(gè)對(duì)象的參數(shù)。
它創(chuàng)建一個(gè)對(duì)象,在查找失敗時(shí)委托給參數(shù)對(duì)象
它返回新創(chuàng)建的對(duì)象。
Object.create = function (objToDelegateTo) { }
現(xiàn)在,我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象,該對(duì)象將在失敗的查找中委托給參數(shù)對(duì)象。 這個(gè)有點(diǎn)棘手。 為此,我們將使用 new 關(guān)鍵字相關(guān)的知識(shí)。
首先,在 Object.create 主體內(nèi)部創(chuàng)建一個(gè)空函數(shù)。 然后,將空函數(shù)的 prototype 設(shè)置為等于傳入?yún)?shù)對(duì)象。 然后,返回使用new關(guān)鍵字調(diào)用我們的空函數(shù)。
Object.create = function (objToDelegateTo) { function Fn(){} Fn.prototype = objToDelegateTo return new Fn() }
當(dāng)我們?cè)谏厦娴拇a中創(chuàng)建一個(gè)新函數(shù)Fn時(shí),它帶有一個(gè)prototype屬性。 當(dāng)我們使用new關(guān)鍵字調(diào)用它時(shí),我們知道我們將得到的是一個(gè)將在失敗的查找中委托給函數(shù)原型的對(duì)象。
如果我們覆蓋函數(shù)的原型,那么我們可以決定在失敗的查找中委托哪個(gè)對(duì)象。 所以在上面的例子中,我們用調(diào)用Object.create時(shí)傳入的對(duì)象覆蓋Fn的原型,我們稱(chēng)之為objToDelegateTo。
請(qǐng)注意,我們只支持 Object.create 的單個(gè)參數(shù)。 官方實(shí)現(xiàn)還支持第二個(gè)可選參數(shù),該參數(shù)允許你向創(chuàng)建的對(duì)象添加更多屬性。箭頭函數(shù)
箭頭函數(shù)沒(méi)有自己的this關(guān)鍵字。 因此,箭頭函數(shù)不能是構(gòu)造函數(shù),如果你嘗試使用new關(guān)鍵字調(diào)用箭頭函數(shù),它將引發(fā)錯(cuò)誤。
const Animal = () => {} const leo = new Animal() // Error: Animal is not a constructor
另外,因?yàn)槲覀冊(cè)谏厦嬲f(shuō)明了偽類(lèi)實(shí)例模式不能與箭頭函數(shù)一起使用,所以箭頭函數(shù)也沒(méi)有原型屬性。
const Animal = () => {} console.log(Animal.prototype) // undefined
代碼部署后可能存在的BUG沒(méi)法實(shí)時(shí)知道,事后為了解決這些BUG,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具 Fundebug。
你的點(diǎn)贊是我持續(xù)分享好東西的動(dòng)力,歡迎點(diǎn)贊!
交流干貨系列文章匯總?cè)缦拢X(jué)得不錯(cuò)點(diǎn)個(gè)Star,歡迎 加群 互相學(xué)習(xí)。
https://github.com/qq44924588...
我是小智,公眾號(hào)「大遷世界」作者,對(duì)前端技術(shù)保持學(xué)習(xí)愛(ài)好者。我會(huì)經(jīng)常分享自己所學(xué)所看的干貨,在進(jìn)階的路上,共勉!
關(guān)注公眾號(hào),后臺(tái)回復(fù)福利,即可看到福利,你懂的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/103789.html
摘要:設(shè)計(jì)模式是以面向?qū)ο缶幊虨榛A(chǔ)的,的面向?qū)ο缶幊毯蛡鹘y(tǒng)的的面向?qū)ο缶幊逃行┎顒e,這讓我一開(kāi)始接觸的時(shí)候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解設(shè)計(jì)模式必須要先搞懂面向?qū)ο缶幊蹋駝t只會(huì)讓你自己更痛苦。 JavaScript 中的構(gòu)造函數(shù) 學(xué)習(xí)總結(jié)。知識(shí)只有分享才有存在的意義。 是時(shí)候替換你的 for 循環(huán)大法了~ 《小分享》JavaScript中數(shù)組的那些迭代方法~ ...
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當(dāng)作缺點(diǎn)提及,但是只要善于運(yùn)用,其實(shí)基于原型的繼承模型比傳統(tǒng)的類(lèi)繼承還要強(qiáng)大。中文指南基本操作指南二繼續(xù)熟悉的幾對(duì)方法,包括,,。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。 怎樣使用 this 因?yàn)楸救藢儆趥吻岸耍虼宋闹兄豢炊?8 成左右,希望能夠給大家?guī)?lái)幫助....(據(jù)說(shuō)是阿里的前端妹子寫(xiě)的) this 的值到底...
摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對(duì)于解釋型語(yǔ)言例如來(lái)說(shuō),通過(guò)詞法分析語(yǔ)法分析語(yǔ)法樹(shù),就可以開(kāi)始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫(xiě)在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...
摘要:創(chuàng)建對(duì)象的最常用方法是使用花括號(hào),并使用點(diǎn)表示法向?qū)ο筇砑訉傩院头椒ā.?dāng)然,下一步是將邏輯封裝在我們可以在需要?jiǎng)?chuàng)建新動(dòng)物時(shí)調(diào)用的函數(shù)內(nèi)部。我們將這種模式稱(chēng)為,我們將函數(shù)本身稱(chēng)為構(gòu)造函數(shù),因?yàn)樗?fù)責(zé)構(gòu)造一個(gè)新對(duì)象。 視頻Videohttps://www.youtube.com/watch... 前言 如果不好好的學(xué)習(xí)對(duì)象,你就無(wú)法在JavaScript中獲得很大的成就。它們幾乎是Java...
摘要:創(chuàng)建對(duì)象的最常用方法是使用花括號(hào),并使用點(diǎn)表示法向?qū)ο筇砑訉傩院头椒ā.?dāng)然,下一步是將邏輯封裝在我們可以在需要?jiǎng)?chuàng)建新動(dòng)物時(shí)調(diào)用的函數(shù)內(nèi)部。我們將這種模式稱(chēng)為,我們將函數(shù)本身稱(chēng)為構(gòu)造函數(shù),因?yàn)樗?fù)責(zé)構(gòu)造一個(gè)新對(duì)象。 視頻Videohttps://www.youtube.com/watch... 前言 如果不好好的學(xué)習(xí)對(duì)象,你就無(wú)法在JavaScript中獲得很大的成就。它們幾乎是Java...
摘要:創(chuàng)建對(duì)象的最常用方法是使用花括號(hào),并使用點(diǎn)表示法向?qū)ο筇砑訉傩院头椒ā.?dāng)然,下一步是將邏輯封裝在我們可以在需要?jiǎng)?chuàng)建新動(dòng)物時(shí)調(diào)用的函數(shù)內(nèi)部。我們將這種模式稱(chēng)為,我們將函數(shù)本身稱(chēng)為構(gòu)造函數(shù),因?yàn)樗?fù)責(zé)構(gòu)造一個(gè)新對(duì)象。 視頻Videohttps://www.youtube.com/watch... 前言 如果不好好的學(xué)習(xí)對(duì)象,你就無(wú)法在JavaScript中獲得很大的成就。它們幾乎是Java...
閱讀 1307·2019-08-30 15:44
閱讀 1979·2019-08-30 13:49
閱讀 1651·2019-08-26 13:54
閱讀 3484·2019-08-26 10:20
閱讀 3239·2019-08-23 17:18
閱讀 3294·2019-08-23 17:05
閱讀 2129·2019-08-23 15:38
閱讀 1012·2019-08-23 14:35