JavaScript Prototype(原型) 新手指南

2021-03-02 web前端開發

英文原文 | https://tylermcginnis.com/beginners-guide-to-javascript-prototype/

引言:如果不處理對象,您就無法在 JavaScript 方面取得很大進展。它們幾乎是 JavaScript 程式語言的所有方面的基礎。在這篇文章中,您將了解用於實例化新對象的各種模式,在此過程中,您將逐步深入了解 JavaScript 的原型。

基礎入門

如果不處理對象,您就無法在 JavaScript 方面取得很大進展。它們幾乎是 JavaScript 程式語言的所有方面的基礎。事實上,學習如何創建對象可能是你剛開始學習的第一件事。話雖如此,為了最有效地學習 JavaScript 中的原型,我們將從基礎開始。

首先,對象是鍵/值對。創建對象的最常用方法是使用花括號{},並使用點表示法向對象添加屬性和方法。

JavaScript 代碼:

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

}

這個很簡單。現在,我們在應用程式中我們需要創建多個動物。很自然地,下一步就是將邏輯封裝到一個函數中,以便我們在需要創建新動物時調用這個函數。我們將調用這個模式 Functional Instantiation(函數實例化),並將函數本身稱為 constructor function(構造函數) ,因為它負責「構造」一個新對象。

Functional Instantiation (函數實例化)


JavaScript 代碼:

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)

現在,每當我們想要創建一種新動物(或者更廣泛地說是一種新的「實例」)時,我們所要做的就是調用我們的 Animal 函數,將動物的 name 和energy 傳遞給這個函數。這非常有效,而且非常簡單。但是,你有發現這種模式的不足之處嗎?我們要嘗試解決的最大的問題與三種方法有關 – eat,sleep 和 play。這些方法中的每一種都不僅是動態的,而且它們也是完全通用的。這意味著沒有理由重新創建這些方法,正如我們在創建新動物時所做的那樣。

我們只是在浪費內存,讓每個動物物體都比它需要的更大。你能想到一個解決方案嗎?

如果我們每次創建一個新動物時不需要重新創建這些方法,而是將它們移動到它們自己的對象上,那麼我們就可以讓每個動物引用那個對象了?我們可以把這種模式稱為 Functional Instantiation with Shared Methods(共享方法的函數實例化) 。描述起來有點囉嗦 🤷‍ 。

Functional Instantiation with Shared Methods (共享方法的函數實例化)

JavaScript 代碼:

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)

通過將共享方法移動到它們自己的對象並在 Animal 函數中引用該對象,我們現在已經解決了內存浪費和動物對象過大的問題。

Object.create

讓我們使用 Object.create 再次改進我們的例子。 簡而言之,Object.create 允許您創建一個對象,該對象將在查找失敗時委託給另一個對象。 換句話說,Object.create 允許您創建一個對象,只要該對象上的屬性查找失敗,它就可以查詢另一個對象,以查看另一個對象中是否具有該屬性。 說清楚需要很多文字, 我們來看一些代碼。

JavaScript 代碼:

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) 創建的,所以每當在 child 中查找屬性失敗時,JavaScript 就會將該查找委託給 parent 對象。這意味著即使 child 沒有 heritage 屬性,當你查找 child.heritage 時你會得到 parent 的heritage 屬性,即 Irish。

現在,通過使用 Object.create ,我們該如何使用它來簡化之前的 Animal 代碼呢?好吧,我們可以使用 Object.create 委託給animalMethods 對象,而不是像我們之前一樣逐個將所有共享方法添加到 Animal 中。 為了聽起來很智能,讓我們稱之為 Functional Instantiation with Shared Methods and Object.create(使用共享方法和Object.create進行函數實例化)  。

Functional Instantiation with Shared Methods and Object.create (使用共享方法和Object.create進行函數實例化)

JavaScript 代碼:

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)

所以現在當我們調用 leo.eat 時,JavaScript 會在 leo 對象上查找 eat 方法。 這個查找將失敗,因為使用了 Object.create,它將委託給 animalMethods 對象,然後在這裡將找到 eat 方法。

到現在為止還挺好的。 儘管如此,我們仍然可以做出一些改進。 為了跨實例共享方法,必須管理一個單獨的對象(animalMethods)似乎有點「hacky」。 這似乎是您希望在語言本身中實現的常見功能。 事實證明,這就是你看這篇文章的原因 – prototype(原型) 。

那麼究竟什麼是 JavaScript 的 prototype(原型)呢? 簡單地說,JavaScript 中的每個函數都有一個引用對象的 prototype 屬性。 我們來親自測試一下。

JavaScript 代碼:

function doThing () {}

console.log(doThing.prototype) // {}

如果不是創建一個單獨的對象(比如我們正在使用的 animalMethods )來管理我們的方法,也就是我們只是將每個方法放在 Animal 函數的 prototype(原型) 對象上,該怎麼辦呢?我們所要做的就是不使用 Object.create 委託給 animalMethods,我們可以用使用來委託Animal.prototype。 我們將這種模式稱為 Prototypal Instantiation(原型實例化)。

Prototypal Instantiation (原型實例化)


JavaScript 代碼:

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)

這裡你可以為自己鼓掌鼓勵一下了。 

同樣,原型只是 JavaScript 中每個函數都具有的屬性,並且如上所述,它允許我們在函數的所有實例之間共享方法。 我們所有的功能仍然相同,但現在我們不必為所有方法管理一個單獨的對象,我們可以使用另一個內置於 Animal 函數本身的對象Animal.prototype 。

更深的,走起!

首先,我們需要知道三件事:

如何創建構造函數。

如何將方法添加到構造函數的原型中。

如何使用 Object.create 將失敗的查找委託給函數的原型。

這三個任務似乎是任何程式語言的基礎。 JavaScript 是否真的那麼糟糕,沒有更簡單,「內置」的方式來完成同樣的事情? 正如你可能已經猜測的那樣,它是通過使用 new 關鍵字。

我們採用的這種緩慢而有條理的方法的好處是,您現在可以深入了解 JavaScript 中的 new 關鍵字在幕後的作用。

回顧一下我們的 Animal 構造函數,最重要的兩個部分是創建對象並返回它。 如果不使用 Object.create創建對象,我們將無法在查找失敗時委託給函數的原型。 如果沒有 return 語句,我們將永遠不會返回創建的對象。

JavaScript 代碼:

function Animal (name, energy) {

 let animal = Object.create(Animal.prototype)

 animal.name = name

 animal.energy = energy

 

 return animal

}

new 有一個很酷的地方——當您使用 new 關鍵字調用函數時,注釋掉的這兩行代碼是隱式(引擎)完成的,創建的對象稱為 this。

使用注釋來顯示在幕後發生的事情並假設使用 new 關鍵字調用 Animal 構造函數,可以將其重寫為這樣:

JavaScript 代碼:

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)

去掉注釋後:

JavaScript 代碼:

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)

同樣,這樣做以及為我們創建 this 對象的原因是,我們使用 new 關鍵字調用構造函數。如果在調用函數時不使用 new ,則該對象永遠不會創建,也不會隱式返回。我們可以在下面的例子中看到這個問題。

JavaScript 代碼:

function Animal (name, energy) {

 this.name = name

 this.energy = energy

}

 

const leo = Animal('Leo', 7)

console.log(leo) // undefined

此模式的名稱是 Pseudoclassical Instantiation(偽類實例化) 。

如果 JavaScript 不是您的第一種程式語言,您可能會有點不安。

「WTF這個傢伙只是重新創造了一個更糟糕的版本」 – 你

對於那些不熟悉的人,Class(類) 允許您為對象創建模板。然後,無論何時創建該類的實例,都會獲得一個具有模板中定義的屬性和方法的對象。

聽起來有點熟悉?這基本上就是我們對上面的 Animal 構造函數所做的事情。但是對於 Animal 構造函數,我們只使用常規的舊 JavaScript 函數來重新創建相同的功能,而不是使用 class 關鍵字。當然,它需要一些額外的工作以及一些關於 JavaScript 「引擎」 所處理的事情的相關知識,但結果是一樣的。

這是個好消息。 JavaScript 不是一種 「死」 語言。它不斷得到改進,並由 TC-39委員會 不斷的制定標準。這意味著即使 JavaScript 的初始版本不支持類,也不影響後續將它們添加到官方規範中。事實上,這正是TC-39委員會所做的事情。 2015年,發布了 EcmaScript(官方JavaScript規範)6 ,支持 Classes(類) 和 class 關鍵字。讓我們看看上面的 Animal 構造函數如何使用新的 class(類) 語法。

JavaScript 代碼:

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)

很乾淨是吧?

因此,如果這是創建類的新方法,為什麼我們前面花了這麼多時間來討論舊的方式呢? 原因是新的方式(使用 class 關鍵字)只是經典偽類模式的 「語法糖」。 為了完全理解 ES6 類的便捷語法,首先必須理解經典的偽類模式。

關於 ES6 Classes(類) 更多相關信息可以查看 面向對象的 JavaScript – 深入了解 ES6 類。

我們已經介紹了 JavaScript 原型的基礎知識。 本文的其餘部分將致力於加深理解相關知識的主題。 在另一篇文章中,我們將看看如何利用這些基礎知識並使用它們來理解 JavaScript 中繼承的工作原理。

數組方法

我們在上面深入討論了如何在類的實例之間共享方法,您應該將這些方法放在類(或函數)原型上。 如果我們查看 Array 類,我們可以看到相同的模式。 從歷史上看,您可能已經創建了這樣的數組:

JavaScript 代碼:

const friends = []

事實證明,這只是創建一個新的 Array 類實例的語法糖。

JavaScript 代碼:

const friendsWithSugar = []

 

const friendsWithoutSugar = new Array()

您可能從未想過:數組的每個實例是如何具有所有內置方法的(splice , slice, pop 等)?

正如您現在所知,這是因為這些方法存在於 Array.prototype 上,當您創建一個新的 Array 實例時,您使用 new 關鍵字在查找失敗時將該委託設置為 Array.prototype 。

我們可以通過簡單地 console.log(Array.prototype) 來查看所有數組的方法。

JavaScript 代碼:

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

*/

Objects(對象) 也是完全相同的邏輯。 所有對象將在查找失敗時委託給 Object.prototype ,這就是所有對象都有 toString 和hasOwnProperty 等方法的原因。

靜態方法

到目前為止,我們已經介紹了為什麼,以及如何在類的實例之間共享方法。 但是,如果我們有一個對 Class 很重要但不需要又跨實例共享的方法,該怎麼辦呢? 例如,如果我們有一個函數,它接收一系列 Animal 實例並決定下一個需要餵食的對象,會怎樣? 我們將其稱為 nextToEat。

JavaScript 代碼:

function nextToEat (animals) {

 const sortedByLeastEnergy = animals.sort((a,b) => {

   return a.energy - b.energy

 })

 

 return sortedByLeastEnergy[0].name

}

我們不希望在所有實例之間共享它,所以在 Animal.prototype 上使用 nextToEat 是沒有意義的。 相反,我們可以將其視為輔助方法。 所以如果 nextToEat 不應該存在於 Animal.prototype 中,我們應該把它放在哪裡呢? 那麼顯而易見的答案是我們可以將 nextToEat 放在與 Animal 類相同的作用域中,然後像我們平常那樣,在需要時引用它。

JavaScript 代碼:

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

現在這可行,但有更好的方法。

只要有一個特定於類本身的方法,但不需要在該類的實例之間共享,就可以將其添加為類的 static(靜態) 屬性。

JavaScript 代碼:

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

 }

}

現在,因為我們在類上添加了 nextToEat 作為 static(靜態) 屬性,所以它存在於 Animal 類本身(而不是它的原型)中,並且可以使用 Animal.nextToEat 進行訪問。

JavaScript 代碼:

const leo = new Animal('Leo', 7)

const snoop = new Animal('Snoop', 10)

 

console.log(Animal.nextToEat([leo, snoop])) // Leo

因為我們在這篇文章中都遵循了類似的模式,讓我們來看看如何使用 ES5 完成同樣的事情。 在上面的例子中,我們看到了如何使用 static 關鍵字將方法直接放在類本身上。 使用ES5,同樣的模式就像手動將方法添加到函數對象一樣簡單。

JavaScript 代碼:

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

獲取對象的原型

無論您使用哪種模式創建對象,都可以使用 Object.getPrototypeOf 方法完成獲取該對象的原型。

JavaScript 代碼:

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 prototype = Object.getPrototypeOf(leo)

 

console.log(prototype)

// {constructor: ?, eat: ?, sleep: ?, play: ?}

 

prototype === Animal.prototype // true

上面的代碼有兩個要點。

首先,你會注意到 proto 是一個對象,有4種方法,constructor,eat,sleep,和play。那講得通。我們將實例傳遞給getPrototypeOf,leo 獲取了實例的原型,這裡是所有的方法。這提示我們,關於原型的另外一件事我們還沒有討論過。默認情況下,原型對象將具有 constructor 屬性,該屬性指向原始函數或創建實例的類。這也意味著 JavaScript 默認在原型上放置 constructor 屬性,所以任何實例都可以通過 instance.constructor 訪問它們的構造函數。

上面的第二個要點是 Object.getPrototypeOf(leo) === Animal.prototype 。這也是有道理的。 Animal 構造函數有一個 prototype(原型) 屬性,我們可以在所有實例之間共享方法,getPrototypeOf 允許我們查看實例本身的原型。

JavaScript 代碼:

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 所討論的內容,其工作原因是因為任何 Animal 實例都會在查找失敗時委託給 Animal.prototype。 因此,當您嘗試訪問 leo.prototype 時, leo 沒有 prototype 屬性,因此它會將該查找委託給Animal.prototype,它確實具有 constructor 屬性。 如果這段沒有看懂,請回過頭來閱讀上面的 Object.create 。

您可能以前看到過使用 __proto__ 獲取實例的原型。 這是過去的遺物。 現在,如上所述使用 Object.getPrototypeOf(instance) 獲取實例的原型。

確定屬性是否存在於原型上

在某些情況下,您需要知道屬性是否存在於實例本身上,還是存在於對象委託的原型上。 我們可以通過循環我們創建的 leo 對象來知道這一點。假設目標是循環 leo 並記錄其所有鍵和值。使用 for in 循環,可能看起來像這樣。

JavaScript 代碼:

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

}

你期望看到什麼?最有可能的是,它是這樣的 –

JavaScript 代碼:

Key: name. Value: Leo

Key: energy. Value: 7

但是,如果你運行代碼,你看到的是這樣的 –

JavaScript 代碼:

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

}

這是為什麼? for in 循環將循環遍歷對象本身以及它所委託的原型的所有 可枚舉屬性 。 因為默認情況下,您添加到函數原型的任何屬性都是可枚舉的,我們不僅會看到name 和 energy ,還會看到原型上的所有方法 – eat,sleep 和 play 。 要解決這個問題,我們需要指定所有原型方法都是不可枚舉的,或者如果屬性在 leo 對象本身上而不是 leo 查找失敗時委託給的原型上。 hasOwnProperty 可以幫助我們實現這個需求。

hasOwnProperty 是每個對象上的一個屬性,它返回一個布爾值,指示對象是否具有指定的屬性作為其自身的屬性,而不是對象委託給的原型。 這正是我們所需要的。 現在有了這些新知識,我們可以修改我們的代碼,以便利用 for in 循環中的 hasOwnProperty 。

JavaScript 代碼:

...

 

const leo = new Animal('Leo', 7)

 

for(let key in leo) {

 if (leo.hasOwnProperty(key)) {

   console.log(`Key: ${key}. Value: ${leo[key]}`)

 }

}

而現在我們看到的只是 leo 對象本身的屬性,而不是 leo 原型中的方法。

JavaScript 代碼:

Key: name. Value: Leo

Key: energy. Value: 7

如果你仍然對 hasOwnProperty 感到困惑,這裡有一些代碼可以幫你消除困惑。

JavaScript 代碼:

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

檢查對象是否是類的實例

有時您想知道對象是否是指定類的實例。 為此,您可以使用 instanceof 運算符。 用例非常簡單,但如果您以前從未見過它,實際的語法有點奇怪。 它的工作原理如下

JavaScript 代碼:

object instanceof Class

如果 object 是 Class 的實例,則上面的語句將返回 true ,否則返回 false 。回到我們的 Animal 示例,我們會有類似的東西。

JavaScript 代碼:

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 的工作方式是檢查對象原型鏈中是否存在 constructor.prototype 。 在上面的例子中,leo instanceof Animal 為 true ,因為Object.getPrototypeOf(leo) === Animal.prototype。 另外,leo instanceof User 為 false ,因為 Object.getPrototypeOf(leo) !== User.prototype。

創建新的不可知構造函數

你能發現下面代碼中的錯誤嗎?

JavaScript 代碼:

function Animal (name, energy) {

 this.name = name

 this.energy = energy

}

 

const leo = Animal('Leo', 7)

即使是經驗豐富的 JavaScript 開發人員有時也會因為上面的例子而被絆倒。 因為我們正在使用之前學過的 pseudoclassical pattern(經典偽類模式),所以當調用 Animal 構造函數時,我們需要確保使用 new 關鍵字調用它。 如果我們不這樣做,則不會創建 this 關鍵字,也不會隱式返回。

作為複習,注釋掉的行是在函數上使用 new 關鍵字時幕後所做的事情。

JavaScript 代碼:

function Animal (name, energy) {

 // const this = Object.create(Animal.prototype)

 

 this.name = name

 this.energy = energy

 

 // return this

}

這似乎是一個非常重要的細節,讓其他開發人員記住。 假設我們正在與其他開發人員合作,有沒有辦法確保我們的 Animal 構造函數始終使用 new 關鍵字調用呢? 事實證明,它是通過使用我們之前學到的 instanceof 運算符來實現的。

如果使用 new 關鍵字調用構造函數,那麼構造函數體的內部將是構造函數本身的實例。 那是很多文字才能說清楚的。 這是一些代碼。

JavaScript 代碼:

function Animal (name, energy) {

 if (this instanceof Animal === false) {

   console.warn('Forgot to call Animal with the new keyword')

 }

 

 this.name = name

 this.energy = energy

}

現在,如果我們使用 new 關鍵字重新調用函數,而不是只向函數的使用者列印警告,會發生什麼呢?

JavaScript 代碼:

function Animal (name, energy) {

 if (this instanceof Animal === false) {

   return new Animal(name, energy)

 }

 

 this.name = name

 this.energy = energy

}

現在無論是否使用 new 關鍵字調用 Animal,它都可以正常工作。

重新創建 Object.create

在這篇文章中,我們非常依賴於 Object.create 來創建委託給構造函數原型的對象。 此時,您應該知道如何在代碼中使用 Object.create ,但您可能沒有想到的一件事是Object.create 實際上是如何工作的。 為了讓您真正了解 Object.create 的工作原理,我們將重新創建它。 首先,我們對 Object.create 的工作原理了解多少?

它接受一個對象作為參數。

它創建一個對象,該對象在查找失敗時委託給參數對象。

它返回新創建的對象。

讓我們從第1點開始吧。

JavaScript 代碼:

Object.create = function (objToDelegateTo) {

 

}

很簡單。

現在第2點 – 我們需要創建一個對象,該對象將在查找失敗時委託給參數對象。 這個有點棘手。 為此,我們將使用我們對 new 關鍵字和原型如何在 JavaScript 中工作的知識。首先,在 Object.create 實現的主體中,我們將創建一個空函數。 然後,我們將該空函數的原型設置為參數對象。然後,為了創建一個新對象,我們將使用 new 關鍵字調用空函數。如果我們返回新創建的對象,也會完成第3點。

JavaScript 代碼:

Object.create = function (objToDelegateTo) {

 function Fn(){}

 Fn.prototype = objToDelegateTo

 return new Fn()

}

有點野蠻是吧?讓我們來看看吧。

當我們在上面的代碼中創建一個新函數 Fn 時,它帶有一個 prototype 屬性。 當我們使用 new 關鍵字調用它時,我們知道我們將得到的是一個對象,該對象將在查找失敗時委託給函數的原型。 如果我們覆蓋函數的原型,那麼我們可以決定在查找失敗時委託給哪個對象。 所以在我們上面的例子中,我們用調用 Object.create 時傳入的對象覆蓋 Fn的原型,我們稱之為 objToDelegateTo。

請注意,我們只支持 Object.create 的單個參數。官方實現還支持第二個可選參數,該參數允許您向創建的對象添加更多屬性。

箭頭函數

箭頭函數沒有自己的 this 關鍵字。因此,箭頭函數不能用於構造函數,如果您嘗試使用 new 關鍵字調用箭頭函數,它將拋出錯誤。

JavaScript 代碼:

const Animal = () => {}

 

const leo = new Animal() // Error: Animal is not a constructor

另外,因為我們在上面證明了 pseudoclassical pattern(經典偽類模式) 不能與箭頭函數一起使用,所以箭頭函數也沒有 prototype(原型) 屬性。

JavaScript 代碼:

const Animal = () => {}

console.log(Animal.prototype) // undefined

相關焦點

  • JavaScript 原型對象(ProtoType)介紹 | JavaScript 教程
    所有的 JavaScript 對象都會從一個 prototype(原型對象)中繼承屬性和方法。
  • 深入總結Javascript原型及原型鏈
    本篇文章給大家詳細分析了javascript原型及原型鏈的相關知識點以及用法分享,具有一定的參考價值,對此有需要的朋友可以參考學習下。如有不足之處,歡迎批評指正。我們創建的每個函數都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個原型對象,而這個原型對象中擁有的屬性和方法可以被所以實例共享一、理解原型對象無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。
  • JavaScript——繼承(prototype)
    所有的 JavaScript 對象都會從一個 prototype(原型對象)中繼承屬性和方法。
  • 一句有趣的JS代碼-prototype
    (Object.prototype.toString);這到底是想幹嘛?的定義javascript中的每個對象都有prototype屬性,Javascript中對象的prototype屬性的解釋是:返回對象類型原型的引用。
  • JavaScript Prototype汙染攻擊
    原型每個函數對象都會有個prototype屬性,它指向了該構建函數實例化的原型。使用該構建函數實例化對象時,會繼承該原型中的屬性及方法。所有的對象都有__proto__屬性,它指向了創建它的構建函數的原型。
  • javascript 對象詳解:__proto__ 和 prototype 的區別和聯繫
    我們知道,每個JS對象一定對應一個原型對象,並從原型對象繼承屬性和方法。那麼對象是怎麼和這個原型對象對應的呢?沒關係,我貼出來讓大家溫故而知新,哈哈~js在創建對象的時候,都有一個叫做 __proto__的內置屬性,用於指向創建它的函數對象的原型對象 prototype而原型鏈的基本思想就是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
  • Javascript 原型中的哲學思想
    __proto__ === Array.prototype // true上面代碼中,創建了一個Array的實例a,該實例的原型指向了Array.prototype。Array.prototype本身也是一個對象,也有繼承的原型:a.__proto__.
  • JavaScript為什麼這麼難?
    只有真正的javascript程式設計師才知道javascript太難了。其他程式設計師都覺得javascript是門玩具語言。javascript太難了。javascript使用一種非主流的對象機制,基於原型鏈的對象繼承機制。這需要我們拋棄很多語言的Class的思想。認真研究下這個原型鏈。
  • javascript自學記錄:原型與in操作符
    2、原型與in操作符// 通過 實例化對象.屬性 能訪問到對話的屬性// 訪問的順序是先實例化屬性,後原型屬性prototype.姓名 = "伍德春";Person.prototype.年齡 = 38;Person.
  • 淺析JavaScript原型設計模式
    在JavaScript中創建對象,對於JavaScript開發者來說,會有很多種方法去創建,本文將剖析JavaScript原型設計模式,在大多數地方,可以使用這種原型屬性來分享對象中的實例方法。
  • 【JavaScript】箭頭函數
    你可以通過命名參數或者 rest 參數的形式訪問參數:let res = (...rest) => rest;無原型 & 不能使用 new & 沒有super不能使用new操作符var Foo = () => {};var foo = new Foo(); // TypeError: Foo is
  • 12 個 GitHub 上超火的 JavaScript 奇技淫巧項目,找到寫 JavaScript 的靈感!
    它不是必備,但在未來學習(JavaScript)中,可以作為一篇指南。Promise立即執行函數, 模塊化, 命名空間遞歸算法數據結構消息隊列和事件循環setTimeout, setInterval 和 requestAnimationFrame繼承, 多態和代碼復用按位操作符, 類數組對象和類型化數組DOM 樹和渲染過程new 與構造函數, instanceof 與實例原型繼承與原型鏈
  • 一篇JavaScript技術棧帶你了解繼承和原型鏈
    原型鏈:prototype?類的prototype是什麼?對象的proto是什麼?類中的prototype被稱作原型:在JavaScript中,每當我們定義一個構造函數時,JavaScript引擎中就會自動為這個類添加一個prototype。
  • Javascript面向對象新手入門教程
    3、原型模式function Person(name) {this.name = name;}Person.prototype.age = 23;Person.prototype.getAge = function () {return this.age;};var person
  • JavaScript中的「黑話」
    簡單而言, ==用於判斷值是否相等, ===判斷值與類型是否都相等,因此使用全等運算符判斷操作數會更準確,新手也在學習JavaScript接收到的前幾條Tips就是避免使用相等運算符,真的是這樣嗎?沒錯,這樣能確保在你不徹底熟悉語言的情況下,儘可能的去避免犯錯,但是我們也應該清楚在哪些情況下應該使用相等運算符,規則往往只針對於新手,而對聰明的你來說,最重要的是要清楚自己在做什麼。
  • javascript 面試的完美指南(開發者視角)
    Object.prototype(object)Object.freeze(function)Object.seal(function)Object.prototype 上提供了許多應用上相關的函數,如下:Object.prototype.hasOwnProperty 用於檢查給定的屬性/鍵是否存在於對象中。
  • javascript常用函數推薦
    繼續上一篇的內容,本文繼續javascript數組相關的常用函數推薦,基於ES6+規範,上一篇請查看這裡countOccurrences計算數組中值的出現次數。每次遇到數組內的特定值時,使用Array.prototype.reduce()遞增計數器。
  • 【專業技術】關於JS的prototype
    相信大家也看出來了,直接聲明的函數 擁有prototype這個屬性,而new 構造出來的函數不存在prototype這個屬性象。什麼是prototype:function定義的對象有一個prototype屬性,prototype屬性又指向了一個prototype對象,注意prototype屬性與prototype對象是兩個不同的東西,要注意區別。
  • JavaScript:對象都是這樣生成的!
    頭圖 | CSDN下載自視覺中國作者 | flydean 責編 | 張文來源 | 程序那些事(ID:flydean-tech)本文將會深入講解面向對象在 javascript 中的應用,並詳細介紹三種對象的生成方式:構造函數、原型鏈、類。
  • 44 個 Javascript 變態題解析 (下)
    數組在比較大小的時候按照字典序比較答案 false, false, false, true第29題var a = {}, b = Object.prototype;[a.prototype === b, Object.getPrototypeOf(a) === b]知識點:只有 Function 擁有一個 prototype