构造函数与工厂函数
有人可以澄清在Javascript中的构造函数和工厂函数之间的区别。
什么时候使用一个而不是另一个?
基本的区别在于一个构造函数与new
关键字一起使用(这会导致JavaScript自动创build一个新的对象,在函数内设置this
对象并返回该对象):
var objFromConstructor = new ConstructorFunction();
工厂函数被称为“常规”函数:
var objFromFactory = factoryFunction();
但是对于它被认为是一个“工厂”,它将需要返回一个对象的新实例:如果它只是返回一个布尔值或其他东西,就不会将其称为“工厂”函数。 这不会像new
那样自动发生,但它确实在某些情况下允许更多的灵活性。
在一个非常简单的例子中,上面引用的函数可能看起来像这样:
function ConstructorFunction() { this.someProp1 = "1"; this.someProp2 = "2"; } ConstructorFunction.prototype.someMethod = function() { /* whatever */ }; function factoryFunction() { var obj = { someProp1 : "1", someProp2 : "2", someMethod: function() { /* whatever */ } }; // other code to manipulate obj in some way here return obj; }
当然,你可以使工厂function比这个简单的例子复杂得多。
有些人更喜欢使用工厂函数的一切,只是因为他们不喜欢不得不记得使用new
(编辑:这可能是一个问题,因为没有new
的function将仍然运行,但不是预期的)。 我不认为这是一个优势: new
是语言的核心部分,所以我故意避免它是有点武断 – 可能会避免其他关键字。
工厂函数的一个优点是,根据某些参数,要返回的对象可能有几种不同的types。
使用构造函数的好处
-
大多数书籍教你使用build设者和
new
-
this
是指新的对象 -
有些人喜欢
var myFoo = new Foo();
读取。
缺点
-
实例化的细节被泄漏到调用API中(通过
new
需求),所以所有的调用者都紧紧地和构造器实现耦合。 如果你需要工厂的额外的灵活性,你将不得不重构所有的呼叫者(承认是例外,而不是规则)。 -
忘记
new
就是这样一个常见的bug,你应该强烈考虑添加一个样板检查来确保正确调用构造函数(if (!(this instanceof Foo)) { return new Foo() }
)。 编辑:由于ES6(ES2015)你不能忘记new
的class
构造函数,或者构造函数会抛出一个错误。 -
如果你做了
instanceof
检查,那么对于是否需要new
来说就不明确了。 在我看来,这不应该是。 你已经有效地短路了new
要求,这意味着你可以消除#1的缺点。 但是你刚刚得到了一个工厂函数,除了名字外 ,还有一个大写字母,大写字母,而且this
上下文不太灵活。
构造函数违反开放/closures原则
但我主要关心的是违反了开放/封闭的原则。 您开始导出构造函数,用户开始使用构造函数,然后沿着您意识到的需要工厂的灵活性,而不是(例如,将实现切换为使用对象池,或跨执行上下文实例化,或者使用原型OO具有更多的inheritance灵活性)。
不过你被困住了。 在不破坏所有调用构造函数的代码的情况下,您无法进行更改。 例如,您不能切换到使用对象池来提高性能。
另外,使用构造函数会给你一个欺骗性的instanceof
,它不能跨越执行上下文,而且如果你的构造函数原型被换出,那么它就不起作用。 如果你从你的构造函数开始返回this
,那么它也会失败,然后切换到导出一个任意的对象,你必须要在你的构造函数中启用类似工厂的行为。
使用工厂的好处
-
代码较less – 不需要样板文件。
-
你可以返回任意的对象,并使用任意的原型 – 给你更多的灵活性来创build实现相同API的各种types的对象。 例如,可以创buildHTML5和Flash播放器实例的媒体播放器,或者可以发出DOM事件或Web套接字事件的事件库。 工厂还可以在执行上下文中实例化对象,利用对象池,并允许更灵活的原型inheritance模型。
-
你永远不需要从工厂转换到构造函数,所以重构永远不会是一个问题。
-
没有使用
new
歧义。 别。 (这会使this
行为不好,看下一点)。 -
this
就像通常那样运行 – 所以你可以使用它来访问父对象(例如,在player.create()
内部player.create()
,this
就像player
,就像任何其他方法调用一样,call
和apply
也可以重新分配this
如果将原型存储在父对象上,那么这可能是dynamicreplacefunction的好方法,并且可以为对象实例化启用非常灵活的多态。 -
关于是否大写,没有歧义。 别。 皮特工具会抱怨,然后你会试图尝试使用
new
,然后你会取消上述的好处。 -
有些人喜欢
var myFoo = foo();
或var myFoo = foo.create();
读取。
缺点
-
new
行为不如预期(见上文)。 解决scheme:不要使用它。 -
this
不会引用新的对象(相反,如果构造函数是用点符号或方括号表示来调用的,例如foo.bar() –this
指的是foo
– 就像其他JavaScript方法一样)。
构造函数返回你调用它的类的一个实例。 工厂函数可以返回任何东西。 当你需要返回任意值或者当一个类有一个大的设置过程时,你会使用一个工厂函数。
工厂“总是”更好。 当使用面向对象的语言时
- 决定合同(方法和他们会做什么)
- 创build公开这些方法的接口(在JavaScript中你没有接口,所以你需要想出一些方法来检查实现)
- 创build一个返回所需每个接口的实现的工厂。
实现(用新创build的实际对象)不会暴露给工厂用户/用户。 这意味着工厂开发人员可以扩展并创build新的实现,只要他/她不违反合同……并且允许工厂消费者从新的API中受益,而不必更改他们的代码。如果他们使用新的和“新”的实现出现,那么他们必须去改变每一个使用“新”的行来使用“新”的实现…与工厂他们的代码不会改变…
工厂 – 比其他所有东西都要好 – spring框架完全是围绕这个想法而build立的。
工厂是一个抽象层,像所有的抽象一样,它们复杂性很高。 当遇到基于工厂的API时,找出给定API的工厂对于API消费者来说可能具有挑战性。 使用构造函数的可发现性是微不足道的。
在决定工厂和工厂时,您需要确定复杂性是否合理。
值得注意的是,JavaScript的构造函数可以是任意的工厂通过返回以外的东西或未定义。 所以在JS你可以得到两全其美 – 发现的API和对象池/caching。