构造函数与工厂函数
Constructor function vs Factory functions
谁能解释一下Javascript中构造函数和工厂函数的区别?
什么时候用一个代替另一个?
基本区别是构造函数与new
关键字一起使用(这会导致JavaScript自动创建一个新对象,将函数中的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;
}
当然,你可以使工厂函数比这个简单的例子复杂得多。
工厂函数的一个优点是,根据参数的不同,返回的对象可以是几种不同的类型。
使用构造函数的好处
-
大多数书教你使用构造函数和
new
-
this
为新对象 -
有些人喜欢
var myFoo = new Foo();
的阅读方式。
实例化的细节被泄露到调用API中(通过new
要求),因此所有调用者都与构造函数实现紧密耦合。如果您需要工厂的额外灵活性,则必须重构所有调用者(当然是例外情况,而不是规则)。
忘记new
是如此常见的错误,您应该强烈考虑添加一个样板检查,以确保正确调用构造函数(if (!(this instanceof Foo)) { return new Foo() }
)。编辑:从ES6 (ES2015)开始,你不能忘记new
与class
构造函数,否则构造函数会抛出错误。
如果你做instanceof
检查,它留下模糊是否需要new
。在我看来,这是不应该的。您已经有效地缩短了new
要求,这意味着您可以消除缺点#1。但是,在中,除了名称之外,您只是得到了一个工厂函数,带有额外的样板,大写字母和不太灵活的this
上下文。
构造函数打破了开/闭原则
但是我主要担心的是它违反了开/闭原则。您开始导出构造函数,用户开始使用构造函数,然后您意识到您需要工厂的灵活性,而不是(例如,切换实现以使用对象池,或跨执行上下文实例化,或使用原型OO获得更多继承灵活性)。
你被困住了。如果不破坏使用new
调用构造函数的所有代码,就无法进行更改。例如,您不能为了提高性能而切换到使用对象池。
此外,使用构造函数会给你一个欺骗性的instanceof
,它不能在执行上下文中工作,如果你的构造函数原型被交换了,它也不能工作。如果您开始从构造函数返回this
,然后切换到导出任意对象,那么它也会失败,您必须这样做才能在构造函数中启用类似工厂的行为。
使用工厂的好处
代码少-不需要样板
你可以返回任意对象,并使用任意原型-给你更大的灵活性来创建实现相同API的各种类型的对象。例如,可以创建HTML5和flash播放器实例的媒体播放器,或者可以发出DOM事件或web套接字事件的事件库。工厂还可以跨执行上下文实例化对象,利用对象池,并允许更灵活的原型继承模型。
你永远不需要从工厂转换为构造函数,所以重构永远不会成为一个问题。
使用
new
没有歧义。不喜欢。(它会使this
表现不好,见下一点)。this
的行为与正常情况一样-因此您可以使用它来访问父对象(例如,在player.create()
中,this
引用player
,就像任何其他方法调用一样)。call
和apply
也像预期的那样重新赋值this
。如果您将原型存储在父对象上,那么这将是动态交换功能的好方法,并为您的对象实例化启用非常灵活的多态性。对于是否大写没有歧义。不喜欢。Lint工具会抱怨,然后你会尝试使用
new
,然后你会撤销上面描述的好处。有些人喜欢
var myFoo = foo();
或var myFoo = foo.create();
的阅读方式。
new
的行为不符合预期(见上文)。解决方法:不要使用
this
不指向新对象(相反,如果构造函数是用点表示法或方括号表示法调用的,例如foo.bar() - this
指向foo
-就像其他JavaScript方法一样-参见好处)。
构造函数返回调用它的类的实例。工厂函数可以返回任何值。当需要返回任意值或类具有大型设置过程时,可以使用工厂函数。
构造函数示例
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
new
在User.prototype
上创建一个原型对象,并使用创建的对象作为this
值调用User
。new
将其操作数的参数表达式视为可选的:let user = new User;
将导致
new
不带参数地调用User
。new
返回它创建的对象,除非构造函数返回一个对象值,而不是返回。这是一个边缘情况,大多数情况下可以忽略。
利与弊
由构造函数创建的对象继承构造函数的prototype
属性,并在构造函数上使用instanceOf
操作符返回true。
prototype
属性的值,则上述行为可能会失败。这样做是罕见的,如果构造函数是使用class
关键字创建的,则不能更改。
构造函数可以使用extends
关键字进行扩展。
构造函数不能返回null
作为错误值。因为它不是对象数据类型,所以被new
忽略。
一个Factory函数示例
function User(name, age) {
return {
name,
age,
}
};
let user = User("Tom", 23);
这里调用的工厂函数没有new
。函数对其参数及其返回的对象类型的直接或间接使用完全负责。在这个例子中,它返回一个简单的[Object Object],并从参数中设置一些属性。
利与弊
很容易对调用者隐藏对象创建的实现复杂性。这对于浏览器中的本机代码函数特别有用。
工厂函数不需要总是返回相同类型的对象,甚至可以返回null
作为错误指示符。
在简单的情况下,工厂函数在结构和意义上可以是简单的。
返回的对象一般不继承工厂函数的prototype
属性,而从instanceOf factoryFunction
返回false
。
不能使用extends
关键字安全地扩展工厂函数,因为扩展对象将继承工厂函数的prototype
属性,而不是工厂函数使用的构造函数的prototype
属性。
工厂"总是"更好。当使用面向对象语言时,
- 决定合同(方法和他们将做什么)
- 创建暴露这些方法的接口(在javascript中你没有接口,所以你需要想出一些检查实现的方法)
- 创建一个工厂,返回所需的每个接口的实现。
实现(用new创建的实际对象)不公开给工厂用户/消费者。这意味着工厂开发人员可以扩展和创建新的实现,只要他/她不破坏合同……而且它允许工厂消费者从新的API中受益,而不必改变他们的代码……如果他们使用了"new",然后出现了"新"实现,那么他们必须去修改每一行使用"new"的代码,以使用"新"实现……对于工厂,它们的代码不会改变…
工厂——比其他任何东西都好——spring框架完全是围绕这个想法构建的。
工厂是一个抽象层,像所有的抽象一样,它们在复杂性上是有代价的。当遇到基于工厂的API时,找出给定API的工厂是什么对API消费者来说是一个挑战。对于构造函数,可发现性是微不足道的。
当你在actor和factory之间做决定时,你需要决定它们的复杂性是否值得。
值得注意的是,Javascript构造函数可以是任意工厂,返回的不是this或undefined。所以在js中,你可以两全其美——可发现API和对象池/缓存。
-
我认为工厂函数优于构造函数。使用
new
和构造函数,我们将代码绑定到创建对象的一种特定方式,而使用工厂,我们可以自由地创建更多不同的实例,而无需绑定自己。假设我们有这样一个类:const file = new CreateFile(name)
如果我们想重构CreateFile类,为服务器支持的文件格式创建子类,我们可以编写一个简洁的工厂函数:
function CreateFile(name) {
if (name.match(/'.pdf$/)) {
return new FilePdf(name);
} else if (name.match(/'.txt$/)) {
return new FileTxt(name);
} else if (name.match(/'.md$/)) {
return new FileMd(name);
} else {
throw new Error("Not supprted file type");
}
}
使用工厂函数,我们可以实现私有变量,对用户隐藏信息,这就是
encapsulation
。function createPerson(name) { const privateInfo = {}; // we create person object const person = { setName(name) { if (!name) { throw new Error("A person must have a name"); } privateInfo.name = name; }, getName() { return privateInfo.name; }, }; person.setName(name); return person; }
对于差异,Eric Elliott解释得很清楚,
但是对于第二个问题:
什么时候用一个代替另一个?
如果你有面向对象的背景,构造函数对你来说看起来更自然。这样你就不会忘记使用new
关键字。
- Angular,函数在(模型)工厂中返回值
- 返回一个新函数,而不是工厂中的对象
- 为什么针对工厂的Angular数据绑定只适用于函数
- Jasmine测试工厂函数调用本地函数和另一个函数-获取错误:应该是间谍,但得到了function
- 在同一工厂中的函数中使用函数并传递$scope(AngularJS)
- 我是否需要将工厂调用放在控制器的函数中
- 如何从工厂传递的函数返回值
- Angular,设置一个回调函数,用于在工厂和控制器之间更新
- 在angularjs工厂中,如何在函数解析后返回结果
- 为什么我没有从angularJS中的这个工厂函数中得到响应
- 对象属性不是在工厂成员函数内部创建的
- 从函数内部修改角度工厂对象
- 工厂函数给出“;错误:未定义
- AngularJS-控制器和工厂-我在include/inject函数方面哪里出了问题
- 在工厂构造函数中解析 Restangular.get
- AngularJS:undefined不是工厂的函数
- 什么是jQuery中的工厂函数
- Javascript函数工厂嵌套在一个即时函数内嵌套jQuery准备好了
- 无法从JSON字符串实例化类型的值;没有单字符串构造函数/工厂方法
- Javascript 函数工厂代码需要理解