1. 程式人生 > >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,我們將函式本身稱為“建構函式”,因為它負責“構造”一個​​新物件。

功能例項化

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)

想要學習Web前端的小夥伴,小編在這推薦下自己的Web學習群:957389100,群內不定期分享乾貨,進群就可領取基礎教學視訊,歡迎各位加入

“我認為這是高階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允許建立一個物件,只要該物件上的屬性查詢失敗,它就可以查詢另一個物件以檢視該另一個物件是否具有該屬性。我們來看一些程式碼。

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)建立的,所以每當在子級上查詢失敗的屬性時,JavaScript都會將該查詢委託給父物件。這意味著即使孩子沒有遺產,父母也會在你記錄孩子時這樣做。這樣你就會得到父母的遺產(屬性值的傳遞)。

現在在我們的工具棚中使用Object.create,我們如何使用它來簡化之前的Animal程式碼?好吧,我們可以使用Object.create委託給animalMethods物件,而不是像我們現在一樣逐個將所有共享方法新增到動物中。

聽起來很聰明,讓我們將這個稱為功能例項化與共享方法用Object.create實現吧。

使用共享方法和Object.create進行功能例項化

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物件。

到現在為止還挺好。儘管如此,我們仍然可以做出一些改進。為了跨例項共享方法,必須管理一個單獨的物件(animalMethods)似乎有點“hacky”。這似乎是您希望在語言本身中實現的常見功能。這就是你在這裡的全部原因 - prototype。

那麼究竟什麼是JavaScript的原型?好吧,簡單地說,JavaScript中的每個函式都有一個引用物件的prototype屬性。

對嗎?親自測試一下。

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

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

原型例項化

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。

在這一點上,我們知道三件事:

1.如何建立建構函式。
2.如何將方法新增到建構函式的原型中。
3.如何使用Object.create將失敗的查詢委託給函式的原型。
這三個任務似乎是任何程式語言的基礎。JavaScript是否真的那麼糟糕,沒有更簡單“內建”的方式來完成同樣的事情?然而並不是的,它是通過使用new關鍵字來完成的。

我們採取的緩慢,有條理的方法有什麼好處,現在可以深入瞭解JavaScript中新關鍵字的內容。

回顧一下我們的Animal建構函式,最重要的兩個部分是建立物件並返回它。如果不使用Object.create建立物件,我們將無法在失敗的查詢上委託函式的原型。如果沒有return語句,我們將永遠不會返回建立的物件。

function Animal (name, energy) {
 let animal = Object.create(Animal.prototype)
 animal.name = name
 animal.energy = energy
 return animal
}

這是關於new的一個很酷的事情 - 當你使用new關鍵字呼叫一個函式時,這兩行是隱式完成的(JavaScript引擎),並且建立的物件稱為this。

使用註釋來顯示在幕後發生的事情並假設使用new關鍵字呼叫Animal建構函式,為此可以將其重寫。

想要學習Web前端的小夥伴,小編在這推薦下自己的Web學習群:957389100,群內不定期分享乾貨,進群就可領取基礎教學視訊,歡迎各位加入

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)

這個工作的原因以及為我們建立這個物件的原因是因為我們使用new關鍵字呼叫了建構函式。如果在呼叫函式時不使用new,則此物件永遠不會被建立,也不會被隱式返回。我們可以在下面的示例中看到這個問題。

function Animal (name, energy) {
 this.name = name
 this.energy = energy
}
const leo = Animal('Leo', 7)
console.log(leo) // undefined

此模式的名稱是Pseudoclassical Instantiation(原型例項化)。

對於那些不熟悉的人,Class允許您為物件建立藍圖。然後,無論何時建立該類的例項,都會獲得一個具有藍圖中定義的屬性和方法的物件。

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

JavaScript不是一種死語言。它正在不斷得到改進

看看上面的Animal建構函式如何使用新的類語法。

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類的便捷語法,首先必須理解偽古典模式。

陣列方法

我們在上面深入討論瞭如果要在類的例項之間共享方法,您應該將這些方法放在類(或函式)原型上。如果我們檢視Array類,我們可以看到相同的模式。從歷史上看,你可能已經建立了這樣的陣列

const friends = []

事實證明,建立一個新的Array類其實也是一個語法糖。

const friendsWithSugar = []
const friendsWithoutSugar = new Array()

你可能從未想過的一件事是陣列的每個例項中的內建方法是從何而來的(splice, slice, pop, etc)?

正如您現在所知,這是因為這些方法存在於Array.prototype上,當你建立新的Array例項時,使用new關鍵字將該委託設定為Array.prototype。

我們可以通過簡單地記錄Array.prototype來檢視所有陣列的方法。

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

物件也存在完全相同的邏輯。所有物件將在失敗的查詢中委託給Object.prototype,這就是所有物件都有toString和hasOwnProperty等方法的原因。

靜態方法

到目前為止,已經介紹了為什麼以及如何在類的例項之間共享方法。但是,如果我們有一個對Class很重要但不需要跨例項共享的方法呢?例如,如果我們有一個函式接受一個Animal例項陣列並確定下一個需要接收哪一個呢?我們將其稱為nextToEat。

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類相同的範圍內,然後像我們通常那樣在需要時引用它。

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
現在這可行,但有更好的方法。

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

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進行訪問。

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
console.log(Animal.nextToEat([leo, snoop])) // Leo

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

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方法完成獲取該物件的原型。

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種方法,建構函式,吃飯,睡眠和遊戲的物件。那講得通。我們在例項中使用了getPrototypeOf傳遞,leo獲取了例項的原型,這是我們所有方法都存在的地方。這告訴我們關於原型的另外一件事我們還沒有談過。預設情況下,原型物件將具有建構函式屬性,該屬性指向原始函式或建立例項的類。這也意味著因為JavaScript預設在原型上放置建構函式屬性,所以任何例項都可以通過instance.constructor訪問它們的建構函式。

上面的第二個重要內容是Object.getPrototypeOf(leo)=== Animal.prototype。這也是有道理的。Animal建構函式有一個prototype屬性,我們可以在所有例項之間共享方法,getPrototypeOf允許我們檢視例項本身的原型。

function Animal (name, energy) {
 this.name = name
 this.energy = energy
}
const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function

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

在某些情況下,需要知道屬性是否存在於例項本身上,還是存在於物件委託的原型上。我們可以通過迴圈我們建立的leo物件來看到這一點。讓我們說目標是迴圈leo並記錄它的所有鍵和值。使用for迴圈,可能看起來像這樣。

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

最有可能的是,它是這樣的

Key: name. Value: Leo
Key: energy. Value: 7

但是,如果你執行程式碼,你看到的是這個

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迴圈將迴圈遍歷物件本身以及它所委託的原型的所有可列舉屬性。因為預設情況下,你新增到函式原型的任何屬性都是可列舉的,我們不僅會看到名稱和能量,還會看到原型上的所有方法 - 吃,睡,玩。要解決這個問題,我們需要指定所有原型方法都是不可列舉的或者我們需要一種類似console.log的方法,如果屬性是leo物件本身而不是leo委託給的原型在失敗的查詢。這是hasOwnProperty可以幫助我們的地方。

這是為什麼?for迴圈將迴圈遍歷物件本身以及它所委託的原型的所有可列舉屬性。因為預設情況下,您新增到函式原型的任何屬性都是可列舉的,我們不僅會看到名稱和能量,還會看到原型上的所有方法 - 吃,睡,玩。要解決這個問題,我們需要指定所有原型方法都是不可列舉的或者我們需要一種類似console.log的方法,如果屬性是leo物件本身而不是leo委託給的原型在失敗的查詢。這是hasOwnProperty可以幫助我們的地方。

const leo = new Animal('Leo', 7)
for(let key in leo) {
 if (leo.hasOwnProperty(key)) {
 console.log(Key: ${key}. Value: ${leo[key]})
 }
}

而現在我們看到的只是leo物件本身的屬性,而不是leo委託的原型。

Key: name. Value: Leo
Key: energy. Value: 7

如果你仍然對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

檢查物件是否是類的例項

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

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

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。

建立新的不可知建構函式

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

function Animal (name, energy) {
 this.name = name
 this.energy = energy
}
const leo = Animal('Leo', 7)

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

作為複習,以下程式碼中,註釋中的部分是在函式上使用new關鍵字時會發生的事情。

function Animal (name, energy) {
 // const this = Object.create(Animal.prototype)
 this.name = name
 this.energy = energy
 // return this
}

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

如果使用new關鍵字呼叫建構函式,那麼建構函式體的內部將是建構函式本身的例項。這是一些程式碼。

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關鍵字怎麼辦?

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開始吧。

Object.create = function (objToDelegateTo) {
}

很簡單。

現在#2 - 我們需要建立一個物件,該物件將在失敗的查詢中委託給引數物件。這個有點棘手。為此,我們將使用我們對新關鍵字和原型如何在JavaScript中工作的知識。首先,在Object.create實現的主體內部,我們將建立一個空函式。然後,我們將該空函式的原型設定為等於引數物件。然後,為了建立一個新物件,我們將使用new關鍵字呼叫我們的空函式。如果我們返回新建立的物件,那麼它也將完成#3。

Object.create = function (objToDelegateTo) {
 function Fn(){}
 Fn.prototype = objToDelegateTo
 return new Fn()
}

讓我們來看看吧。

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

箭頭函式

箭頭函式沒有自己的this關鍵字。因此,箭頭函式不能是建構函式,如果您嘗試使用new關鍵字呼叫箭頭函式,它將丟擲錯誤。

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

另外,為了證明箭頭函式不能是建構函式,如下,我們看到箭頭函式也沒有原型屬性。

const Animal = () => {}
console.log(Animal.prototype) // undefined

想要學習Web前端的小夥伴,小編在這推薦下自己的Web學習群:957389100,群內不定期分享乾貨,進群就可領取基礎教學視訊,歡迎各位加入

原文