之前我们学习了如何在 ES5 和 ES6 中创建 Animal 类。我们还学习了如何使用 JavaScrip t的原型在这些类之间共享方法。查看我们在之前文章中看到的代码。
    ES5:
    JavaScript 代码:

    1. function Animal (name, energy) {
    2. this.name = name
    3. this.energy = energy
    4. }
    5. Animal.prototype.eat = function (amount) {
    6. console.log(${this.name} is eating.)
    7. this.energy += amount
    8. }
    9. Animal.prototype.sleep = function (length) {
    10. console.log(${this.name} is sleeping.)
    11. this.energy += length
    12. }
    13. Animal.prototype.play = function (length) {
    14. console.log(${this.name} is playing.)
    15. this.energy -= length
    16. }
    17. const leo = new Animal(‘Leo’, 7)

    ES6:
    JavaScript 代码:

    1. class Animal {
    2. constructor(name, energy) {
    3. this.name = name
    4. this.energy = energy
    5. }
    6. eat(amount) {
    7. console.log(${this.name} is eating.)
    8. this.energy += amount
    9. }
    10. sleep() {
    11. console.log(${this.name} is sleeping.)
    12. this.energy += length
    13. }
    14. play() {
    15. console.log(${this.name} is playing.)
    16. this.energy -= length
    17. }
    18. }
    19. const leo = new Animal(‘Leo’, 7)

    现在我们想为特定动物建一个别 class(类) 。 例如,如果我们想要开始制作一堆狗实例,该怎么办? 这些狗有哪些属性和方法? 嗯,类似于我们的 Animal 类,我们可以给每只狗一个 name ,一个 energy 等级,以及 eatsleepplay 的能力。 我们的 Dog 类是独一无二的,我们也可以给Dog 类一些独一无二的的属性,比如一个 breed(品种) 属性以及 bark(吠叫) 的能力。 在 ES5 中,我们的 Dog 类可能看起来像这样:
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. this.name = name
    3. this.energy = energy
    4. this.breed = breed
    5. }
    6. Dog.prototype.eat = function (amount) {
    7. console.log(${this.name} is eating.)
    8. this.energy += amount
    9. }
    10. Dog.prototype.sleep = function (length) {
    11. console.log(${this.name} is sleeping.)
    12. this.energy += length
    13. }
    14. Dog.prototype.play = function (length) {
    15. console.log(${this.name} is playing.)
    16. this.energy -= length
    17. }
    18. Dog.prototype.bark = function () {
    19. console.log(‘Woof-Woof!’)
    20. this.energy -= .1
    21. }
    22. const charlie = new Dog(‘Charlie’, 10, ‘Goldendoodle’)

    你应该看出来了,我们刚刚重新创建了 Animal 类并为它添加了一些新属性。 如果我们想创建另一个动物,比如说 Cat ,那么我们必须再次创建一个 Cat 类,将 Animal 类中的所有常用逻辑复制到 Cat ,然后像 Dog 类一样添加 Cat 特定属性。 就是说,我们必须对我们创造的每一种不同类型的动物都这样做。
    JavaScript 代码:

    1. function Dog (name, energy, breed) {}
    2. function Cat (name, energy, declawed) {}
    3. function Giraffe (name, energy, height) {}
    4. function Monkey (name, energy, domesticated) {}

    这项工作似乎很浪费。 Animal 类是完美的基类。 这意味着它具有我们每只动物的共同特征。 无论我们是创造 狗,猫,长颈鹿还是猴子,它们都会有一个nameenergy 等级,以及 eatsleepplay 的能力。 那么每当我们为每个不同的动物创建单独的类时,我们是否可以利用 Animal类? 我们来试试吧。 我将在下面再次粘贴 Animal 类以便于参考。
    JavaScript 代码:

    1. function Animal (name, energy) {
    2. this.name = name
    3. this.energy = energy
    4. }
    5. Animal.prototype.eat = function (amount) {
    6. console.log(${this.name} is eating.)
    7. this.energy += amount
    8. }
    9. Animal.prototype.sleep = function (length) {
    10. console.log(${this.name} is sleeping.)
    11. this.energy += length
    12. }
    13. Animal.prototype.play = function (length) {
    14. console.log(${this.name} is playing.)
    15. this.energy -= length
    16. }
    17. function Dog (name, energy, breed) {
    18. }

    我们对上面的 Dog 构造函数你了解多少?
    首先,我们知道它需要3个参数,name, energybreed
    其次,我们知道它将使用 new 关键字调用,因此我们将拥有一个 this 对象。
    第三,我们知道我们需要利用 Animal 函数,这样任何狗的实例都会有一个nameenergy 等级,以及 eatsleepplay 的能力。
    第三点是有点棘手的问题。 你“利用”一个函数的方式就是调用它。 所以我们知道在 Dog 里面,我们想要调用 Animal 。 我们需要弄清楚的是我们如何在Dog 的上下文中调用 Animal。 这意味着我们想用 Dog 中的 this 关键字调用 Animal。 如果我们正确地做到了,那么 Dog 函数内部将具有 Animal 的所有属性(nameenergy)。 如果你记得 上一节我们所讨论的内容,JavaScript 中的每个函数都有一个 .call 方法。
    .call() 是函数的一个方法,它允许你调用函数时,指定该函数的上下文。
    听起来正是我们所需要的。我们想在 Dog 上下文中调用 Animal
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. Animal.call(this, name, energy)
    3. this.breed = breed
    4. }
    5. const charlie = new Dog(‘Charlie’, 10, ‘Goldendoodle’)
    6. charlie.name // Charlie
    7. charlie.energy // 10
    8. charlie.breed // Goldendoodle

    知道这个,我们就已经成功一半了。你将在上面的代码中注意到,因为这一行是 Animal.call(this, name, energy)Dog 的每个实例都将有一个 nameenergy 属性。同样,这样做的原因是,就好像我们使用从 Dog 生成的 this 关键字运行 Animal 函数一样。在我们添加了一个nameenergy 属性之后,我们又像往常一样添加了一个 breed 属性。
    请记住,这里的目标是让 Dog 的每个实例不仅具有 Animal 的所有属性,而且还具有所有方法。如果你运行上面的代码,你会注意到如果你尝试运行 charlie.eat(10) ,你将收到一个错误。目前 Dog 的每个实例都具有 Animalnameenergy)的属性,但我们没有做任何事情来确保他们也有方法(eatsleepplay)。
    让我们考虑如何解决这个问题。我们知道所有 Animal 的方法都位于 Animal.prototype 上。这意味着我们想要确保 Dog 的所有实例都可以访问Animal.prototype 上的方法。如果我们在这里使用我们的好朋友 Object.create 怎么办?如果你还记得,Object.create 允许你创建一个对象,该对象将在失败的查找中委托给另一个对象。所以在我们的例子中,我们想要创建的对象将是 Dog 的原型,而我们想要在失败的查找中委托的对象是Animal.prototype
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. Animal.call(this, name, energy)
    3. this.breed = breed
    4. }
    5. Dog.prototype = Object.create(Animal.prototype)

    现在,只要在 Dog 实例上查找失败,JavaScript 就会将该查找委托给 Animal.prototype 。 如果这仍然有点模糊,请重新阅读 JavaScript Prototype(原型) 新手指南 ,其中我们讨论了 Object.create 和 JavaScript 的 原型(prototype) 。
    让我们一起看完整个代码,然后我们将了解发生的事情。
    JavaScript 代码:

    1. function Animal (name, energy) {
    2. this.name = name
    3. this.energy = energy
    4. }
    5. Animal.prototype.eat = function (amount) {
    6. console.log(${this.name} is eating.)
    7. this.energy += amount
    8. }
    9. Animal.prototype.sleep = function (length) {
    10. console.log(${this.name} is sleeping.)
    11. this.energy += length
    12. }
    13. Animal.prototype.play = function (length) {
    14. console.log(${this.name} is playing.)
    15. this.energy -= length
    16. }
    17. function Dog (name, energy, breed) {
    18. Animal.call(this, name, energy)
    19. this.breed = breed
    20. }
    21. Dog.prototype = Object.create(Animal.prototype)

    现在我们已经创建了我们的基类( Animal )以及我们的子类( Dog ),让我们在创建 Dog 实例时看看它的样子。
    JavaScript 代码:

    1. const charlie = new Dog(‘Charlie’, 10, ‘Goldendoodle’)
    2. charlie.name // Charlie
    3. charlie.energy // 10
    4. charlie.breed // Goldendoodle

    到目前为止没有任何花哨的东西,但让我们来看看当我们调用位于 Animal 上的方法时会发生什么。
    JavaScript 代码:

    1. charlie.eat(10)
    2. /*
    3. 1) JavaScript checks if charlie has an eat property - it doesn’t.
    4. 2) JavaScript then checks if Dog.prototype has an eat property
      • it doesn’t.
    5. 3) JavaScript then checks if Animal.prototype has an eat property
      • it does so it calls it.
    6. */

    Dog.prototype 被检查的原因是因为当我们创建一个新的 Dog 实例时,我们使用了 new 关键字。在引擎中,为我们创建的 this 对象委托给Dog.prototype(见下面的注释)。
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. // this = Object.create(Dog.prototype)
    3. Animal.call(this, name, energy)
    4. this.breed = breed
    5. // return this
    6. }

    之所以检查 Animal.prototype 是因为我们用这一行覆盖了 Dog.prototype 以委托给失败的查找的 Animal.prototype
    JavaScript 代码:

    1. Dog.prototype = Object.create(Animal.prototype)

    现在我们还没有谈到的一件事是,如果 Dog 有自己的方法呢? 嗯,这是一个简单的解决方案。 就像 Animal 一样,如果我们想在该类的所有实例之间共享一个方法,我们将它添加到函数的原型中。
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. Animal.call(this, name, energy)
    3. this.breed = breed
    4. }
    5. Dog.prototype = Object.create(Animal.prototype)
    6. Dog.prototype.bark = function () {
    7. console.log(‘Woof Woof!’)
    8. this.energy -= .1
    9. }

    非常好。我们需要做一个小小的补充。如果你不记得了请回到 JavaScript Prototype(原型) 新手指南 了解详情,我们可以通过使用 instance.constructor 来访问实例的构造函数。
    JavaScript 代码:

    1. function Animal (name, energy) {
    2. this.name = name
    3. this.energy = energy
    4. }
    5. const leo = new Animal(‘Leo’, 7)
    6. console.log(leo.constructor) // Logs the constructor function

    正如前一篇文章中所解释的那样,“其工作原因是因为任何 Animal 实例都会在失败的查找中委托给 Animal.prototype 。 因此,当你尝试访问leo.prototype 时,leo 没有 prototype 属性,因此它会将该查找委托给 Animal.prototype ,它确实具有 constructor 属性。“
    我提出这个问题的原因是因为在我们的实现中,我们用一个委托给 Animal.prototype 的对象覆盖了 Dog.prototype
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. Animal.call(this, name, energy)
    3. this.breed = breed
    4. }
    5. Dog.prototype = Object.create(Animal.prototype)
    6. Dog.prototype.bark = function () {
    7. console.log(‘Woof Woof!’)
    8. this.energy -= .1
    9. }

    这意味着现在,任何打印 Dog 的实例 instance.constructor 都将获得 Animal 构造函数而不是 Dog 构造函数。你可以通过运行此代码自行查看 –
    JavaScript 代码:

    1. function Animal (name, energy) {
    2. this.name = name
    3. this.energy = energy
    4. }
    5. Animal.prototype.eat = function (amount) {
    6. console.log(${this.name} is eating.)
    7. this.energy += amount
    8. }
    9. Animal.prototype.sleep = function (length) {
    10. console.log(${this.name} is sleeping.)
    11. this.energy += length
    12. }
    13. Animal.prototype.play = function (length) {
    14. console.log(${this.name} is playing.)
    15. this.energy -= length
    16. }
    17. function Dog (name, energy, breed) {
    18. Animal.call(this, name, energy)
    19. this.breed = breed
    20. }
    21. Dog.prototype = Object.create(Animal.prototype)
    22. Dog.prototype.bark = function () {
    23. console.log(‘Woof Woof!’)
    24. this.energy -= .1
    25. }
    26. const charlie = new Dog(‘Charlie’, 10, ‘Goldendoodle’)
    27. console.log(charlie.constructor)

    请注意,即使 charlieDog 的直接实例,它也会为你提供 Animal 构造函数。同样,我们可以像上面一样了解这里发生的事情。
    JavaScript 代码:

    1. const charlie = new Dog(‘Charlie’, 10, ‘Goldendoodle’)
    2. console.log(charlie.constructor)
    3. /*
    4. 1) JavaScript checks if charlie has a constructor property - it doesn’t.
    5. 2) JavaScript then checks if Dog.prototype has a constructor property
      • it doesn’t because it was deleted when we overwrote Dog.prototype.
    6. 3) JavaScript then checks if Animal.prototype has a constructor property
      • it does so it logs that.
    7. */

    我们该如何解决这个问题?嗯,这很简单。一旦我们覆盖它,我们就可以向 Dog.prototype 添加正确的 constructor 属性。
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. Animal.call(this, name, energy)
    3. this.breed = breed
    4. }
    5. Dog.prototype = Object.create(Animal.prototype)
    6. Dog.prototype.bark = function () {
    7. console.log(‘Woof Woof!’)
    8. this.energy -= .1
    9. }
    10. Dog.prototype.constructor = Dog

    此时如果我们想要创建另一个子类,比如 Cat ,我们将遵循相同的模式。
    JavaScript 代码:

    1. function Cat (name, energy, declawed) {
    2. Animal.call(this, name, energy)
    3. this.declawed = declawed
    4. }
    5. Cat.prototype = Object.create(Animal.prototype)
    6. Cat.prototype.constructor = Cat
    7. Cat.prototype.meow = function () {
    8. console.log(‘Meow!’)
    9. this.energy -= .1
    10. }

    这种具有委托给它的子类的基类的概念称为继承,它是面向对象编程(OOP)的主要部分。 如果你来自不同的编程语言,你可能已经熟悉OOP和继承了。 在 ES6 classes 之前,在 JavaScript 中,继承是一项非常艰巨的任务,正如你在上面所看到的。你现在只需要了解什么时候使用继承,以及 .callObject.createthis ,和 FN.prototype 的良好组合。- 这些都是高级 JS 主题。让我们看看如何使用 ES6 类来完成同样的事情。
    首先,让我们回顾一下使用我们的 Animal 类从 ES5 “类” 到 ES6 类的样子。
    ES5:
    JavaScript 代码:

    1. function Animal (name, energy) {
    2. this.name = name
    3. this.energy = energy
    4. }
    5. Animal.prototype.eat = function (amount) {
    6. console.log(${this.name} is eating.)
    7. this.energy += amount
    8. }
    9. Animal.prototype.sleep = function (length) {
    10. console.log(${this.name} is sleeping.)
    11. this.energy += length
    12. }
    13. Animal.prototype.play = function (length) {
    14. console.log(${this.name} is playing.)
    15. this.energy -= length
    16. }
    17. const leo = new Animal(‘Leo’, 7)

    ES6:
    JavaScript 代码:

    1. class Animal {
    2. constructor(name, energy) {
    3. this.name = name
    4. this.energy = energy
    5. }
    6. eat(amount) {
    7. console.log(${this.name} is eating.)
    8. this.energy += amount
    9. }
    10. sleep() {
    11. console.log(${this.name} is sleeping.)
    12. this.energy += length
    13. }
    14. play() {
    15. console.log(${this.name} is playing.)
    16. this.energy -= length
    17. }
    18. }
    19. const leo = new Animal(‘Leo’, 7)

    现在我们已经将我们的 Animal 构造函数重构为 ES6 类,接下来我们需要做的是弄清楚如何重构我们的基类( Dog )。好消息是它更加直观。作为参考,在ES5 中,这是我们所拥有的。
    JavaScript 代码:

    1. function Dog (name, energy, breed) {
    2. Animal.call(this, name, energy)
    3. this.breed = breed
    4. }
    5. Dog.prototype = Object.create(Animal.prototype)
    6. Dog.prototype.bark = function () {
    7. console.log(‘Woof Woof!’)
    8. this.energy -= .1
    9. }
    10. Dog.prototype.constructor = Dog

    在我们进入继承之前,让我们使用 ES6 类来重构 Dog ,就像我们在之前的帖子中学到的那样。
    JavaScript 代码:

    1. class Dog {
    2. constructor(name, energy, breed) {
    3. this.breed = breed
    4. }
    5. bark() {
    6. console.log(‘Woof Woof!’)
    7. this.energy -= .1
    8. }
    9. }

    看起来很棒。现在,让我们弄清楚如何确保 Dog 继承自 Animal 。我们需要做的第一步是非常直接的。使用 ES6 类,你可以使用此语法 extend 基类
    JavaScript 代码:

    1. class Subclass extends Baseclass {}

    翻译成我们的例子,这将使我们的 Dog 类看起来像这样:
    JavaScript 代码:

    1. class Animal {
    2. constructor(name, energy) {
    3. this.name = name
    4. this.energy = energy
    5. }
    6. eat(amount) {
    7. console.log(${this.name} is eating.)
    8. this.energy += amount
    9. }
    10. sleep() {
    11. console.log(${this.name} is sleeping.)
    12. this.energy += length
    13. }
    14. play() {
    15. console.log(${this.name} is playing.)
    16. this.energy -= length
    17. }
    18. }
    19. class Dog extends Animal {
    20. constructor(name, energy, breed) {
    21. this.breed = breed
    22. }
    23. bark() {
    24. console.log(‘Woof Woof!’)
    25. this.energy -= .1
    26. }
    27. }

    在ES5中,为了确保 Dog 的每个实例都具有nameenergy 属性,我们使用 .call 以在 Dog 实例的上下文中调用 Animal 构造函数。 幸运的是,在 ES6 中,它更直接。 每当你扩展一个基类并且你需要调用那个基类的构造函数时,你调用 super 传递它需要的任何参数即可。 所以在我们的例子中,我们的 Dog 构造函数被重构为这样:
    JavaScript 代码:

    1. class Animal {
    2. constructor(name, energy) {
    3. this.name = name
    4. this.energy = energy
    5. }
    6. eat(amount) {
    7. console.log(${this.name} is eating.)
    8. this.energy += amount
    9. }
    10. sleep() {
    11. console.log(${this.name} is sleeping.)
    12. this.energy += length
    13. }
    14. play() {
    15. console.log(${this.name} is playing.)
    16. this.energy -= length
    17. }
    18. }
    19. class Dog extends Animal {
    20. constructor(name, energy, breed) {
    21. super(name, energy) // calls Animal’s constructor
    22. this.breed = breed
    23. }
    24. bark() {
    25. console.log(‘Woof Woof!’)
    26. this.energy -= .1
    27. }
    28. }

    就是这样。不使用 .call ,不使用 Object.create ,不用担心重置原型上的构造函数 – 只需 extends 基类并确保调用 super 即可。


    JavaScript 的有趣之处在于你学到的相同模式,最后几篇文章直接融入语言本身。 以前你了解到 Array 的所有实例都可以访问 popslicefilter 等数组方法的原因是因为所有这些方法都存在于 Array.prototype 中。
    JavaScript 代码:

    1. console.log(Array.prototype)
    2. /*
    3. concat: ?n concat()
    4. constructor: ?n Array()
    5. copyWithin: ?n copyWithin()
    6. entries: ?n entries()
    7. every: ?n every()
    8. fill: ?n fill()
    9. filter: ?n filter()
    10. find: ?n find()
    11. findIndex: ?n findIndex()
    12. forEach: ?n forEach()
    13. includes: ?n includes()
    14. indexOf: ?n indexOf()
    15. join: ?n join()
    16. keys: ?n keys()
    17. lastIndexOf: ?n lastIndexOf()
    18. length: 0n
    19. map: ?n map()
    20. pop: ?n pop()
    21. push: ?n push()
    22. reduce: ?n reduce()
    23. reduceRight: ?n reduceRight()
    24. reverse: ?n reverse()
    25. shift: ?n shift()
    26. slice: ?n slice()
    27. some: ?n some()
    28. sort: ?n sort()
    29. splice: ?n splice()
    30. toLocaleString: ?n toLocaleString()
    31. toString: ?n toString()
    32. unshift: ?n unshift()
    33. values: ?n values()
    34. */

    你知道,所有 Object 实例都可以访问 hasOwnPropertytoString 等方法的原因是因为这些方法存在于 Object.prototype 上。
    JavaScript 代码:

    1. console.log(Object.prototype)
    2. /*
    3. constructor: ?n Object()
    4. hasOwnProperty: ?n hasOwnProperty()
    5. isPrototypeOf: ?n isPrototypeOf()
    6. propertyIsEnumerable: ?n propertyIsEnumerable()
    7. toLocaleString: ?n toLocaleString()
    8. toString: ?n toString()
    9. valueOf: ?n valueOf()
    10. */

    这对你来说是一个挑战。使用上面的 Array 方法和 Object 方法列表,为什么下面的代码有效?
    JavaScript 代码:

    1. const friends = [‘Mikenzi’, ‘Jake’, ‘Ean’]
    2. friends.hasOwnProperty(‘push’) // false

    如果查看 Array.prototype ,则没有 hasOwnProperty 方法。 好吧,如果 Array.prototype 上没有 hasOwnProperty 方法,上面示例中的 friends 数组如何访问 hasOwnProperty? 原因是因为 Array 类扩展了 Object 类。 因此,在上面的示例中,当 JavaScript 看到friends 没有 hasOwnProperty 属性时,它会检查 Array.prototype 是否具有该方法。 当 Array.prototype 没有时,它会检查 Object.prototype 是否有该方法,然后再调用它。 这是我们在这篇博客文章中看到的相同过程。
    JavaScript 有两种类型 – 原始类型 和 引用类型 。
    原始类型是 booleannumberstringnullundefined 并且是不可变的。 其他所有内容都是引用类型,它们都扩展了 Object.prototype 。 这就是为什么你可以为函数和数组添加属性,这就是为什么函数和数组都可以访问 Object.prototype 上的方法。
    JavaScript 代码:

    1. function speak(){}
    2. speak.woahFunctionsAreLikeObjects = true
    3. console.log(speak.woahFunctionsAreLikeObjects) // true
    4. const friends = [‘Mikenzi’, ‘Jake’, ‘Ean’]
    5. friends.woahArraysAreLikeObjectsToo = true
    6. console.log(friends.woahArraysAreLikeObjectsToo) // true