视频 Videohttps://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,我们将函数本身称为“构造函数”,因为它负责“构造”一个新对象。
功能实例化
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 允许您创建一个对象,只要该对象上的属性查找失败,它就可以查询另一个对象以查看该另一个对象是否具有该属性。我们来看一些代码。
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。
在这一点上,我们知道三件事:
如何创建构造函数。
如何将方法添加到构造函数的原型中。
如何使用 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
译者注:以下是一些扩展阅读,希望对理解这篇文章有所帮助
instanceof
constructor
Object.create()