在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 }
一个简单animal对象就创建完成了,当然还可以继续给它添加属性和方法。
简单来说,现在在我们的程序中,我们将需要创造不止一种动物,猫、狗、大象……。自然,下一步是将该逻辑封装在一个函数中,我们可以在需要创建新动物时调用该逻辑。我们将其称为“功能实例化” 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 cat = Animal('Cat', 7) const dog = Animal('Dog', 10)
现在,每当我们要创建一个新的动物(或者更广泛地说一个新的“实例”),我们所要做的就是调用我们的Animal函数
,给它添加名字和能量。
上面的方法简单明了,这很好用,而且非常简单。
但是,您可以发现这种模式有什么缺点吗?
动物都有三种方法- eat(吃饭)
,sleep(睡觉)
和play(玩)
。这些方法中的每一个不仅是动态的,而且是完全通用的。
这意味着没有必要重新创建这些方法,就像我们在创建新动物时所做的那样。我们只是在浪费内存,使每个动物对象都超出其需要。
您能想到解决方案吗?
如果不是每次创建新动物时都重新创建这些方法,而是将它们移到它们自己的对象上,然后让每个动物都引用该对象怎么办?
我们可以称这种模式为共享方法的功能实例化。
共享方法的功能实例化
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 cat = Animal('Cat', 7) const dog = Animal('Dog', 10)
通过将共享方法移动到它们自己的对象并在我们的Animal
函数内部引用该对象,我们现在解决了内存浪费和过大的Animal对象的问题。
对象创建
让我们通过使用Object.create再次完善示例。
简而言之,Object.create允许您创建一个对象,该对象将在失败的查找时委派给另一个对象。
换句话说,Object.create允许您创建一个对象,并且只要对该对象的属性查找失败,它就可以查询另一个对象以查看该另一个对象是否具有该属性。
话有点多,让我们看一些代码。
const parent = { name: 'Stacey', age: 35, heritage: '一千万' } 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) // 一千万
因此,在上面的示例中,由于child
是使用创建的Object.create(parent)
,因此每当对属性进行失败的查找时child
,JavaScript都会将查找委托给该parent
对象(找到它的父类)。
这意味着即使child
没有heritage
财产,就会往上查找parent(父类)
,child.heritage
您将获得parent
的遗产“一千万”
。
现在,Object.create
在我们的工具类中,我们如何使用它来简化Animal
以前的代码?
好吧,与其像现在那样将所有共享方法一一添加到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 cat = Animal('Cat', 7) const dog = Animal('Dog', 10) cat.eat(10) dog.play(5)
因此,现在当我们调用时cat.eat
,JavaScript将eat
在cat对象上寻找方法。该查找将失败,然后由于Object.create,它将委派给将
animalMethods
在其中找到的对象eat
。
到现在为止还挺好。我们仍然可以做一些改进。似乎必须要管理一个单独的对象(animalMethods
)才能在实例之间共享方法,这只是一点点笨拙 。
这似乎是您希望在语言本身中实现的一项常用功能。
事实证明,这就是您在这里的全部原因- prototype(原型)
。
那么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 cat = Animal('Cat', 7) const dog = Animal('dog', 10) leo.eat(10) snoop.play(5)
同样,prototype
它只是JavaScript中每个函数都具有的属性,并且如我们上面所见,它允许我们在函数的所有实例之间共享方法。我们所有的功能仍然相同,但是现在不必为所有方法管理单独的对象,我们只需使用Animal
函数本身内置的另一个对象即可Animal.prototype
。
这篇就先讲这么多!