构造函数与工厂函数

有人可以澄清在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)你不能忘记newclass构造函数,或者构造函数会抛出一个错误。

  • 如果你做了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 ,就像任何其他方法调用一样, callapply也可以重新分配this如果将原型存储在父对象上,那么这可能是dynamicreplacefunction的好方法,并且可以为对象实例化启用非常灵活的多态。

  • 关于是否大写,没有歧义。 别。 皮特工具会抱怨,然后你会试图尝试使用new ,然后你会取消上述的好处。

  • 有些人喜欢var myFoo = foo();var myFoo = foo.create(); 读取。

缺点

  • new行为不如预期(见上文)。 解决scheme:不要使用它。

  • this不会引用新的对象(相反,如果构造函数是用点符号或方括号表示来调用的,例如foo.bar() – this指的是foo – 就像其他JavaScript方法一样)。

构造函数返回你调用它的类的一个实例。 工厂函数可以返回任何东西。 当你需要返回任意值或者当一个类有一个大的设置过程时,你会使用一个工厂函数。

工厂“总是”更好。 当使用面向对象的语言时

  1. 决定合同(方法和他们会做什么)
  2. 创build公开这些方法的接口(在JavaScript中你没有接口,所以你需要想出一些方法来检查实现)
  3. 创build一个返回所需每个接口的实现的工厂。

实现(用新创build的实际对象)不会暴露给工厂用户/用户。 这意味着工厂开发人员可以扩展并创build新的实现,只要他/她不违反合同……并且允许工厂消费者从新的API中受益,而不必更改他们的代码。如果他们使用新的和“新”的实现出现,那么他们必须去改变每一个使用“新”的行来使用“新”的实现…与工厂他们的代码不会改变…

工厂 – 比其他所有东西都要好 – spring框架完全是围绕这个想法而build立的。

工厂是一个抽象层,像所有的抽象一样,它们复杂性很高。 当遇到基于工厂的API时,找出给定API的工厂对于API消费者来说可能具有挑战性。 使用构造函数的可发现性是微不足道的。

在决定工厂和工厂时,您需要确定复杂性是否合理。

值得注意的是,JavaScript的构造函数可以是任意的工厂通过返回以外的东西或未定义。 所以在JS你可以得到两全其美 – 发现的API和对象池/caching。