javascript原型
You can't get very far in JavaScript without dealing with objects. They're foundational to almost every aspect of the JavaScript programming language. In fact, learning how to create objects is probably one of the first things you studied when you were starting out. With that said, in order to most effectively learn about prototypes in JavaScript, we're going to channel our inner Jr. developer and go back to the basics.
如果不處理對象,您將無法在JavaScript中走得很遠。 它們是JavaScript編程語言幾乎所有方面的基礎。 實際上,學習如何創建對象可能是您剛開始學習時首先學習的內容之一。 話雖如此,為了最有效地學習JavaScript原型,我們將引導內部的Jr.開發人員回到基礎知識。
Objects are key/value pairs. The most common way to create an object is with curly braces {}
and you add properties and methods to an object using dot notation.
對象是鍵/值對。 創建對象的最常見方法是使用大括號{}
,然后使用點表示法將屬性和方法添加到對象。
let animal = {}
animal.name = 'Leo'
animal.energy = 10animal.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
}
Simple. Now odds are in our application we'll need to create more than one animal. Naturally the next step for this would be to encapsulate that logic inside of a function that we can invoke whenever we needed to create a new animal. We'll call this pattern Functional Instantiation
and we'll call the function itself a "constructor function" since it's responsible for "constructing" a new object.
簡單。 現在,在我們的應用程序中,我們將需要創造不止一種動物。 當然,下一步是將邏輯封裝在函數中,以便在需要創建新動物時可以調用該邏輯。 我們將這種模式稱為“ Functional Instantiation
,并將函數本身稱為“構造函數”,因為它負責“構造”新對象。
功能實例化 (Functional Instantiation)
function Animal (name, energy) {let animal = {}animal.name = nameanimal.energy = energyanimal.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)
"I thought this was an Advanced JavaScript course...?" - Your brain
"I thought this was an Advanced JavaScript course...?" - Your brain
It is. We'll get there.
它是。 我們到達那里。
Now whenever we want to create a new animal (or more broadly speaking a new "instance"), all we have to do is invoke our Animal
function, passing it the animal's name
and energy
level. This works great and it's incredibly simple. However, can you spot any weaknesses with this pattern? The biggest and the one we'll attempt to solve has to do with the three methods - eat
, sleep
, and play
. Each of those methods are not only dynamic, but they're also completely generic. What that means is that there's no reason to re-create those methods as we're currently doing whenever we create a new animal. We're just wasting memory and making each animal object bigger than it needs to be. Can you think of a solution? What if instead of re-creating those methods every time we create a new animal, we move them to their own object then we can have each animal reference that object? We can call this pattern Functional Instantiation with Shared Methods
, wordy but descriptive ??♂?.
現在,每當我們要創建新的動物(或更籠統地說是新的“實例”)時,我們要做的就是調用我們的Animal
函數,并為其傳遞動物的name
和energy
水平。 這很好用,而且非常簡單。 但是,您可以發現這種模式有什么缺點嗎? 我們嘗試解決的最大的問題與三種方法有關- eat
, sleep
和play
。 這些方法中的每一個不僅是動態的,而且是完全通用的。 這意味著沒有必要重新創建那些方法,就像我們在創建新動物時正在做的那樣。 我們只是在浪費記憶,使每個動物對象都超出其需要。 您能想到解決方案嗎? 如果不是每次創建新動物時都重新創建這些方法,而是將它們移到它們自己的對象上,然后讓每個動物都引用該對象怎么辦? 我們可以將這種模式稱為“ Functional Instantiation with Shared Methods
,冗長但描述性強?
共享方法的功能實例化 (Functional Instantiation with Shared Methods)
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 = nameanimal.energy = energyanimal.eat = animalMethods.eatanimal.sleep = animalMethods.sleepanimal.play = animalMethods.playreturn animal
}const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
By moving the shared methods to their own object and referencing that object inside of our Animal
function, we've now solved the problem of memory waste and overly large animal objects.
通過將共享方法移動到它們自己的對象并在我們的Animal
函數中引用該對象,我們現在解決了內存浪費和過大的動物對象的問題。
對象創建 (Object.create)
Let's improve our example once again by using Object.create
. Simply put, Object.create allows you to create an object which will delegate to another object on failed lookups. Put differently, Object.create allows you to create an object and whenever there's a failed property lookup on that object, it can consult another object to see if that other object has the property. That was a lot of words. Let's see some code.
讓我們通過使用Object.create
再次改進我們的示例。 簡而言之, Object.create允許您創建一個對象,該對象將在失敗的查找時委派給另一個對象 。 換句話說,Object.create允許您創建一個對象,只要對該對象進行的屬性查找失敗,它就可以查詢另一個對象以查看該另一個對象是否具有該屬性。 那是很多話。 讓我們看一些代碼。
const parent = {name: 'Stacey',age: 35,heritage: 'Irish'
}const child = Object.create(parent)
child.name = 'Ryan'
child.age = 7console.log(child.name) // Ryan
console.log(child.age) // 7
console.log(child.heritage) // Irish
So in the example above, because child
was created with Object.create(parent)
, whenever there's a failed property lookup on child
, JavaScript will delegate that lookup to the parent
object. What that means is that even though child
doesn't have a heritage
property, parent
does so when you log child.heritage
you'll get the parent
's heritage which was Irish
.
因此,在上面的示例中,由于child
是使用Object.create(parent)
創建的,因此每當child
上的屬性查找失敗時,JavaScript都會將該查找委托給parent
對象。 這意味著即使child
沒有heritage
,但當您登錄child.heritage
時, parent
也child.heritage
您將獲得parent
的Irish
遺產。
Now with Object.create
in our tool shed, how can we use it in order to simplify our Animal
code from earlier? Well, instead of adding all the shared methods to the animal one by one like we're doing now, we can use Object.create to delegate to the animalMethods
object instead. To sound really smart, let's call this one Functional Instantiation with Shared Methods and Object.create
?
現在,在工具棚中有了Object.create
,我們如何使用它來簡化我們的Animal
代碼? 好了, animalMethods
像現在那樣將所有共享方法一一添加到動物, animalMethods
使用Object.create委托給animalMethods
對象。 聽起來真的很聰明,讓我們將此Functional Instantiation with Shared Methods and Object.create
稱為“ Functional Instantiation with Shared Methods and Object.create
?
共享方法和Object.create的功能實例化 (Functional Instantiation with Shared Methods and 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 = nameanimal.energy = energyreturn animal
}const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)leo.eat(10)
snoop.play(5)
? So now when we call leo.eat
, JavaScript will look for the eat
method on the leo
object. That lookup will fail, then, because of Object.create, it'll delegate to the animalMethods
object which is where it'll find eat
.
? 因此,現在當我們調用leo.eat
,JavaScript將在leo
對象上尋找eat
方法。 該查找將失敗,然后由于Object.create的原因,它將委托給animalMethods
對象,該對象將在此處找到eat
。
So far, so good. There are still some improvements we can make though. It seems just a tad "hacky" to have to manage a separate object (animalMethods
) in order to share methods across instances. That seems like a common feature that you'd want to be implemented into the language itself. Turns out it is and it's the whole reason you're here - prototype
.
到目前為止,一切都很好。 我們仍然可以做一些改進。 似乎必須要管理一個單獨的對象( animalMethods
)才能在實例之間共享方法,這只是一點點“ hacky”。 這似乎是您希望在語言本身中實現的一項常用功能。 事實證明,這就是您來到這里的全部原因- prototype
。
So what exactly is prototype
in JavaScript? Well, simply put, every function in JavaScript has a prototype
property that references an object. Anticlimactic, right? Test it out for yourself.
那么JavaScript中的prototype
到底是什么? 簡而言之,JavaScript中的每個函數都有一個引用對象的prototype
屬性。 防腳傷,對嗎? 自己測試一下。
function doThing () {}
console.log(doThing.prototype) // {}
What if instead of creating a separate object to manage our methods (like we're doing with animalMethods
), we just put each of those methods on the Animal
function's prototype? Then all we would have to do is instead of using Object.create to delegate to animalMethods
, we could use it to delegate to Animal.prototype
. We'll call this pattern Prototypal Instantiation
.
如果不是將單獨的每個方法放在Animal
函數的原型上,而不是創建一個單獨的對象來管理方法(就像我們對animalMethods
所做的animalMethods
)怎么辦? 然后,我們要做的就是代替使用Object.create委托給animalMethods
,而可以使用它委托給Animal.prototype
。 我們將這種模式稱為“ Prototypal Instantiation
。
原型實例化 (Prototypal Instantiation)
function Animal (name, energy) {let animal = Object.create(Animal.prototype)animal.name = nameanimal.energy = energyreturn 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)
??? Hopefully you just had a big "aha" moment. Again, prototype
is just a property that every function in JavaScript has and, as we saw above, it allows us to share methods across all instances of a function. All our functionality is still the same but now instead of having to manage a separate object for all the methods, we can just use another object that comes built into the Animal
function itself, Animal.prototype
.
??? 希望您有一個重要的“啊哈”時刻。 同樣, prototype
只是JavaScript中每個函數都具有的屬性,并且如我們上面所見,它允許我們在函數的所有實例之間共享方法。 我們所有的功能仍然相同,但是現在不必為所有方法管理一個單獨的對象,我們只需使用Animal
函數本身內置的另一個對象Animal.prototype
。
讓我們。 走。 更深的。 (Let's. Go. Deeper.)
At this point we know three things:
至此,我們知道三件事:
- How to create a constructor function. 如何創建構造函數。
- How to add methods to the constructor function's prototype. 如何向構造函數的原型添加方法。
- How to use Object.create to delegate failed lookups to the function's prototype. 如何使用Object.create將失敗的查詢委派給函數的原型。
Those three tasks seem pretty foundational to any programming language. Is JavaScript really that bad that there's no easier, "built in" way to accomplish the same thing? As you can probably guess at this point there is, and it's by using the new
keyword.
對于任何編程語言,這三個任務似乎都是非常基礎的。 JavaScript真的很糟糕嗎,沒有一種簡單的“內置”方式來完成同一件事嗎? 您可能會猜到這一點,這是通過使用new
關鍵字實現的。
What's nice about the slow, methodical approach we took to get here is you'll now have a deep understanding of exactly what the new
keyword in JavaScript is doing under the hood.
我們采用緩慢,有條理的方法來解決問題的好處是,您現在將對JavaScript中的new
關鍵字到底是做什么的有了深刻的了解。
Looking back at our Animal
constructor, the two most important parts were creating the object and returning it. Without creating the object with Object.create
, we wouldn't be able to delegate to the function's prototype on failed lookups. Without the return
statement, we wouldn't ever get back the created object.
回顧我們的Animal
構造函數,兩個最重要的部分是創建對象并返回它。 如果不使用Object.create
創建對象,我們將無法在失敗的查詢中委托給函數的原型。 沒有return
語句,我們將永遠不會取回創建的對象。
function Animal (name, energy) {let animal = Object.create(Animal.prototype)animal.name = nameanimal.energy = energyreturn animal
}
Here's the cool thing about new
- when you invoke a function using the new
keyword, those two lines are done for you implicitly ("under the hood") and the object that is created is called this
.
這是有關new
的很酷的事情-當您使用new
關鍵字調用函數時,這兩行隱式地為您完成(“幕后”),并且創建的對象稱為this
。
Using comments to show what happens under the hood and assuming the Animal
constructor is called with the new
keyword, it can be re-written as this.
使用注釋來顯示幕后情況,并假設使用new
關鍵字調用Animal
構造函數,則可以按此方式重寫它。
function Animal (name, energy) {// const this = Object.create(Animal.prototype)this.name = namethis.energy = energy// return this
}const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
and without the "under the hood" comments
并沒有“內幕”評論
function Animal (name, energy) {this.name = namethis.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)
Again the reason this works and that the this
object is created for us is because we called the constructor function with the new
keyword. If you leave off new
when you invoke the function, that this
object never gets created nor does it get implicitly returned. We can see the issue with this in the example below.
同樣,它起作用并為我們創建了this
對象的原因是因為我們使用new
關鍵字調用了構造函數。 如果在調用函數時不使用new
,則永遠不會創建this
對象,也不會隱式返回this
對象。 我們可以在下面的示例中看到此問題。
function Animal (name, energy) {this.name = namethis.energy = energy
}const leo = Animal('Leo', 7)
console.log(leo) // undefined
The name for this pattern is Pseudoclassical Instantiation
.
該模式的名稱是Pseudoclassical Instantiation
。
If JavaScript isn't your first programming language, you might be getting a little restless.
如果JavaScript不是您的第一門編程語言,那么您可能會有些不安。
"WTF this dude just re-created a crappier version of a Class" - You
“ WTF這個家伙剛剛重新創建了更糟糕的Class版本”-您
For those unfamiliar, a Class allows you to create a blueprint for an object. Then whenever you create an instance of that Class, you get an object with the properties and methods defined in the blueprint.
對于那些不熟悉的人,可以使用“類”為對象創建藍圖。 然后,無論何時創建該Class的實例,都將獲得一個具有藍圖中定義的屬性和方法的對象。
Sound familiar? That's basically what we did with our Animal
constructor function above. However, instead of using the class
keyword, we just used a regular old JavaScript function to re-create the same functionality. Granted, it took a little extra work as well as some knowledge about what happens "under the hood" of JavaScript but the results are the same.
聽起來有點熟? 這基本上就是我們上面的Animal
構造函數所做的事情。 但是,我們沒有使用class
關鍵字,而是使用了常規的舊JavaScript函數來重新創建相同的功能。 當然,這花費了一些額外的工作,并且對JavaScript的“幕后”知識有所了解,但是結果是相同的。
Here's the good news. JavaScript isn't a dead language. It's constantly being improved and added to by the TC-39 committee. What that means is that even though the initial version of JavaScript didn't support classes, there's no reason they can't be added to the official specification. In fact, that's exactly what the TC-39 committee did. In 2015, EcmaScript (the official JavaScript specification) 6 was released with support for Classes and the class
keyword. Let's see how our Animal
constructor function above would look like with the new class syntax.
這是個好消息。 JavaScript不是一門硬漢。 TC-39委員會不斷對其進行改進和添加。 這意味著即使JavaScript的初始版本不支持類,也沒有理由不能將它們添加到正式規范中。 實際上,這正是TC-39委員會所做的。 2015年,發布了EcmaScript(正式JavaScript規范)6,該版本支持Classes和class
關鍵字。 讓我們看看上面的Animal
構造函數在新類語法下的樣子。
class Animal {constructor(name, energy) {this.name = namethis.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)
Pretty clean, right?
很干凈吧?
So if this is the new way to create classes, why did we spend so much time going over the old way? The reason for that is because the new way (with the class
keyword) is primarily just "syntactical sugar" over the existing way we've called the pseudoclassical pattern. In order to fully understand the convenience syntax of ES6 classes, you first must understand the pseudoclassical pattern.
因此,如果這是創建類的新方法,那么為什么我們要花那么多時間來處理舊方法呢? 這樣做的原因是,新方法(帶有class
關鍵字)主要只是“語法糖”,而不是我們稱為偽古典模式的現有方法。 為了完全理解ES6類的便捷語法,首先必須了解偽古典模式。
At this point we've covered the fundamentals of JavaScript's prototype. The rest of this post will be dedicated to understanding other "good to know" topics related to it. In another post we'll look at how we can take these fundamentals and use them to understand how inheritance works in JavaScript.
至此,我們已經介紹了JavaScript原型的基礎知識。 這篇文章的其余部分將致力于理解與之相關的其他“好知識”主題。 在另一篇文章中,我們將研究如何掌握這些基礎知識,并使用它們來了解繼承在JavaScript中的工作方式。
數組方法 (Array Methods)
We talked in depth above about how if you want to share methods across instances of a class, you should stick those methods on the class' (or function's) prototype. We can see this same pattern demonstrated if we look at the Array
class. Historically you've probably created your arrays like this
上面我們深入討論了如何在類的實例之間共享方法,如何將這些方法粘貼在類(或函數)的原型上。 如果我們查看Array
類,我們可以看到演示了相同的模式。 從歷史上看,您可能是這樣創建數組的
const friends = []
Turns out that's just sugar over creating a new
instance of the Array
class.
事實證明,創建一個Array
類的new
實例只是糖。
const friendsWithSugar = []const friendsWithoutSugar = new Array()
One thing you might have never thought about is how does every instance of an array have all of those built in methods (splice
, slice
, pop
, etc)?
您可能從未想過的一件事是,數組的每個實例如何具有所有內置方法( splice
, slice
, pop
等)?
Well as you now know, it's because those methods live on Array.prototype
and when you create a new instance of Array
, you use the new
keyword which sets up that delegation to Array.prototype
on failed lookups.
眾所周知,這是因為這些方法存在于Array.prototype
并且當您創建Array
的新實例時,可以使用new
關鍵字來設置在失敗的查找中對Array.prototype
委派。
We can see all the array's methods by simply logging 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: 0nmap: ?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()
*/
The exact same logic exists for Objects as well. Alls object will delegate to Object.prototype
on failed lookups which is why all objects have methods like toString
and hasOwnProperty
.
對象也存在完全相同的邏輯。 Alls對象將在失敗的查找時委托給Object.prototype
,這就是為什么所有對象都具有諸如toString
和hasOwnProperty
類的方法的原因。
靜態方法 (Static Methods)
Up until this point we've covered the why and how of sharing methods between instances of a Class. However, what if we had a method that was important to the Class, but didn't need to be shared across instances? For example, what if we had a function that took in an array of Animal
instances and determined which one needed to be fed next? We'll call it nextToEat
.
到目前為止,我們已經介紹了在類實例之間共享方法的原因和方式。 但是,如果我們有一個對類很重要但又不需要在實例之間共享的方法呢? 例如,如果我們有一個函數可以接收一系列Animal
實例并確定接下來需要提供哪個實例呢? 我們將其稱為nextToEat
。
function nextToEat (animals) {const sortedByLeastEnergy = animals.sort((a,b) => {return a.energy - b.energy})return sortedByLeastEnergy[0].name
}
It doesn't make sense to have nextToEat
live on Animal.prototype
since we don't want to share it amongst all instances. Instead, we can think of it as more of a helper method. So if nextToEat
shouldn't live on Animal.prototype
, where should we put it? Well the obvious answer is we could just stick nextToEat
in the same scope as our Animal
class then reference it when we need it as we normally would.
在nextToEat
上運行Animal.prototype
沒有意義,因為我們不想在所有實例之間共享它。 相反,我們可以將其視為輔助方法。 因此,如果nextToEat
不應該生活在Animal.prototype
,我們應該放在哪里? 顯而易見,答案是我們可以將nextToEat
在與Animal
類相同的范圍內,然后在需要時像往常一樣引用它。
class Animal {constructor(name, energy) {this.name = namethis.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
Now this works, but there's a better way.
現在可以使用,但是有更好的方法。
Whenever you have a method that is specific to a class itself, but doesn't need to be shared across instances of that class, you can add it as a static
property of the class.
只要您有一個特定于類本身的方法,但不需要在該類的實例之間共享,就可以將其添加為該類的static
屬性。
class Animal {constructor(name, energy) {this.name = namethis.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}
}
Now, because we added nextToEat
as a static
property on the class, it lives on the Animal
class itself (not its prototype) and can be accessed using Animal.nextToEat
.
現在,因為我們在類上添加了nextToEat
作為static
屬性,所以它位于Animal
類本身(而不是其原型)上,可以使用Animal.nextToEat
進行訪問。
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)console.log(Animal.nextToEat([leo, snoop])) // Leo
Because we've followed a similar pattern throughout this post, let's take a look at how we would accomplish this same thing using ES5. In the example above we saw how using the static
keyword would put the method directly onto the class itself. With ES5, this same pattern is as simple as just manually adding the method to the function object.
因為我們在本文中一直遵循類似的模式,所以讓我們看一下如何使用ES5完成相同的操作。 在上面的示例中,我們看到了如何使用static
關鍵字將方法直接置于類本身上。 使用ES5,這種相同的模式非常簡單,就像將方法手動添加到功能對象中一樣。
function Animal (name, energy) {this.name = namethis.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
獲取對象原型 (Getting the prototype of an object)
Regardless of whichever pattern you used to create an object, getting that object's prototype can be accomplished using the Object.getPrototypeOf
method.
無論您使用哪種模式創建對象,都可以使用Object.getPrototypeOf
方法來獲取該對象的原型。
function Animal (name, energy) {this.name = namethis.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
There are two important takeaways from the code above.
上面的代碼有兩個重要的要點。
First, you'll notice that proto
is an object with 4 methods, constructor
, eat
, sleep
, and play
. That makes sense. We used getPrototypeOf
passing in the instance, leo
getting back that instances' prototype, which is where all of our methods are living. This tells us one more thing about prototype
as well that we haven't talked about yet. By default, the prototype
object will have a constructor
property which points to the original function or the class that the instance was created from. What this also means is that because JavaScript puts a constructor
property on the prototype by default, any instances will be able to access their constructor via instance.constructor
.
首先,您會注意到proto
是一個具有4種方法的對象,這些方法分別是constructor
, eat
, sleep
和play
。 這就說得通了。 我們在實例中使用了getPrototypeOf
,而leo
返回了該實例的原型,這就是我們所有方法所處的位置。 這也告訴我們關于prototype
另一件事,我們還沒有談論過。 默認情況下, prototype
對象將具有一個constructor
屬性,該屬性指向原始函數或創建實例的類。 這還意味著,由于JavaScript默認情況下在原型上放置了一個constructor
屬性,因此任何實例都可以通過instance.constructor
訪問它們的構造函數。
The second important takeaway from above is that Object.getPrototypeOf(leo) === Animal.prototype
. That makes sense as well. The Animal
constructor function has a prototype property where we can share methods across all instances and getPrototypeOf
allows us to see the prototype of the instance itself.
從上面獲得的第二個重要Object.getPrototypeOf(leo) === Animal.prototype
是Object.getPrototypeOf(leo) === Animal.prototype
。 那也很有意義。 Animal
構造函數具有原型屬性,我們可以在所有實例之間共享方法,而getPrototypeOf
允許我們查看實例本身的原型。
function Animal (name, energy) {this.name = namethis.energy = energy
}const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function
To tie in what we talked about earlier with Object.create
, the reason this works is because any instances of Animal
are going to delegate to Animal.prototype
on failed lookups. So when you try to access leo.constructor
, leo
doesn't have a constructor
property so it will delegate that lookup to Animal.prototype
which indeed does have a constructor
property. If this paragraph didn't make sense, go back and read about Object.create
above.
為了與我們之前使用Object.create
討論相結合,之所以Animal.prototype
,是因為在失敗的查找中,任何Animal
實例都將委托給Animal.prototype
。 因此,當您嘗試訪問leo.constructor
, leo
沒有constructor
屬性,因此它將把查找委托給Animal.prototype
,后者確實具有constructor
屬性。 如果該段沒有意義,請返回并閱讀上面的Object.create
。
You may have seen __proto__ used before to get an instances' prototype. That's a relic of the past. Instead, use Object.getPrototypeOf(instance) as we saw above.
您可能已經看過__proto__用來獲取實例的原型。 那是過去的遺物。 相反,如上所述,請使用Object.getPrototypeOf(instance) 。
確定屬性是否存在于原型中 (Determining if a property lives on the prototype)
There are certain cases where you need to know if a property lives on the instance itself or if it lives on the prototype the object delegates to. We can see this in action by looping over our leo
object we've been creating. Let's say the goal was the loop over leo
and log all of its keys and values. Using a for in
loop, that would probably look like this.
在某些情況下,您需要知道某個屬性是否存在于實例本身上,或者是否存在于對象所委托給的原型上。 通過遍歷我們一直在創建的leo
對象,我們可以看到實際的效果。 假設目標是在leo
上循環并記錄其所有鍵和值。 使用for in
循環,可能看起來像這樣。
function Animal (name, energy) {this.name = namethis.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]}`)
}
What would you expect to see? Most likely, it was something like this -
您希望看到什么? 很可能是這樣的-
Key: name. Value: Leo
Key: energy. Value: 7
However, what you saw if you ran the code was this -
但是,如果運行代碼,您會看到的是-
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
}
Why is that? Well a for in
loop is going to loop over all of the enumerable properties on both the object itself as well as the prototype it delegates to. Because by default any property you add to the function's prototype is enumerable, we see not only name
and energy
, but we also see all the methods on the prototype - eat
, sleep
, and play
. To fix this, we either need to specify that all of the prototype methods are non-enumerable or we need a way to only console.log if the property is on the leo
object itself and not the prototype that leo
delegates to on failed lookups. This is where hasOwnProperty
can help us out.
這是為什么? 那么for in
循環將遍歷對象本身及其委托給的原型上的所有可枚舉屬性 。 因為默認情況下,添加到函數原型的任何屬性都是可枚舉的,所以我們不僅看到name
和energy
,而且還看到原型上的所有方法eat
, sleep
和play
。 要解決此問題,我們要么需要指定所有原型方法都是不可枚舉的, 要么需要一種方法來僅console.log,如果該屬性位于leo
對象本身上,而不是leo
在失敗的查詢中委托給的原型。 在這里hasOwnProperty
可以幫助我們。
hasOwnProperty
is a property on every object that returns a boolean indicating whether the object has the specified property as its own property rather than on the prototype the object delegates to. That's exactly what we need. Now with this new knowledge we can modify our code to take advantage of hasOwnProperty
inside of our for in
loop.
hasOwnProperty
是每個對象的屬性,該對象返回一個布爾值,該布爾值指示對象是否具有指定的屬性作為其自己的屬性,而不是對象委托給的原型。 這正是我們所需要的。 現在,有了這些新知識,我們就可以修改代碼,以在for in
循環中利用hasOwnProperty
。
...const leo = new Animal('Leo', 7)for(let key in leo) {if (leo.hasOwnProperty(key)) {console.log(`Key: ${key}. Value: ${leo[key]}`)}
}
And now what we see are only the properties that are on the leo
object itself rather than on the prototype leo
delegates to as well.
現在,我們看到的只是leo
對象本身上的屬性,而不是原型leo
委托上的屬性。
Key: name. Value: Leo
Key: energy. Value: 7
If you're still a tad confused about hasOwnProperty
, here is some code that may clear it up.
如果您仍然對hasOwnProperty
感到困惑,這里有一些代碼可以清除它。
function Animal (name, energy) {this.name = namethis.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
檢查對象是否是類的實例 (Check if an object is an instance of a Class)
Sometimes you want to know whether an object is an instance of a specific class. To do this, you can use the ?instanceof
operator. The use case is pretty straight forward but the actual syntax is a bit weird if you've never seen it before. It works like this
有時您想知道一個對象是否是特定類的實例。 為此,您可以使用instanceof
運算符。 用例非常簡單,但是如果您以前從未看過它,那么實際的語法會有些奇怪。 它是這樣的
object instanceof Class
The statement above will return true if object
is an instance of Class
and false if it isn't. Going back to our Animal
example we'd have something like this.
如果object
是Class
的實例,則上面的語句將返回true,否則返回false。 回到我們的Animal
示例,我們會有類似的東西。
function Animal (name, energy) {this.name = namethis.energy = energy
}function User () {}const leo = new Animal('Leo', 7)leo instanceof Animal // true
leo instanceof User // false
The way that instanceof
works is it checks for the presence of constructor.prototype
in the object's prototype chain. In the example above, leo instanceof Animal
is true
because Object.getPrototypeOf(leo) === Animal.prototype
. In addition, leo instanceof User
is false
because Object.getPrototypeOf(leo) !== User.prototype
.
該方法instanceof
的工作原理是它存在檢查constructor.prototype
在對象的原型鏈。 在上面的示例中, leo instanceof Animal
是true
因為Object.getPrototypeOf(leo) === Animal.prototype
。 另外, leo instanceof User
為false
因為Object.getPrototypeOf(leo) !== User.prototype
。
創建新的不可知構造函數 (Creating new agnostic constructor functions)
Can you spot the error in the code below?
您可以在下面的代碼中發現錯誤嗎?
function Animal (name, energy) {this.name = namethis.energy = energy
}const leo = Animal('Leo', 7)
Even seasoned JavaScript developers will sometimes get tripped up on the example above. Because we're using the pseudoclassical pattern
that we learned about earlier, when the Animal
constructor function is invoked, we need to make sure we invoke it with the new
keyword. If we don't, then the this
keyword won't be created and it also won't be implicitly returned.
即使是經驗豐富JavaScript開發人員,有時也會被上面的示例絆倒。 因為我們使用的是先前了解的pseudoclassical pattern
,所以在調用Animal
構造函數時,我們需要確保使用new
關鍵字對其進行調用。 如果我們不這樣做,那么將不會創建this
關鍵字,也不會隱式返回它。
As a refresher, the commented out lines are what happens behind the scenes when you use the new
keyword on a function.
作為回顧,注釋行是在函數上使用new
關鍵字時在幕后發生的事情。
function Animal (name, energy) {// const this = Object.create(Animal.prototype)this.name = namethis.energy = energy// return this
}
This seems like too important of a detail to leave up to other developers to remember. Assuming we're working on a team with other developers, is there a way we could ensure that our Animal
constructor is always invoked with the new
keyword? ?Turns out there is and it's by using the instanceof
operator we learned about previously.
這似乎太重要了,無法讓其他開發人員記住。 假設我們正在與其他開發人員一起工作,是否有一種方法可以確保始終使用new
關鍵字調用Animal
構造函數? 事實證明,這是通過使用我們先前了解的instanceof
運算符實現的。
If the constructor was called with the new
keyword, then this
inside of the body of the constructor will be an instanceof
the constructor function itself. That was a lot of big words. Here's some code.
如果構造是用所謂的new
關鍵字,則this
構造體的內部將是一個instanceof
構造函數本身。 那是很多大話。 這是一些代碼。
function Animal (name, energy) {if (this instanceof Animal === false) {console.warn('Forgot to call Animal with the new keyword')}this.name = namethis.energy = energy
}
Now instead of just logging a warning to the consumer of the function, what if we re-invoke the function, but with the new
keyword this time?
現在,如果不重新調用該函數,而是重新調用該函數,但是這次使用new
關鍵字,該怎么辦?
function Animal (name, energy) {if (this instanceof Animal === false) {return new Animal(name, energy)}this.name = namethis.energy = energy
}
Now regardless of if Animal
is invoked with the new
keyword, it'll still work properly.
現在,無論是否使用new
關鍵字調用Animal
,它都將正常運行。
重新創建Object.create (Re-creating Object.create)
Throughout this post we've relied heavily upon Object.create
in order to create objects which delegate to the constructor function's prototype. At this point, you should know how to use Object.create
inside of your code but one thing that you might not have thought of is how Object.create
actually works under the hood. In order for you to really understand how Object.create
works, we're going to re-create it ourselves. First, what do we know about how Object.create
works?
在整個這篇文章中,我們非常依賴Object.create
來創建委托給構造函數的原型的對象。 在這一點上,您應該知道如何在代碼內部使用Object.create
,但是您可能沒有想到的一件事是Object.create
實際上是如何在Object.create
運行的。 為了讓您真正了解Object.create
工作原理,我們將自己重新創建它。 首先,我們對Object.create
工作方式了解多少?
- It takes in an argument that is an object. 它接受一個作為對象的參數。
- It creates an object that delegates to the argument object on failed lookups. 它創建一個對象,該對象在失敗的查找時委派給參數對象。
- It returns the new created object. 它返回新創建的對象。
Let's start off with #1.
讓我們從#1開始。
Object.create = function (objToDelegateTo) {}
Simple enough.
很簡單。
Now #2 - we need to create an object that will delegate to the argument object on failed lookups. This one is a little more tricky. To do this, we'll use our knowledge of how the new
keyword and prototypes work in JavaScript. First, inside the body of our Object.create
implementation, we'll create an empty function. Then, we'll set the prototype of that empty function equal to the argument object. Then, in order to create a new object, we'll invoke our empty function using the new
keyword. If we return that newly created object, that'll finish #3 as well.
現在#2-我們需要創建一個對象,該對象將在失敗的查找時委派給參數對象。 這個比較棘手。 為此,我們將使用有關new
關鍵字和原型如何在JavaScript中工作的知識。 首先,在Object.create
實現的主體內,我們將創建一個空函數。 然后,我們將該空函數的原型設置為與參數對象相等。 然后,為了創建一個新對象,我們將使用new
關鍵字調用空函數。 如果我們返回該新創建的對象,則該對象也將排在第3位。
Object.create = function (objToDelegateTo) {function Fn(){}Fn.prototype = objToDelegateToreturn new Fn()
}
Wild. Let's walk through it.
野生。 讓我們來看一看。
When we create a new function, Fn
in the code above, it comes with a prototype
property. When we invoke it with the new
keyword, we know what we'll get back is an object that will delegate to the function's prototype on failed lookups. If we override the function's prototype, then we can decide which object to delegate to on failed lookups. So in our example above, we override Fn
's prototype with the object that was passed in when Object.create
was invoked which we call objToDelegateTo
.
當我們在上面的代碼中創建一個新函數Fn
時,它帶有一個prototype
屬性。 當我們使用new
關鍵字調用它時,我們知道返回的對象將是對象,該對象將在失敗的查找時委派給函數的原型。 如果我們覆蓋了函數的原型,那么我們可以決定在失敗的查找中委派給哪個對象。 因此,在上面的示例中,我們用調用Object.create
時傳入的對象(稱為objToDelegateTo
覆蓋了Fn
的原型。
Note that we're only supporting a single argument to Object.create. The official implementation also supports a second, optional argument which allow you to add more properties to the created object.
請注意,我們僅支持Object.create的單個參數。 官方實現還支持第二個可選參數,該參數可讓您向創建的對象添加更多屬性。
箭頭功能 (Arrow Functions)
Arrow functions don't have their own this
keyword. As a result, arrow functions can't be constructor functions and if you try to invoke an arrow function with the new
keyword, it'll throw an error.
箭頭函數沒有自己的this
關鍵字。 結果,箭頭函數不能是構造函數,并且如果您嘗試使用new
關鍵字調用箭頭函數,則會引發錯誤。
const Animal = () => {}const leo = new Animal() // Error: Animal is not a constructor
Also, because we demonstrated above that the pseudoclassical pattern can't be used with arrow functions, arrow functions also don't have a prototype
property.
另外,由于我們在上面證明了偽古典模式不能與箭頭函數一起使用,因此箭頭函數也沒有prototype
屬性。
const Animal = () => {}
console.log(Animal.prototype) // undefined
這是我們高級JavaScript課程的一部分 。 如果您喜歡這篇文章,請查看。 (This is part of our Advanced JavaScript course. If you enjoyed this post, check it out.)
翻譯自: https://www.freecodecamp.org/news/a-beginners-guide-to-javascripts-prototype/
javascript原型