JavaScript原型-進階者指南

如果不好好的學習對象,你就無法在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,我們將函數本身稱爲“構造函數”,因爲它負責“構造”一個​​新對象。

功能實例化

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)

web前端1-3年  進階Q君羊:731771211

最前沿技術免費分享

“我認爲這是高級JavaScript......?”

現在,每當我們想要創造一種新動物(或者更廣泛地說是一種新的“實例”)時,我們所要做的就是調用我們的動物功能,將動物的名字和能量水平傳遞給它。這非常有效,而且非常簡單。但是,你能發現這種模式的弱點嗎?最大的和我們試圖解決的問題與三種方法有關 - 吃飯,睡覺和玩耍。這些方法中的每一種都不僅是動態的,而且它們也是完全通用的。這意味着沒有理由重新創建這些方法,正如我們在創建新動物時所做的那樣。你能想到一個解決方案嗎?如果不是每次創建新動物時重新創建這些方法,我們將它們移動到自己的對象然後我們可以讓每個動物引用該對象,該怎麼辦?我們可以將這種模式稱爲功能實例化與共享方法

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

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允許創建一個對象,只要該對象上的屬性查找失敗,它就可以查詢另一個對象以查看該另一個對象是否具有該屬性。我們來看一些代碼。

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對象。

到現在爲止還挺好。儘管如此,我們仍然可以做出一些改進。爲了跨實例共享方法,必須管理一個單獨的對象(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(原型實例化)。

對於那些不熟悉的人,Class允許您爲對象創建藍圖。然後,無論何時創建該類的實例,都會獲得一個具有藍圖中定義的屬性和方法的對象。

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

JavaScript不是一種死語言。它正在不斷得到改進

看看上面的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類的便捷語法,首先必須理解僞古典模式。

數組方法

我們在上面深入討論瞭如果要在類的實例之間共享方法,您應該將這些方法放在類(或函數)原型上。如果我們查看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

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

在某些情況下,需要知道屬性是否存在於實例本身上,還是存在於對象委託的原型上。我們可以通過循環我們創建的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可以幫助我們的地方。

這是爲什麼?for循環將循環遍歷對象本身以及它所委託的原型的所有可枚舉屬性。因爲默認情況下,您添加到函數原型的任何屬性都是可枚舉的,我們不僅會看到名稱和能量,還會看到原型上的所有方法 - 吃,睡,玩。要解決這個問題,我們需要指定所有原型方法都是不可枚舉的或者我們需要一種類似console.log的方法,如果屬性是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

如果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。

箭頭函數

箭頭函數沒有自己的this關鍵字。因此,箭頭函數不能是構造函數,如果您嘗試使用new關鍵字調用箭頭函數,它將拋出錯誤。

const Animal = () => {}

const leo = new Animal() // Error: Animal is not a constructor

另外,爲了證明箭頭函數不能是構造函數,如下,我們看到箭頭函數也沒有原型屬性。

const Animal = () => {}
console.log(Animal.prototype) // undefined
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章