javascript原型_JavaScript原型初學者指南

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函數,并為其傳遞動物的nameenergy水平。 這很好用,而且非常簡單。 但是,您可以發現這種模式有什么缺點嗎? 我們嘗試解決的最大的問題與三種方法有關- eatsleepplay 。 這些方法中的每一個不僅是動態的,而且是完全通用的。 這意味著沒有必要重新創建那些方法,就像我們在創建新動物時正在做的那樣。 我們只是在浪費記憶,使每個動物對象都超出其需要。 您能想到解決方案嗎? 如果不是每次創建新動物時都重新創建這些方法,而是將它們移到它們自己的對象上,然后讓每個動物都引用該對象怎么辦? 我們可以將這種模式稱為“ 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時, parentchild.heritage您將獲得parentIrish遺產。

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:

至此,我們知道三件事:

  1. How to create a constructor function.

    如何創建構造函數。
  2. How to add methods to the constructor function's prototype.

    如何向構造函數的原型添加方法。
  3. 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)?

您可能從未想過的一件事是,數組的每個實例如何具有所有內置方法( spliceslicepop等)?

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 ,這就是為什么所有對象都具有諸如toStringhasOwnProperty類的方法的原因。

靜態方法 (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種方法的對象,這些方法分別是constructoreatsleepplay 。 這就說得通了。 我們在實例中使用了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.prototypeObject.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.constructorleo沒有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循環將遍歷對象本身及其委托給的原型上的所有可枚舉屬性 。 因為默認情況下,添加到函數原型的任何屬性都是可枚舉的,所以我們不僅看到nameenergy ,而且還看到原型上的所有方法eatsleepplay 。 要解決此問題,我們要么需要指定所有原型方法都是不可枚舉的, 要么需要一種方法來僅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.

如果objectClass的實例,則上面的語句將返回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 Animaltrue因為Object.getPrototypeOf(leo) === Animal.prototype 。 另外, leo instanceof Userfalse因為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工作方式了解多少?

  1. It takes in an argument that is an object.

    它接受一個作為對象的參數。
  2. It creates an object that delegates to the argument object on failed lookups.

    它創建一個對象,該對象在失敗的查找時委派給參數對象。
  3. 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原型

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/391576.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/391576.shtml
英文地址,請注明出處:http://en.pswp.cn/news/391576.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

leetcode 73. 矩陣置零

給定一個 m x n 的矩陣,如果一個元素為 0 ,則將其所在行和列的所有元素都設為 0 。請使用 原地 算法。 進階: 一個直觀的解決方案是使用 O(mn) 的額外空間,但這并不是一個好的解決方案。 一個簡單的改進方案是使用 O(m n) 的額…

elasticsearch,elasticsearch-service安裝

在Windows上安裝Elasticsearch.zip 1 安裝條件 安裝需具備java 8或更高版本;官方的Oracle發行版,只需安裝JDKElasticsearch的ZIP安裝包——安裝包地址 2 如何安裝 Elasticsearch 傻瓜式的點下一步即可, java 注意環境變量配置 3 如何判斷安裝…

圖表可視化seaborn風格和調色盤

seaborn是基于matplotlib的python數據可視化庫,提供更高層次的API封裝,包括一些高級圖表可視化等工具。 使用seaborn需要先安裝改模塊pip3 install seaborn 。 一、風格style 包括set() / set_style() / axes_style() / despine() / set_context() 創建正…

面向Tableau開發人員的Python簡要介紹(第3部分)

用PYTHON探索數據 (EXPLORING DATA WITH PYTHON) One of Tableau’s biggest advantages is how it lets you swim around in your data. You don’t always need a fine-tuned dashboard to find meaningful insights, so even someone with quite a basic understanding of T…

leetcode 191. 位1的個數(位運算)

編寫一個函數,輸入是一個無符號整數(以二進制串的形式),返回其二進制表達式中數字位數為 ‘1’ 的個數(也被稱為漢明重量)。 提示: 請注意,在某些語言(如 Java&#xf…

7、芯片發展

第一臺繼電器式計算機由康德拉.楚澤制造(1910-1995),這臺機器使用了二進制數,但早期版本中使用的是機械存儲器而非繼電器,使用老式35毫米電影膠片進行穿孔編程。 同一時期,哈佛大學研究生霍華德.艾肯 要尋找…

seaborn分布數據可視化:直方圖|密度圖|散點圖

系統自帶的數據表格(存放在github上https://github.com/mwaskom/seaborn-data),使用時通過sns.load_dataset(表名稱)即可,結果為一個DataFrame。 print(sns.get_dataset_names()) #獲取所有數據表名稱 # [anscombe, attention, …

如何成為一個優秀的程序員_如何成為一名優秀的程序員

如何成為一個優秀的程序員by Amy M Haddad通過艾米M哈達德(Amy M Haddad) 如何成為一名優秀的程序員 (How to be a great programmer) What sets apart the really great programmers?是什么使真正出色的程序員與眾不同? As we all know, great programmers buil…

pymc3使用_使用PyMC3了解飛機事故趨勢

pymc3使用Visually exploring historic airline accidents, applying frequentist interpretations and validating changing trends with PyMC3.使用PyMC3直觀地瀏覽歷史性航空事故,應用常識性解釋并驗證變化趨勢。 前言 (Preface) On the 7th of August this yea…

視頻監控業務上云方案解析

摘要:阿里云針對安防監控服務在傳統IT架構下面臨的上述問題,基于阿里云存儲服務,提供視頻監控解決方案。從2015年推出視頻監控存儲與播放解決方案以來,幫助大量的視頻監控企業解決了上云的過程中遇到的問題,針對不同的…

leetcode 341. 扁平化嵌套列表迭代器(dfs)

給你一個嵌套的整型列表。請你設計一個迭代器,使其能夠遍歷這個整型列表中的所有整數。 列表中的每一項或者為一個整數,或者是另一個列表。其中列表的元素也可能是整數或是其他列表。 示例 1: 輸入: [[1,1],2,[1,1]] 輸出: [1,1,2,1,1] 解釋: 通過重復…

爬蟲結果數據完整性校驗

數據完整性分為三個方面: 1、域完整性(列) 限制輸入數據的類型,及范圍,或者格式,如性別字段必須是“男”或者“女”,不允許其他數據插入,成績字段只能是0-100的整型數據,…

go map數據結構

map數據結構 key-value的數據結構,又叫字典或關聯數組 聲明:var map1 map[keytype]valuetype var a map[string]string var a map[string]int var a map[int]string var a map[string]map[string]string備注:聲明是不會分配內存的&#xff0c…

吳恩達神經網絡1-2-2_圖神經網絡進行藥物發現-第2部分

吳恩達神經網絡1-2-2預測毒性 (Predicting Toxicity) 相關資料 (Related Material) Jupyter Notebook for the article Jupyter Notebook的文章 Drug Discovery with Graph Neural Networks — part 1 圖神經網絡進行藥物發現-第1部分 Introduction to Cheminformatics 化學信息…

android初學者_適用于初學者的Android廣播接收器

android初學者Let’s say you have an application that depends on a steady internet connection. You want your application to get notified when the internet connection changes. How do you do that?假設您有一個依賴穩定互聯網連接的應用程序。 您希望您的應用程序在…

Android熱修復之 - 阿里開源的熱補丁

1.1 基本介紹     我們先去github上面了解它https://github.com/alibaba/AndFix 這里就有一個概念那就AndFix.apatch補丁用來修復方法,接下來我們看看到底是怎么實現的。1.2 生成apatch包      假如我們收到了用戶上傳的崩潰信息,我們改完需要修復…

leetcode 456. 132 模式(單調棧)

給你一個整數數組 nums &#xff0c;數組中共有 n 個整數。132 模式的子序列 由三個整數 nums[i]、nums[j] 和 nums[k] 組成&#xff0c;并同時滿足&#xff1a;i < j < k 和 nums[i] < nums[k] < nums[j] 。 如果 nums 中存在 132 模式的子序列 &#xff0c;返回…

seaborn分類數據可視:散點圖|箱型圖|小提琴圖|lv圖|柱狀圖|折線圖

一、散點圖stripplot( ) 與swarmplot&#xff08;&#xff09; 1.分類散點圖stripplot( ) 用法stripplot(xNone, yNone, hueNone, dataNone, orderNone, hue_orderNone,jitterTrue, dodgeFalse, orientNone, colorNone, paletteNone,size5, edgecolor"gray", linewi…

數據圖表可視化_數據可視化十大最有用的圖表

數據圖表可視化分析師每天使用的最佳數據可視化圖表列表。 (List of best data visualization charts that Analysts use on a daily basis.) Presenting information or data in a visual format is one of the most effective ways. Researchers have proved that the human …

javascript實現自動添加文本框功能

轉自&#xff1a;http://www.cnblogs.com/damonlan/archive/2011/08/03/2126046.html 昨天&#xff0c;我們公司的網絡小組決定為公司做一個內部的網站&#xff0c;主要是為員工比如發布公告啊、填寫相應信息、投訴、問題等等需求。我那同事給了我以下需求&#xff1a; 1.點擊一…