【译】JavaScript 原型的深刻指南

译者:前端小智javascript

原文:tylermcginnis.com/beginners-g…前端

不学会怎么处理对象,你在 JavaScript 道路就就走不了多远。它们几乎是 JavaScript 编程语言每一个方面的基础。事实上,学习如何建立对象多是你刚开始学习的第一件事。java

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!git

对象是键/值对。建立对象的最经常使用方法是使用花括号{},并使用表示法向对象添加属性和方法。github

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
}
复制代码

如今,在咱们的应用程序中,咱们须要建立多个 animal。 固然,下一步是将逻辑封装,当咱们须要建立新 animal 时,只需调用函数便可,咱们将这种模式称为函数的实例化(unctional 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)
复制代码

如今,不管什么时候咱们想要建立一个新 animal(或者更普遍地说,建立一个新的“实例”),咱们所要作的就是调用咱们的 Animal 函数,并传入参数:nameenergy 。这颇有用,并且很是简单。可是,你能说这种模式的哪些缺点吗?数组

最大的和咱们试图解决的问题与函数里面的三个方法有关 - eatsleepplay。 这些方法中的每一种都不只是动态的,并且它们也是彻底通用的。这意味着,咱们没有理由像如今同样,在创造新animal的时候从新建立这些方法。咱们只是在浪费内存,让每个新建的对象都比实际须要的还大。ecmascript

你能想到一个解决方案吗? 若是不是在每次建立新动物时从新建立这些方法,咱们将它们移动到本身的对象而后咱们可让每一个动物引用该对象,该怎么办? 咱们能够将此模式称为函数实例化与共享方法编程语言

函数实例化与共享方法

const animalMethods = {
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  },
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  },
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy
  animal.eat = animalMethods.eat
  animal.sleep = animalMethods.sleep
  animal.play = animalMethods.play

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)
复制代码

经过将共享方法移动到它们本身的对象并在 Animal 函数中引用该对象,咱们如今已经解决了内存浪费和新对象体积过大的问题。ide

Object.create

让咱们再次使用 Object.create 改进咱们的例子。 简单地说,Object.create 容许你建立一个对象,该对象将在失败的查找中委托给另外一个对象。 换句话说,Object.create 容许你建立一个对象,只要该对象上的属性查找失败,它就能够查询另外一个对象以查看该另外一个对象是否具备该属性。 咱们来看一些代码:

const parent = {
  name: 'Stacey',
  age: 35,
  heritage: 'Irish'
}

const child = Object.create(parent)
child.name = 'Ryan'
child.age = 7

console.log(child.name) // Ryan
console.log(child.age) // 7
console.log(child.heritage) // Irish
复制代码

所以,在上面的示例中,因为 child 是用 object.create(parent) 建立的,因此每当child 对象上的属性查找失败时,JavaScript 就会将该查找委托给 parent 对象。这意味着即便 child 没有属性 heritage ,当你打印 child.heritage 时,它会从 parent 对象中找到对应 heritage 并打印出来。

如今如何使用 Object.create 来简化以前的 Animal代码? 好吧,咱们可使用Object.create 来委托给animalMethods对象,而不是像咱们如今同样逐一贯 animal 添加全部共享方法。 为了B 格一点,就叫作 使用共享方法 和 Object.create 的函数实例化

使用共享方法 和 Object.create 的函数实例化

const animalMethods = {
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  },
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  },
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function Animal (name, energy) {
  let animal = Object.create(animalMethods)
  animal.name = name
  animal.energy = energy

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

leo.eat(10)
snoop.play(5)
复制代码

因此如今当咱们调用 leo.eat 时,JavaScript 将在 leo 对象上查找 eat 方法,由于 leo 中没有 eat 方法,因此查找将失败,因为 Object.create,它将委托给animalMethods对象,因此会从 animalMethods 对象上找到 eat 方法。

到如今为止还挺好。尽管如此,咱们仍然能够作出一些改进。为了跨实例共享方法,必须管理一个单独的对象(animalMethods)彷佛有点“傻哈”。咱们但愿这在语言自己中实现的一个常见特,因此就须要引出下一个属性 - prototype

那么究竟 JavaScript 中的 prototype 是什么? 好吧,简单地说,JavaScript 中的每一个函数都有一个引用对象的prototype属性。

function doThing () {}
console.log(doThing.prototype) // {}
复制代码

若是不是建立一个单独的对象来管理咱们的方法(如上例中 animalMethods),咱们只是将每一个方法放在 Animal 函数的 prototype 上,该怎么办? 而后咱们所要作的就是不使用Object.create 委托给animalMethods,咱们能够用它来委托Animal.prototype。 咱们将这种模式称为 原型实例化。

原型(prototype)实例化

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)
复制代码

一样,prototype 只是 JavaScript 中的每一个函数都具备的一个属性,正如咱们前面看到的,它容许咱们跨函数的全部实例共享方法。咱们全部的功能仍然是相同的,可是如今咱们没必要为全部的方法管理一个单独的对象,咱们只须要使用 Animal 函数自己内置的另外一个对象Animal.prototype

更进一步

如今咱们知道三个点:

  1. 如何建立构造函数。

  2. 如何向构造函数的原型添加方法。

  3. 如何使用 Object.create 将失败的查找委托给函数的原型。

这三个点对于任何编程语言来讲都是很是基础的。JavaScript 真的有那么糟糕,以致于没有更简单的方法来完成一样的事情吗?正如你可能已经猜到的那样,如今已经有了,它是经过使用new关键字来实现的。

回顾一下咱们的 Animal 构造函数,最重要的两个部分是建立对象并返回它。 若是不使用Object.create建立对象,咱们将没法在失败的查找上委托函数的原型。 若是没有return语句,咱们将永远不会返回建立的对象。

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype) // 1 
  animal.name = name
  animal.energy = energy

  return animal   // 2
}
复制代码

关于 new,有一件很酷的事情——当你使用new关键字调用一个函数时,如下编号为12两行代码将隐式地(在底层)为你完成,所建立的对象被称为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)
复制代码

正常以下:

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
复制代码

再次说明,之因此这样作,而且这个对象是为咱们建立的,是由于咱们用new关键字调用了构造函数。若是在调用函数时省略new,则永远不会建立该对象,也不会隐式地返回该对象。咱们能够在下面的例子中看到这个问题。

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)
console.log(leo) // undefined
复制代码

这种模式称为 伪类实例化

对于那些不熟悉的人,容许你为对象建立蓝图。 而后,每当你建立该类的实例时,你能够访问这个对象中定义的属性和方法。

听起来有点熟? 这基本上就是咱们对上面的 Animal 构造函数所作的。 可是,咱们只使用常规的旧 JavaScript 函数来从新建立相同的功能,而不是使用class关键字。 固然,它须要一些额外的工做以及了解一些 JavaScript “底层” 发生的事情,但结果是同样的。

这是个好消息。 JavaScript 不是一种死语言。 TC-39委员会不断改进和补充。 这意味着即便JavaScript的初始版本不支持类,也没有理由将它们添加到官方规范中。 事实上,这正是TC-39委员会所作的。 2015 年,发布了EcmaScript(官方JavaScript规范)6,支持类和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)
复制代码

这个相对前面的例子,是相对简单明了的。

所以,若是这是建立类的新方法,为何咱们花了这么多时间来复习旧的方式呢? 缘由是由于新方法(使用class关键字)主要只是咱们称之为伪类实例化模式现有方式的“语法糖”。 为了彻底理解 ES6 类的便捷语法,首先必须理解伪类实例化模式

至此,咱们已经介绍了 JavaScript 原型的基本原理。这篇文章的其他部分将致力于理解与之相关的其余好话题。在另外一篇文章中,咱们将研究如何利用这些基本原理,并使用它们来理解JavaScript中的继承是如何工做的。

数组方法

咱们在上面深刻讨论了如何在一个类的实例之间共享方法,你应该将这些方法放在类(或函数)原型上。 若是咱们查看Array类,咱们能够看到相同的模式。

onst friends = []
复制代码

觉得是代替使用 new Array() 的一个语法糖。

const friendsWithSugar = []

const friendsWithoutSugar = new Array()
复制代码

你可能从未想过的一件事是,数组的每一个实例如何具备全部内置方法 (splice, slice, pop 等)?

正如你如今所知,这是由于这些方法存在于 Array.prototype 上,当你建立新的Array实例时,你使用new关键字在失败的查找中将该委托设置为 Array.prototype

咱们能够打印 Array.prototype 来查看有哪些方法:

console.log(Array.prototype)

/*
  concat: ƒn concat()
  constructor: ƒn Array()
  copyWithin: ƒn copyWithin()
  entries: ƒn entries()
  every: ƒn every()
  fill: ƒn fill()
  filter: ƒn filter()
  find: ƒn find()
  findIndex: ƒn findIndex()
  forEach: ƒn forEach()
  includes: ƒn includes()
  indexOf: ƒn indexOf()
  join: ƒn join()
  keys: ƒn keys()
  lastIndexOf: ƒn lastIndexOf()
  length: 0n
  map: ƒn map()
  pop: ƒn pop()
  push: ƒn push()
  reduce: ƒn reduce()
  reduceRight: ƒn reduceRight()
  reverse: ƒn reverse()
  shift: ƒn shift()
  slice: ƒn slice()
  some: ƒn some()
  sort: ƒn sort()
  splice: ƒn splice()
  toLocaleString: ƒn toLocaleString()
  toString: ƒn toString()
  unshift: ƒn unshift()
  values: ƒn values()
*/
复制代码

对象也存在彻底相同的逻辑。全部的对象将在失败的查找后委托给 Object.prototype,这就是全部对象都有 toStringhasOwnProperty 等方法的缘由

静态方法

到目前为止,咱们已经讨论了为何以及如何在类的实例之间共享方法。可是,若是咱们有一个对类很重要的方法,可是不须要在实例之间共享该方法怎么办?例如,若是咱们有一个函数,它接收一系列 Animal 实例,并肯定下一步须要喂养哪个呢?咱们这个方法叫作 nextToEat

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}
复制代码

由于咱们不但愿在全部实例之间共享 nextToEat,因此在 Animal.prototype上使用nextToEat 是没有意义的。 相反,咱们能够将其视为辅助方法。

因此若是nextToEat不该该存在于Animal.prototype中,咱们应该把它放在哪里? 显而易见的答案是咱们能够将nextToEat放在与咱们的Animal类相同的范围内,而后像咱们一般那样在须要时引用它。

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(nextToEat([leo, snoop])) // Leo
复制代码

这是可行的,可是还有一个更好的方法。

只要有一个特定于类自己的方法,但不须要在该类的实例之间共享,就能够将其定义为类的静态属性

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
  static nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a,b) => {
      return a.energy - b.energy
    })

    return sortedByLeastEnergy[0].name
  }
}
复制代码

如今,由于咱们在类上添加了nextToEat做为静态属性,因此它存在于Animal类自己(而不是它的原型)上,而且可使用Animal.nextToEat进行调用 。

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo
复制代码

由于咱们在这篇文章中都遵循了相似的模式,让咱们来看看如何使用 ES5 完成一样的事情。 在上面的例子中,咱们看到了如何使用 static 关键字将方法直接放在类自己上。 使用 ES5,一样的模式就像手动将方法添加到函数对象同样简单。

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

Animal.nextToEat = function (nextToEat) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo
复制代码

获取对象的原型

不管您使用哪一种模式建立对象,均可以使用Object.getPrototypeOf方法完成获取该对象的原型。

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
const proto  = Object.getPrototypeOf(leo)

console.log(proto )
// {constructor: ƒ, eat: ƒ, sleep: ƒ, play: ƒ}

proto === Animal.prototype // true
复制代码

上面的代码有两个重要的要点。

首先,你将注意到 proto 是一个具备 4 个方法的对象,constructoreatsleepplay。这是有意义的。咱们使用getPrototypeOf传递实例,leo取回实例原型,这是咱们全部方法的所在。

这也告诉了咱们关于 prototype 的另外一件事,咱们尚未讨论过。默认状况下,prototype对象将具备一个 constructor 属性,该属性指向初始函数或建立实例的类。这也意味着由于 JavaScript 默认在原型上放置构造函数属性,因此任何实例均可以经过。

第二个重要的点是Object.getPrototypeOf(leo) === Animal.prototype。 这也是有道理的。 Animal 构造函数有一个prototype属性,咱们能够在全部实例之间共享方法,getPrototypeOf 容许咱们查看实例自己的原型。

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function
复制代码

为了配合咱们以前使用 Object.create 所讨论的内容,其工做原理是由于任何Animal实例都会在失败的查找中委托给Animal.prototype。 所以,当你尝试访问leo.constructor时,leo没有 constructor 属性,所以它会将该查找委托给 Animal.prototype,而Animal.prototype 确实具备构造函数属性。

你以前可能看过使用 __proto__ 用于获取实例的原型,这是过去的遗物。 相反,如上所述使用 Object.getPrototypeOf(instance)

判断原型上是否包含某个属性

在某些状况下,你须要知道属性是否存在于实例自己上,仍是存在于对象委托的原型上。 咱们能够经过循环打印咱们建立的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]}`)
}
复制代码

我所指望的打印结果可能以下:

Key: name. Value: Leo
Key: energy. Value: 7
复制代码

然而,若是你运行代码,看到的是这样的-

Key: name. Value: Leo
Key: energy. Value: 7
Key: eat. Value: function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}
Key: sleep. Value: function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}
Key: play. Value: function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}
复制代码

这是为何? 对于for in循环来讲,循环将遍历对象自己以及它所委托的原型的全部可枚举属性。 由于默认状况下,你添加到函数原型的任何属性都是可枚举的,咱们不只会看到nameenergy,还会看到原型上的全部方法 -eatsleepplay

要解决这个问题,咱们须要指定全部原型方法都是不可枚举的,或者只打印属性位于 leo 对象自己而不是leo委托给失败查找的原型。 这是 hasOwnProperty 能够帮助咱们的地方。

...

const leo = new Animal('Leo', 7)

for(let key in leo) {
  if (leo.hasOwnProperty(key)) {
    console.log(`Key: ${key}. Value: ${leo[key]}`)
  }
}
复制代码

如今咱们看到的只是leo对象自己的属性,而不是leo委托的原型。

Key: name. Value: Leo
Key: energy. Value: 7
复制代码

果你仍然对 hasOwnProperty 感到困惑,这里有一些代码能够帮你更好的理清它。

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)

leo.hasOwnProperty('name') // true
leo.hasOwnProperty('energy') // true
leo.hasOwnProperty('eat') // false
leo.hasOwnProperty('sleep') // false
leo.hasOwnProperty('play') // false
复制代码

检查对象是不是类的实例

有时你想知道对象是不是特定类的实例。 为此,你可使用instanceof运算符。 用例很是简单,但若是你之前从未见过它,实际的语法有点奇怪。 它的工做方式以下

object instanceof Class
复制代码

若是 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
复制代码

instanceof 的工做方式是检查对象原型链中是否存在 constructor.prototype。 在上面的例子中,leo instanceof Animal 为 true,由于 Object.getPrototypeOf(leo) === Animal.prototype。 另外,leo instanceof User 为 false,由于Object.getPrototypeOf(leo) !== User.prototype

建立新的不可知的构造函数

你能找出下面代码中的错误吗

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)
复制代码

即便是经验丰富的 JavaScript 开发人员有时也会被上面的例子绊倒。由于咱们使用的是前面学过的伪类实例模式,因此在调用Animal构造函数时,须要确保使用new关键字调用它。若是咱们不这样作,那么 this 关键字就不会被建立,它也不会隐式地返回。

做为复习,注释掉的行是在函数上使用new关键字时背后发生的事情。

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

  this.name = name
  this.energy = energy

  // return this
}
复制代码

让其余开发人员记住,这彷佛是一个很是重要的细节。 假设咱们正在与其余开发人员合做,咱们是否有办法确保始终使用new关键字调用咱们的Animal构造函数? 事实证实,能够经过使用咱们以前学到的instanceof运算符来实现的。

若是使用new关键字调用构造函数,那么构造函数体的内部 this 将是构造函数自己的实例。

function Aniam (name, energy) {
  if (this instanceof Animal === false) {
     console.warn('Forgot to call Animal with the new keyword')
  }

  this.name = name
  this.energy = energy
}
复制代码

如今,若是咱们从新调用函数,可是此次使用 new 的关键字,而不是仅仅向函数的调用者打印一个警告呢?

function Animal (name, energy) {
  if (this instanceof Animal === false) {
    return new Animal(name, energy)
  }

  this.name = name
  this.energy = energy
}
复制代码

如今,无论是否使用new关键字调用Animal,它仍然能够正常工做。

重写 Object.create

在这篇文章中,咱们很是依赖于Object.create来建立委托给构造函数原型的对象。 此时,你应该知道如何在代码中使用Object.create,但你可能没有想到的一件事是Object.create其实是如何工做的。 为了让你真正了解Object.create的工做原理,咱们将本身从新建立它。 首先,咱们对Object.create的工做原理了解多少?

  1. 它接受一个对象的参数。

  2. 它建立一个对象,在查找失败时委托给参数对象

  3. 它返回新建立的对象。

    Object.create = function (objToDelegateTo) {

    }

如今,咱们须要建立一个对象,该对象将在失败的查找中委托给参数对象。 这个有点棘手。 为此,咱们将使用 new 关键字相关的知识。

首先,在 Object.create 主体内部建立一个空函数。 而后,将空函数的 prototype 设置为等于传入参数对象。 而后,返回使用new关键字调用咱们的空函数。

Object.create = function (objToDelegateTo) {
  function Fn(){}
  Fn.prototype = objToDelegateTo
  return new Fn()
}
复制代码

当咱们在上面的代码中建立一个新函数Fn时,它带有一个prototype属性。 当咱们使用new关键字调用它时,咱们知道咱们将获得的是一个将在失败的查找中委托给函数原型的对象。

若是咱们覆盖函数的原型,那么咱们能够决定在失败的查找中委托哪一个对象。 因此在上面的例子中,咱们用调用Object.create时传入的对象覆盖Fn的原型,咱们称之为objToDelegateTo

请注意,咱们只支持 Object.create 的单个参数。 官方实现还支持第二个可选参数,该参数容许你向建立的对象添加更多属性。

箭头函数

箭头函数没有本身的this关键字。 所以,箭头函数不能是构造函数,若是你尝试使用new关键字调用箭头函数,它将引起错误。

const Animal = () => {}

const leo = new Animal() // Error: Animal is not a constructor
复制代码

另外,由于咱们在上面说明了伪类实例模式不能与箭头函数一块儿使用,因此箭头函数也没有原型属性。

const Animal = () => {}
console.log(Animal.prototype) // undefined
复制代码

代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

你的点赞是我持续分享好东西的动力,欢迎点赞!

交流

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

github.com/qq449245884…

我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,便可看到福利,你懂的。

相关文章
相关标签/搜索