JavaScript 原型的深刻指南

阿里雲最近在作活動,低至2折,有興趣能夠看看:
https://promotion.aliyun.com/...

爲了保證的可讀性,本文采用意譯而非直譯。html

不學會怎麼處理對象,你在 JavaScript 道路就就走不了多遠。它們幾乎是 JavaScript 編程語言每一個方面的基礎。事實上,學習如何建立對象多是你剛開始學習的第一件事。前端

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

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),咱們將函數自己稱爲「構造函數」,由於它負責「構造」一個​​新對象。github

函數的實例化

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的時候從新建立這些方法。咱們只是在浪費內存,讓每個新建的對象都比實際須要的還大。segmentfault

你能想到一個解決方案嗎? 若是不是在每次建立新動物時從新建立這些方法,咱們將它們移動到本身的對象而後咱們可讓每一個動物引用該對象,該怎麼辦? 咱們能夠將此模式稱爲函數實例化與共享方法數組

函數實例化與共享方法

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

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類,咱們能夠看到相同的模式。

const 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,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索