Comparison and principles of the four most common patterns
四种最常见模式的比较和原理
If you started learning programming with a traditional class-based language, JavaScript’s Object Oriented Programming might look confusing at first. You will find a lot of articles on different object creation patterns which might be a bit overwhelming. In this article, we will analyze 4 common object creation patterns. I will start with a brief review of some of the underlying concepts/features. Then, I will examine some code samples and discuss how each pattern works using some visual aid.
如果您开始使用传统的基于类的语言学习编程,那么JavaScript的面向对象编程一开始可能会令人困惑。 您会发现很多关于不同对象创建模式的文章,这些文章可能有点让人难以理解。 在本文中,我们将分析4种常见的对象创建模式。 我将首先简要回顾一些基本概念/功能。 然后,我将检查一些代码示例,并讨论如何使用视觉辅助来实现每个模式。
If you are a Launch School student and studying OOP JavaScript as part of JS225 or JS120, and preparing towards the assessment, this article would be a good read for you. The aim of this article is to build up how each object creation pattern implements prototypal inheritance in steps and solidify your mental model. Let’s start with some background.
如果您是入门学校的学生,并且正在学习作为JS225或JS120一部分的OOP JavaScript,并准备进行评估,那么这篇文章对您来说是一本好书。 本文的目的是建立每个对象创建模式如何逐步实现原型继承并巩固您的思维模型。 让我们从一些背景开始。
JavaScript的继承 (Inheritance in JavaScript)
The term inheritance in OOP, commonly refers to class-based languages where there is a sub-class/super-class relationship, being sub-classes inherit behavior and data from super-classes.
OOP中的“ 继承 ”一词通常是指存在子类/超类关系的基于类的语言,因为子类从超类继承行为和数据。
JavaScript does not have true-inheritance as we know it from class-based languages. Instead, it uses something called prototypal inheritance. When we create an object, say child
from another object called parent
, child
references it's properties and methods to parent
object's prototype. This way it can delegate an access request up in the prototype chain which is why we call this behavior delegation.
正如我们从基于类的语言中了解到的那样,JavaScript没有真正的继承。 相反,它使用一种称为原型继承的东西。 当我们创建一个对象时,从另一个称为parent
的对象说child
, child
将其属性和方法引用到parent
对象的prototype 。 这样,它可以在原型链中向上委派一个访问请求,这就是为什么我们称这种行为委派 。
![Image for post](https://img-service.csdnimg.cn/img_convert/a4550bda9199f199ec9224b60095d34a.png)
类属性与函数属性 (Class Properties vs. Function Properties)
![Image for post](https://img-service.csdnimg.cn/img_convert/11ba528abd4bbfca7459b4718838cca6.png)
In JavaScript,
prototype
property only exists in Functions. It is basically an object that Function returns when invoked as a constructor with thenew
keyword.在JavaScript中,
prototype
属性仅存在于Functions中。 从本质上讲,它是使用new
关键字作为构造函数调用时函数返回的对象。[[Prototype]]
is a property that all objects contain. This property points to prototype object that the initial object created from.[[Prototype]]
是所有对象都包含的属性。 此属性指向创建初始对象的原型对象。
Let’s look at the above diagram to see what happens when we create an empty function vs an empty object.
让我们看一下上面的图,看看当我们创建一个空函数与一个空对象时会发生什么。
On the left, we have the Sample
function. When we create a function, it initially inherits from Function.prototype
which is referenced to it's [[Prototype]]
property. Functions also contain a prototype
property that contains a prototype object. This object contains a property called constructor
that refers back to the function. We will make use of constructor
later.
在左侧,我们具有Sample
功能。 当我们创建一个函数时,它最初是从Function.prototype
继承的,该Function.prototype
引用了它的[[Prototype]]
属性。 函数还包含一个prototype
属性,该属性包含一个原型对象。 该对象包含一个称为constructor
的属性,该属性返回该函数。 稍后我们将使用constructor
。
On the right, we have our object Sample
with [[Prototype]]
property. The value that [[Prototype]]
points to depends on how the object is created. If it is created using the object literal syntax it will reference to Object.prototype
. If the object is created by a constructor function, it will be referencing to prototype
property of that function.
在右侧,我们的对象Sample
具有[[Prototype]]
属性。 [[Prototype]]
指向的值取决于如何创建对象。 如果使用对象文字语法创建它,则将引用Object.prototype
。 如果对象是由构造函数创建的 ,则它将引用该函数的prototype
属性。
通过函数创建对象 (Creating Objects From Functions)
Lastly, let’s remember what happens when we invoke a constructor function with new
keyword. Let's say we have a construction function ConstFunc
and we have a line of code like this: let newObj = new ConstFunc(arg1, arg2);
最后,让我们记住使用new
关键字调用构造函数时发生的情况。 假设我们有一个构造函数ConstFunc
并且我们有这样的代码行: let newObj = new ConstFunc(arg1, arg2);
When invoked, constructor function
ConstFunc
creates a new object调用时,构造函数
ConstFunc
创建一个新对象this
(execution context) assigned to this new object as part of the function call.this
(执行上下文)分配给此新对象,作为函数调用的一部分。ConstFunc
is executed in this context with passed in arguments.ConstFunc
在此上下文中使用传入的参数执行。If there is no explicit
return
statement, the value ofthis
will be returned.如果没有显式的
return
语句,则将返回this
值。
The created object will have the below relationship with the constructor function
创建的对象将与构造函数具有以下关系
newObj.constructor === [ Function: ConstFunc ]
newObj.constructor === [ Function: ConstFunc ]
ConstFunc.prototype === ConstFunc {}
ConstFunc.prototype === ConstFunc {}
newObj.constructor.prototype === ConstFunc {}
newObj.constructor.prototype === ConstFunc {}
Now we covered all the basics, the rest will be quite straightforward.
现在我们涵盖了所有基础知识,其余的将非常简单。
构造函数 (Constructor Functions)
Previously, we’ve discussed how constructor functions work while we explained how the new
keyword works. The key takeaway from this pattern is Album.prototype
. It is the prototype object for constructor function's return value.
之前,我们讨论了构造函数的工作原理,同时还解释了new
关键字的工作原理。 该模式的关键之处在于Album.prototype
。 它是构造函数的返回值的原型对象。
![Image for post](https://img-service.csdnimg.cn/img_convert/8f1da275ac40d3207cabe6231316e714.png)
When we created the new objects using the constructor function Album
, all shared methods are copied into instances of Album.prototype
. Therefore, although [[Prototype]]
property of the new objects point to Album.prototype
, they will still use their own copy of the methods.
使用构造函数Album
创建新对象时,所有共享方法都将复制到Album.prototype
实例中。 因此,尽管新对象的[[Prototype]]
属性指向Album.prototype
,但它们仍将使用它们自己的方法副本。
工厂模式 (Factory Pattern)
Factory pattern makes use of a function that returns an object when invoked. Above example makes use of the object literal syntax for object creation, but other ways such as new Object()
can be also used.
工厂模式利用一个函数,该函数在调用时返回一个对象。 上面的示例将对象文字语法用于对象创建,但也可以使用其他方式,例如new Object()
。
![Image for post](https://img-service.csdnimg.cn/img_convert/ead77deae575ce529009fab4f0e75c30.png)
Factory pattern is computationally inefficient since all the methods will be copied to every single object that is returned. Unlike constructor functions it is not possible to track down how these objects are created, or how they are related to makeAlbum
function. Also, it is not possible to update the "shared" behavior once the objects are returned by the factory function.
工厂模式的计算效率低下,因为所有方法都将复制到返回的每个对象中。 与构造函数不同 ,无法跟踪这些对象的创建方式或它们与makeAlbum
函数的关系。 同样,一旦工厂函数返回了对象,就不可能更新“共享”行为。
伪古典模式 (Pseudo-Classical Pattern)
Pseudo-classical pattern combines constructor function and prototype pattern. This popular pattern resolves the problem of inefficiencies that have been discussed on the previous two patterns and introduces prototypal inheritance. Pseudo-classical pattern achieves this by creating a distinction between private properties and shared properties/methods. These are separated into a constructor function and the constructor function’s prototype.
伪古典模式结合了构造函数和原型模式。 这种流行的模式解决了前两种模式所讨论的效率低下的问题,并引入了原型继承 。 伪古典模式通过在私有属性和共享属性/方法之间建立区别来实现此目的。 它们分为构造函数和构造函数的prototype 。
![Image for post](https://img-service.csdnimg.cn/img_convert/e72d574b05cd4a0dd0b88638f418de84.png)
JavaScript class
is introduced with ES6. Essentially, this does the same thing as the pseudo-classical model, it is just a syntactic sugar. It improves the organization of the code and provides a constructor method.
ES6引入了JavaScript class
。 本质上,这与伪古典模型具有相同的作用,只是语法上的糖 。 它改善了代码的组织并提供了构造方法。
Lastly, The Pseudo classical pattern can be combined in the constructor function:
最后,可以在构造函数中组合伪经典模式:
The if
statement checks if the "to be created" object has already a property called readTag
or type
. Since inAbsentia
is the first object that has been created by Album
constructor, this will evaluate true
and the shared behavior will be defined on the object's prototype. When we create other objects with the same constructor function, this block of code will be skipped by the if
statement.
if
语句检查“要创建”对象是否已经具有称为readTag
或type
的属性。 由于inAbsentia
是Album
构造函数创建的第一个对象,因此它将评估为true
,并且共享行为将在对象的原型上定义。 当我们使用相同的构造函数创建其他对象时, if
语句将跳过此代码块。
![Image for post](https://img-service.csdnimg.cn/img_convert/4fb2ee30d51df3795c40e7b26e936bb4.png)
OLOO(对象链接到其他对象) (OLOO(Object Linking to Other Object))
OLOO, is an object creation pattern based on creating new objects with Object.create
method using prototype objects. It is a relatively simpler pattern since it is not dealing with constructors and prototype properties.
OLOO是一种对象创建模式,它基于使用原型对象的 Object.create
方法 创建新 对象 。 这是一种相对简单的模式,因为它不处理构造函数和原型属性。
With OLOO, object creation and initialization occur at different times. The latter can be done by using an optional init
method as shown above.
使用OLOO, 对象创建和初始化发生在不同的时间。 后者可以通过使用可选的init
方法完成,如上所示。
Because OLOO Pattern does not use constructors, examining inheritance with methods like Object.prototype.constructor
will not work as expected and simply return Object.prototype
(the top element in the prototypal inheritance hierarchy). Instead, isPrototypeOf
and Object.getPrototypeOf
could be used for such purpose. Also, we can not use instanceof
because the right-hand operand has to be a function.
因为OLOO Pattern不使用构造函数,所以使用Object.prototype.constructor
方法检查继承将无法按预期方式工作,而是仅返回Object.prototype
(原型继承层次结构中的顶级元素)。 而是可以将isPrototypeOf
和Object.getPrototypeOf
用于此目的。 同样,我们不能使用instanceof
因为右侧操作数必须是一个函数。
Above inAbsentia
and unknowAlbum
have different properties, but they share the same methods as Album
.
以上的inAbsentia
和unknowAlbum
具有不同的属性,但它们与Album
具有相同的方法。
![Image for post](https://img-service.csdnimg.cn/img_convert/ab5cb573bfa48aba2346886fcf1051fa.png)
This approach provides behavior delegation rather than copying of all methods at object creation time. Therefore, when we add a new method check
into prototype object and call this method on the already created instance inAbsentia
, this method call will be delegated to its prototype.
这种方法提供行为委托,而不是在对象创建时复制所有方法 。 因此,当我们向原型对象中添加新方法check
并在已经创建的实例inAbsentia
调用此方法时,此方法调用将委派给其原型。
TL;DR
TL; DR
There are a lot of good articles on object creation patterns in JavaScript. In this article, I’ve tried to focus on the underlying principles first.
关于JavaScript中的对象创建模式 ,有很多不错的文章。 在本文中,我首先尝试着重于基本原理。
[[Prototype]]
is a hidden property contained in all objects. It is referenced to initial object's prototype object.[[Prototype]]
是所有对象中包含的隐藏属性。 它被引用到初始对象的原型对象。prototype
is a functions prototype object that it returns when invoked withnew
keyword.prototype
是一个函数原型对象,当使用new
关键字调用时会返回该对象。When an object is created invoking a constructor function the returned object has a
constructor
property that points to constructor function.创建调用构造函数的对象时,返回的对象具有指向构造函数的
constructor
属性。
At the second part I’ve discussed and compared 4 common object creation patterns with code samples and diagrams. Some key takeaways:
在第二部分中,我讨论了4种常见的对象创建模式,并将它们与代码示例和图表进行了比较。 一些要点:
Factory Pattern gathers object creation functionality in a single function, prevents repetition (more on DRY. Each function invocation creates a new object. It is inefficient, hard to trace the sources of the objects. Objects have a copy of their own methods.
Factory Pattern在单个函数中收集对象创建功能,防止重复( 有关DRY的更多信息 。每个函数调用都会创建一个新对象。效率低下,难以跟踪对象的来源。对象具有自己方法的副本。
Constructor Function, takes one step further from factory pattern by introducing a fake prototypal inheritance. Created object will still have their own copy of methods, but their source can be traced by using
constructor
property on the instances.构造函数,通过引入伪造的原型继承,比工厂模式更进一步。 创建的对象仍将具有其自己的方法副本 ,但可以通过在实例上使用
constructor
属性来跟踪其源。Pseudo-Classical Pattern is a combination of constructor function and prototype pattern. It resolves the above inefficiencies by introducing prototypal inheritance. It achieves this by separating private properties and shared properties into constructor function and it’s prototype.
伪经典模式是构造函数和原型模式的组合。 它通过引入原型继承来解决上述低效率问题。 它通过将私有属性和共享属性分成构造函数和它的原型来实现。
OLOO(Object Linking to Other Object), is a simpler solution that uses a prototype object instead of a constructor function. It relies of creating new object using
Object.create
with prototype object passed in as argument.OLOO(对象链接到其他对象)是一种使用原型对象而不是构造函数的更简单解决方案。 它依赖于使用
Object.create
创建新对象,并将原型对象作为参数传入。
We now know all these patterns but which is “the best”? My answer would be It depends… The latter two are better practices when it comes to building your code organization. However, the former two are also important to understand the principals behind prototypal inheritance and can save you some time when dealing with smaller problems.
现在我们知道所有这些模式,但是哪个是“ 最好的” ? 我的答案是……这取决于……在构建代码组织时,后两种是更好的做法。 但是,前两个对理解原型继承的原理也很重要,在处理较小的问题时可以节省一些时间。