Answers:
基本区别在于,构造函数与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;
}
当然,您可以使工厂功能比该简单示例复杂得多。
工厂函数的一个优点是,根据某些参数,要返回的对象可以是几种不同的类型。
someMethod
工厂返回的对象,因此这里有些模糊。在工厂函数内部,如果只做一次var obj = { ... , someMethod: function() {}, ... }
,那将导致返回的每个对象都拥有一个不同的副本,someMethod
这是我们可能不想要的。那就是在工厂函数中使用new
和prototype
内部帮助的地方。
new
构造函数的地方。我认为这可能是需要了解如何用工厂函数示例替换构造函数的地方,而我认为示例之间的一致性是必需的。无论如何,答案是足够的。这只是我想提出的一点,而不是我以任何方式降低答案的质量。
new
内部使用,也可以Object.create()
用于创建具有特定原型的对象。
大多数书籍都教您使用构造函数和 new
this
指新对象
有些人喜欢阅读的方式var myFoo = new Foo();
。
实例化的细节(通过new
需求)泄漏到了调用API中,因此所有调用者都与构造函数实现紧密耦合。如果您需要工厂的额外灵活性,则必须重构所有调用方(公认的例外情况,而不是规则)。
忘记new
是一个常见的错误,您应该强烈考虑添加样板检查,以确保正确调用了构造函数(if (!(this instanceof Foo)) { return new Foo() }
)。编辑:自ES6(ES2015)起,您不能忘记new
使用class
构造函数,否则构造函数将引发错误。
如果您进行instanceof
检查,则对于是否new
需要进行检查会产生歧义。我认为不应该这样。您已经有效地缩短了new
要求,这意味着您可以消除缺点1。但是,除了name之外,您几乎只有一个工厂函数,带有额外的样板,大写字母和较不灵活的this
上下文。
但我主要担心的是,它违反了开放/封闭原则。您开始导出构造器,用户开始使用构造器,然后逐渐意识到需要工厂的灵活性,例如(将实现切换为使用对象池,或在执行上下文之间实例化,或者使用原型OO具有更大的继承灵活性)。
但是,您被困住了。如果不破坏调用构造函数的所有代码,就无法进行更改new
。例如,您不能切换到使用对象池来提高性能。
另外,使用构造函数会给您一个欺骗性的效果instanceof
,该欺骗性在跨执行上下文的情况下不起作用,并且如果您的构造函数原型被换出也不起作用。如果您开始返回,它也会失败this
从构造函数,然后切换到导出任意对象,则必须执行此操作才能在构造函数中启用类似工厂的行为,。
更少的代码-无需样板。
您可以返回任意对象,也可以使用任意原型-给您更大的灵活性来创建实现相同API的各种对象。例如,可以创建HTML5和Flash播放器实例的媒体播放器,或者可以发出DOM事件或Web套接字事件的事件库。工厂还可以跨执行上下文实例化对象,利用对象池,并允许更灵活的原型继承模型。
您将不需要从工厂转换为构造函数,因此重构永远不会成为问题。
使用毫不含糊new
。别。(它将使this
行为变差,请参阅下一点)。
this
表现为它通常会-所以你可以用它来访问父对象(例如,内部的player.create()
,this
指的是player
,就像任何其他方法将调用call
和apply
也重新分配this
,如预期如果您存储父对象上的原型,即。可以很好地动态交换功能,并为对象实例化启用非常灵活的多态性。
对于是否要大写没有歧义。别。棉绒工具会抱怨,然后您会尝试使用new
,然后您将撤消上述好处。
有些人喜欢阅读var myFoo = foo();
或var myFoo = foo.create();
阅读。
new
表现不符合预期(请参见上文)。解决方案:不要使用它。
this
不引用新对象(相反,如果使用点符号或方括号符号调用构造函数,例如foo.bar()- this
则与foo
其他JavaScript方法一样引用-请参见好处)。
new
违反了开放/封闭原则。参见medium.com/javascript-scene/…进行的讨论超出了这些注释所允许的范围。
new
关键字,所以我不认为该new
关键字确实可以提供任何其他可读性。海事组织,为了使呼叫者能够键入更多内容而跳入箍圈似乎很愚蠢。
构造函数返回您调用它的类的实例。工厂函数可以返回任何东西。当您需要返回任意值或类具有较大的设置过程时,可以使用工厂函数。
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
new
创建一个原型对象,User.prototype
并User
使用创建的对象作为其this
值进行调用。
new
将其操作数的自变量表达式视为可选:
let user = new User;
会导致new
调用User
不带任何参数。
new
返回其创建的对象,除非构造函数返回一个object value,而不是返回。这是一个边缘情况,在大多数情况下可以忽略。
构造函数创建的对象从构造函数的prototype
属性继承属性,并使用instanceOf
构造函数上的运算符返回true 。
如果您prototype
已经在使用构造函数之后动态更改了构造函数的属性值,则上述行为可能会失败。这样做很少见,如果使用class
关键字创建构造函数,则无法更改。
构造函数可以使用extends
关键字扩展。
构造函数不能null
作为错误值返回。由于它不是对象数据类型,因此将被忽略new
。
function User(name, age) {
return {
name,
age,
}
};
let user = User("Tom", 23);
这里的工厂函数不带 new
。如果函数的参数及其返回的对象类型,则该函数完全负责直接或间接使用。在此示例中,它返回一个简单的[Object object],其中包含通过参数设置的某些属性。
轻松向调用者隐藏对象创建的实现复杂性。这对于浏览器中的本机代码功能特别有用。
工厂函数不必总是返回相同类型的对象,甚至可以null
作为错误指示符返回。
在简单的情况下,工厂功能的结构和含义可能很简单。
返回的对象通常不会从工厂函数的prototype
属性继承,而false
从继承instanceOf factoryFunction
。
无法使用extends
关键字安全地扩展工厂函数,因为扩展对象将继承自工厂函数prototype
属性,而不是继承自工厂函数prototype
使用的构造函数的属性。
工厂“总是”更好。当使用面向对象的语言时
实现(使用new创建的实际对象)不会暴露给工厂用户/消费者。这意味着只要不违反合同,工厂开发人员就可以扩展和创建新的实现方式,这使工厂用户只需从新API中受益,而不必更改其代码...如果他们使用new并出现了“ new”实现,那么他们必须去更改每条使用“ new”的行以使用“ new”实现...在工厂中,其代码不会改变...
工厂-比其他所有工厂都要好-spring框架完全基于此思想构建的。
工厂是抽象的一层,就像所有抽象一样,它们也有一定的成本。遇到基于工厂的API时,弄清楚给定API的工厂对于API使用者可能是一个挑战。对于构造函数,可发现性是微不足道的。
在决策者和工厂之间做出决定时,您需要确定该好处是否证明了复杂性是合理的。
值得一提的是,通过返回除this或undefined以外的内容,Javascript构造函数可以是任意工厂。因此,在js中,您可以兼得两者-可发现的API和对象池/缓存。
new
,更改的行为this
,更改返回值,连接原型ref,启用instanceof
(存在且不应用于此目的)。表面上,所有这些都是“功能”。实际上,它们会损害您的代码质量。