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 = 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
}

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

"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 = 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)

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 = 7

console.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 = name
  animal.energy = energy

  return 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 = 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)

??? 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 = name
  animal.energy = energy

  return 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 = name
  this.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 = 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)

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 = name
  this.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 = 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)

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

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 = 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

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 = 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
  }
}

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 = 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

获取对象原型 (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 = 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

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 = name
  this.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 = 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]}`)
}

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 = 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

检查对象是否是类的实例 (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 = name
  this.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 = name
  this.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 = name
  this.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 = name
  this.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 = name
  this.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 = objToDelegateTo
  return 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原型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值