内容简介:小编推荐:
小编推荐: 掘金是一个面向 程序员 的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。
引言:如果不处理对象,您就无法在 JavaScript 方面取得很大进展。它们几乎是 JavaScript 编程语言的所有方面的基础。在这篇文章中,您将了解用于实例化新对象的各种模式,在此过程中,您将逐步深入了解 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
(函数实例化),并将函数本身称为 constructor function
(构造函数) ,因为它负责“构造”一个新对象。
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)
现在,每当我们想要创建一种新动物(或者更广泛地说是一种新的“实例”)时,我们所要做的就是调用我们的 Animal
函数,将动物的 name
和 energy
传递给这个函数。这非常有效,而且非常简单。但是,你有发现这种模式的不足之处吗?我们要尝试解决的最大的问题与三种方法有关 – eat
, sleep
和 play
。这些方法中的每一种都不仅是动态的,而且它们也是完全通用的。这意味着没有理由重新创建这些方法,正如我们在创建新动物时所做的那样。我们只是在浪费内存,让每个动物物体都比它需要的更大。你能想到一个解决方案吗?
如果我们每次创建一个新动物时不需要重新创建这些方法,而是将它们移动到它们自己的对象上,那么我们就可以让每个动物引用那个对象了?我们可以把这种模式称为 Functional Instantiation with Shared Methods
(共享方法的函数实例化) 。描述起来有点啰嗦 路 。
Functional Instantiation with Shared Methods (共享方法的函数实例化)
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)
创建的,所以每当在 child
中查找属性失败时,JavaScript 就会将该查找委托给 parent
对象。这意味着即使 child
没有 heritage
属性,当你查找 child.heritage
时你会得到 parent
的 heritage
属性,即 Irish
。
现在,通过使用 Object.create
,我们该如何使用它来简化之前的 Animal
代码呢?好吧,我们可以使用 Object.create
委托给 animalMethods
对象,而不是像我们之前一样逐个将所有共享方法添加到 Animal
中。 为了听起来很智能,让我们称之为 Functional Instantiation with Shared Methods and Object.create
(使用共享方法和Object.create进行函数实例化) 。
Functional Instantiation with Shared Methods and 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
对象,然后在这里将找到 eat
方法。
到现在为止还挺好的。 尽管如此,我们仍然可以做出一些改进。 为了跨实例共享方法,必须管理一个单独的对象( animalMethods
)似乎有点“hacky”。 这似乎是您希望在语言本身中实现的常见功能。 事实证明,这就是你看这篇文章的原因 – prototype(原型) 。
那么究竟什么是 JavaScript 的 prototype(原型)呢? 简单地说,JavaScript 中的每个函数都有一个引用对象的 prototype 属性。 我们来亲自测试一下。
function doThing () {} console.log(doThing.prototype) // {}
如果不是创建一个单独的对象(比如我们正在使用的 animalMethods
)来管理我们的方法,也就是我们只是将每个方法放在 Animal
函数的 prototype(原型) 对象上,该怎么办呢?我们所要做的就是不使用 Object.create
委托给 animalMethods
,我们可以用使用来委托 Animal.prototype
。 我们将这种模式称为 Prototypal Instantiation
(原型实例化)。
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 中的 new
关键字在幕后的作用。
回顾一下我们的 Animal
构造函数,最重要的两个部分是创建对象并返回它。 如果不使用 Object.create
创建对象,我们将无法在查找失败时委托给函数的原型。 如果没有 return
语句,我们将永远不会返回创建的对象。
function Animal (name, energy) { let animal = Object.create(Animal.prototype) animal.name = name animal.energy = energy return animal }
new
有一个很酷的地方——当您使用 new
关键字调用函数时,注释掉的这两行代码是隐式(引擎)完成的,创建的对象称为 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)
同样,这样做以及为我们创建 this
对象的原因是,我们使用 new
关键字调用构造函数。如果在调用函数时不使用 new
,则该对象永远不会创建,也不会隐式返回。我们可以在下面的例子中看到这个问题。
function Animal (name, energy) { this.name = name this.energy = energy } const leo = Animal('Leo', 7) console.log(leo) // undefined
此模式的名称是 Pseudoclassical Instantiation
(伪类实例化) 。
如果 JavaScript 不是您的第一种编程语言,您可能会有点不安。
“WTF这个家伙只是重新创造了一个更糟糕的版本” – 你
对于那些不熟悉的人,Class(类) 允许您为对象创建模板。然后,无论何时创建该类的实例,都会获得一个具有模板中定义的属性和方法的对象。
听起来有点熟悉?这基本上就是我们对上面的 Animal
构造函数所做的事情。但是对于 Animal
构造函数,我们只使用常规的旧 JavaScript 函数来重新创建相同的功能,而不是使用 class
关键字。当然,它需要一些额外的工作以及一些关于 JavaScript “引擎” 所处理的事情的相关知识,但结果是一样的。
这是个好消息。 JavaScript 不是一种 “死” 语言。它不断得到改进,并由 TC-39委员会 不断的制定标准。这意味着即使 JavaScript 的初始版本不支持类,也不影响后续将它们添加到官方规范中。事实上,这正是TC-39委员会所做的事情。 2015年,发布了 EcmaScript(官方JavaScript规范)6 ,支持 Classes(类) 和 class
关键字。让我们看看上面的 Animal
构造函数如何使用新的 class(类) 语法。
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 类的便捷语法,首先必须理解经典的伪类模式。
关于 ES6 Classes(类) 更多相关信息可以查看 面向对象的 JavaScript – 深入了解 ES6 类 。
我们已经介绍了 JavaScript 原型的基础知识。 本文的其余部分将致力于加深理解相关知识的主题。 在另一篇文章中,我们将看看如何利用这些基础知识并使用它们来理解 JavaScript 中继承的工作原理。
数组方法
我们在上面深入讨论了如何在类的实例之间共享方法,您应该将这些方法放在类(或函数)原型上。 如果我们查看 Array
类,我们可以看到相同的模式。 从历史上看,您可能已经创建了这样的数组:
const friends = []
事实证明,这只是创建一个新的 Array
类实例的语法糖。
const friendsWithSugar = [] const friendsWithoutSugar = new Array()
您可能从未想过:数组的每个实例是如何具有所有内置方法的( splice
, slice
, pop
等)?
正如您现在所知,这是因为这些方法存在于 Array.prototype
上,当您创建一个新的 Array
实例时,您使用 new
关键字在查找失败时将该委托设置为 Array.prototype
。
我们可以通过简单地 console.log(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() */
Objects(对象) 也是完全相同的逻辑。 所有对象将在查找失败时委托给 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
现在这可行,但有更好的方法。
只要有一个特定于类本身的方法,但不需要在该类的实例之间共享,就可以将其添加为类的 static
(静态) 属性。
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种方法, constructor
, eat
, sleep
,和 play
。那讲得通。我们将实例传递给 getPrototypeOf
, leo
获取了实例的原型,这里是所有的方法。这提示我们,关于原型的另外一件事我们还没有讨论过。默认情况下,原型对象将具有 constructor
属性,该属性指向原始函数或创建实例的类。这也意味着 JavaScript 默认在原型上放置 constructor
属性,所以任何实例都可以通过 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
,它确实具有 constructor
属性。 如果这段没有看懂,请回过头来阅读上面的 Object.create
。
您可能以前看到过使用 __proto__
获取实例的原型。 这是过去的遗物。 现在,如上所述使用 Object.getPrototypeOf(instance)
获取实例的原型。
确定属性是否存在于原型上
在某些情况下,您需要知道属性是否存在于实例本身上,还是存在于对象委托的原型上。 我们可以通过循环我们创建的 leo
对象来知道这一点。假设目标是循环 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
循环将循环遍历对象本身以及它所委托的原型的所有 可枚举属性 。 因为默认情况下,您添加到函数原型的任何属性都是可枚举的,我们不仅会看到 name
和 energy
,还会看到原型上的所有方法 – eat
, sleep
和 play
。 要解决这个问题,我们需要指定所有原型方法都是不可枚举的,或者如果属性在 leo
对象本身上而不是 leo
查找失败时委托给的原型上。 hasOwnProperty
可以帮助我们实现这个需求。
hasOwnProperty
是每个对象上的一个属性,它返回一个布尔值,指示对象是否具有指定的属性作为其自身的属性,而不是对象委托给的原型。 这正是我们所需要的。 现在有了这些新知识,我们可以修改我们的代码,以便利用 for 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
。回到我们的 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 开发人员有时也会因为上面的例子而被绊倒。 因为我们正在使用之前学过的 pseudoclassical pattern
(经典伪类模式),所以当调用 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点 – 我们需要创建一个对象,该对象将在查找失败时委托给参数对象。 这个有点棘手。 为此,我们将使用我们对 new
关键字和原型如何在 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
另外,因为我们在上面证明了 pseudoclassical pattern
(经典伪类模式) 不能与箭头函数一起使用,所以箭头函数也没有 prototype
(原型) 属性。
const Animal = () => {} console.log(Animal.prototype) // undefined
英文原文: https://tylermcginnis.com/beginners-guide-to-javascript-prototype/
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Kubernetes新手指南
- VPS新手指南
- 新手指南 | permeate靶场漏洞挖掘思路分享
- Redis新手指南:在node中使用redis
- alinode 新手村生存指南
- 区块链游戏新手权威指南
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。