有人可以澄清 Javascript 中构造函数和工厂函数之间的区别。
什么时候使用一个而不是另一个?
有人可以澄清 Javascript 中构造函数和工厂函数之间的区别。
什么时候使用一个而不是另一个?
基本的区别在于构造函数与new
关键字一起使用(这会导致 JavaScript 自动创建一个新对象,this
在函数内设置为该对象,并返回该对象):
var objFromConstructor = new ConstructorFunction();
工厂函数被称为“常规”函数:
var objFromFactory = factoryFunction();
但是要使其被视为“工厂”,它需要返回某个对象的新实例:如果它只是返回一个布尔值或其他东西,您就不会将其称为“工厂”函数。这不会像 with 那样自动发生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。但是,除了name之外,您只有一个工厂函数,带有额外的样板、大写字母和不太灵活的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
并User
使用创建的对象作为其this
值进行调用。
new
将其操作数的参数表达式视为可选:
let user = new User;
会导致new
调用User
不带任何参数。
new
返回它创建的对象,除非构造函数返回一个对象 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”的每一行以使用“新”实现......在工厂中,他们的代码不会改变......
工厂 - 比其他任何东西都好 - spring 框架完全围绕这个想法构建。