JavaScript原型初學者指南

視頻Video
https://www.youtube.com/watch...編程

前言

若是很差好的學習對象,你就沒法在JavaScript中得到很大的成就。它們幾乎是JavaScript編程語言的每一個方面的基礎。在這篇文章中,您將瞭解用於實例化新對象的各類模式,而且這樣作,您將逐漸深刻了解JavaScript的原型。數組

對象是鍵/值對。建立對象的最經常使用方法是使用花括號{},並使用點表示法向對象添加屬性和方法。編程語言

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
}

如上代碼,在咱們的應用程序中,咱們須要建立多個動物。固然,下一步是將邏輯封裝在咱們能夠在須要建立新動物時調用的函數內部。咱們將這種模式稱爲Functional Instantiation,咱們將函數自己稱爲「構造函數」,由於它負責「構造」一個​​新對象。ide

功能實例化

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)

如今,每當咱們想要創造一種新動物(或者更普遍地說是一種新的「實例」)時,咱們所要作的就是調用咱們的動物功能,將動物的名字和能量水平傳遞給它。這很是有效,並且很是簡單。可是,你能發現這種模式的弱點嗎?最大的和咱們試圖解決的問題與三種方法有關 - 吃飯,睡覺和玩耍。這些方法中的每一種都不只是動態的,並且它們也是徹底通用的。這意味着沒有理由從新建立這些方法,正如咱們在建立新動物時所作的那樣。你能想到一個解決方案嗎?若是不是每次建立新動物時從新建立這些方法,咱們將它們移動到本身的對象而後咱們可讓每一個動物引用該對象,該怎麼辦?咱們能夠將這種模式稱爲功能實例化與共享方法🤷♂️。函數

使用共享方法的功能實例化

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函數中引用該對象,咱們如今已經解決了內存浪費和過大的動物對象的問題。工具

Object.create

讓咱們再次使用Object.create改進咱們的例子。簡單地說, Object.create容許您建立一個對象。換句話說,Object.create容許您建立一個對象,只要該對象上的屬性查找失敗,它就能夠查詢另外一個對象以查看該另外一個對象是否具備該屬性。咱們來看一些代碼。oop

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)建立的,因此每當在子級上查找失敗的屬性時,JavaScript都會將該查找委託給父對象。這意味着即便孩子沒有遺產,父母也會在記錄時這樣作。這樣你就會獲得父母的遺產(屬性值的傳遞)。學習

如今在咱們的工具中使用Object.create,咱們如何使用它來簡化以前的Animal代碼?好吧,咱們可使用Object.create委託給animalMethods對象,而不是像咱們如今同樣逐個將全部共享方法添加到動物中。聽起來很聰明,讓咱們將這個稱爲功能實例化與共享方法用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方法。那個查找將失敗,Object.create,它將委託給animalMethods對象。this

到如今爲止還挺好。儘管如此,咱們仍然能夠作出一些改進。爲了跨實例共享方法,必須管理一個單獨的對象(animalMethods)彷佛有點「hacky」。這彷佛是您但願在語言自己中實現的常見功能。這就是你在這裏的所有緣由 - prototype。

那麼究竟什麼是JavaScript的原型?好吧,簡單地說,JavaScript中的每一個函數都有一個引用對象的prototype屬性。對嗎?親自測試一下。

function doThing () {}
console.log(doThing.prototype) // {}

若是不是建立一個單獨的對象來管理咱們的方法(好比咱們正在使用animalMethods),咱們只是將每一個方法放在Animal函數的原型上,該怎麼辦?而後咱們所要作的就是不使用Object.create委託給animalMethods,咱們能夠用它來委託Animal.prototype。咱們將這種模式稱爲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)

👏👏👏 一樣,原型只是JavaScript中每一個函數都具備的屬性,而且如上所述,它容許咱們在函數的全部實例之間共享方法。咱們全部的功能仍然相同,但如今咱們沒必要爲全部方法管理一個單獨的對象,咱們可使用另外一個內置於Animal函數自己的對象Animal.prototype。

在這一點上,咱們知道三件事:

  1. 如何建立構造函數。
  2. 如何將方法添加到構造函數的原型中。
  3. 如何使用Object.create將失敗的查找委託給函數的原型。(繼承)

這三個任務彷佛是任何編程語言的基礎。JavaScript是否真的那麼糟糕,沒有更簡單「內置」的方式來完成一樣的事情?然而並非的,它是經過使用new關鍵字來完成的。

咱們採起的緩慢,有條理的方法有什麼好處,你如今能夠深刻了解JavaScript中新關鍵字的內容。

回顧一下咱們的Animal構造函數,最重要的兩個部分是建立對象並返回它。若是不使用Object.create建立對象,咱們將沒法在失敗的查找上委託函數的原型。若是沒有return語句,咱們將永遠不會返回建立的對象。

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

  return animal
}

這是關於new的一個很酷的事情 - 當你使用new關鍵字調用一個函數時,這兩行是隱式完成的(JavaScript引擎),而且建立的對象稱爲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

此模式的名稱是Pseudoclassical Instantiation(原型實例化)。

若是JavaScript不是您的第一種編程語言,您可能會有點不安。

對於那些不熟悉的人,Class容許您爲對象建立藍圖。而後,不管什麼時候建立該類的實例,都會得到一個具備藍圖中定義的屬性和方法的對象。

聽起來有點熟?這基本上就是咱們對上面的Animal構造函數所作的。可是,咱們只使用常規的舊JavaScript函數來從新建立相同的功能,而不是使用class關鍵字。固然,它須要一些額外的工做以及一些關於JavaScript引擎運行的知識,但結果是同樣的。

這是個好消息。JavaScript不是一種死語言。它正在不斷獲得改進,並由TC-39委員會添加。事實上,2015年,發佈了EcmaScript(官方JavaScript規範)6(ES6),支持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)

很乾淨吧?

所以,若是這是建立類的新方法,爲何咱們花了這麼多時間來翻過舊的方式呢?緣由是由於新的方式(使用class關鍵字)主要只是咱們稱之爲僞古典模式的現有方式的「語法糖」。爲了更好的理解ES6類的便捷語法,首先必須理解僞古典模式。

在這一點上,咱們已經介紹了JavaScript原型的基礎知識。本文的其他部分將致力於理解與其相關的其餘「知識淵博」主題。在另外一篇文章中,咱們將看看如何利用這些基礎知識並使用它們來理解繼承在JavaScript中的工做原理。

數組方法

咱們在上面深刻討論了若是要在類的實例之間共享方法,您應該將這些方法放在類(或函數)原型上。若是咱們查看Array類,咱們能夠看到相同的模式。從歷史上看,您可能已經建立了這樣的數組

const friends = []

事實證實,建立一個新的Array類其實也是一個語法糖。

const friendsWithSugar = []

const friendsWithoutSugar = new Array()

您可能從未想過的一件事是數組的每一個實例中的內置方法是從何而來的(splice, slice, pop, etc)?

正如您如今所知,這是由於這些方法存在於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,這就是全部對象都有toString和hasOwnProperty等方法的緣由。

靜態方法

到目前爲止,咱們已經介紹了爲何以及如何在類的實例之間共享方法。可是,若是咱們有一個對Class很重要但不須要跨實例共享的方法呢?例如,若是咱們有一個函數接受一個Animal實例數組並肯定下一個須要接收哪個呢?咱們將其稱爲nextToEat。

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

  return sortedByLeastEnergy[0].name
}

由於咱們不但願在全部實例之間共享它,因此在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做爲靜態屬性(static),因此它存在於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 prototype = Object.getPrototypeOf(leo)

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

prototype === Animal.prototype // true

上面的代碼有兩個重要的要點。

首先,你會注意到proto是一個有4種方法,構造函數,吃飯,睡眠和遊戲的對象。那講得通。咱們在實例中使用了getPrototypeOf傳遞,leo獲取了實例的原型,這是咱們全部方法都存在的地方。這告訴咱們關於原型的另一件事咱們尚未談過。默認狀況下,原型對象將具備構造函數屬性,該屬性指向原始函數或建立實例的類。這也意味着由於JavaScript默認在原型上放置構造函數屬性,因此任何實例均可以經過instance.constructor訪問它們的構造函數。

上面的第二個重要內容是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.prototype時,leo沒有prototype屬性,所以它會將該查找委託給Animal.prototype,它確實具備構造函數屬性。若是這段沒有理解到,請回過頭來閱讀上面的Object.create。

您可能已經看過 __ proto __以前用於獲取實例的原型的方法,這已是過去式來,如上所述使用 Object.getPrototypeOf(instance)。

肯定屬性是否存在於原型上

在某些狀況下,您須要知道屬性是否存在於實例自己上,仍是存在於對象委託的原型上。咱們能夠經過循環咱們建立的leo對象來看到這一點。讓咱們說目標是循環leo並記錄它的全部鍵和值。使用for循環,可能看起來像這樣。

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循環將循環遍歷對象自己以及它所委託的原型的全部可枚舉屬性。由於默認狀況下,您添加到函數原型的任何屬性都是可枚舉的,咱們不只會看到名稱和能量,還會看到原型上的全部方法 - 吃,睡,玩。要解決這個問題,咱們須要指定全部原型方法都是不可枚舉的或者咱們須要一種相似console.log的方法,若是屬性是leo對象自己而不是leo委託給的原型在失敗的查找。這是hasOwnProperty能夠幫助咱們的地方。

hasOwnProperty是每一個對象上的一個屬性,它返回一個布爾值,指示對象是否具備指定的屬性做爲其本身的屬性,而不是對象委託給的原型。這正是咱們所須要的。如今有了這些新知識,咱們能夠修改咱們的代碼,以便利用in循環中的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

若是object是Class的實例,則上面的語句將返回true,不然返回false。回到咱們的動物示例,咱們會有相似的東西。

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
}

這彷佛是一個很是重要的細節,讓其餘開發人員記住。假設咱們正在與其餘開發人員合做,有沒有辦法確保咱們的Animal構造函數始終使用new關鍵字調用?事實證實,它是經過使用咱們以前學到的instanceof運算符來實現的。

若是使用new關鍵字調用構造函數,那麼構造函數體的內部將是構造函數自己的實例。這是一些代碼。

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
}
如今不是僅僅向函數的使用者記錄警告,若是咱們從新調用該函數,但此次若是不使用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開始吧。

Object.create = function (objToDelegateTo) {

}
很簡單。

如今#2 - 咱們須要建立一個對象,該對象將在失敗的查找中委託給參數對象。這個有點棘手。爲此,咱們將使用咱們對新關鍵字和原型如何在JavaScript中工做的知識。首先,在Object.create實現的主體內部,咱們將建立一個空函數。而後,咱們將該空函數的原型設置爲等於參數對象。而後,爲了建立一個新對象,咱們將使用new關鍵字調用咱們的空函數。若是咱們返回新建立的對象,那麼它也將完成#3。

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

譯者注:如下是一些擴展閱讀,但願對理解這篇文章有所幫助

相關文章
相關標籤/搜索