构造函数与工厂函数

IT技术 javascript oop
2021-01-27 01:23:59

有人可以澄清 Javascript 中构造函数和工厂函数之间的区别。

什么时候使用一个而不是另一个?

6个回答

基本的区别在于构造函数与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;
}

当然,您可以使工厂函数比那个简单的例子复杂得多。

工厂函数的一个优点是当要返回的对象根据某些参数可以是几种不同的类型时。

@Federico - 工厂方法不必只返回一个普通对象。它们可以在new内部使用,也可以Object.create()用于创建具有特定原型的对象。
2021-03-23 01:23:59
我想指出这两种情况(构造函数与工厂函数)的示例应该是一致的。工厂函数的示例不包括someMethod工厂返回的对象,这就是它有点模糊的地方。在工厂函数内部,如果只是这样做var obj = { ... , someMethod: function() {}, ... },那将导致返回的每个对象都持有不同的副本,someMethod而这些副本可能是我们不想要的。这就是在工厂函数中使用newprototype内部会有所帮助的地方。
2021-03-29 01:23:59
“(编辑:这可能是一个问题,因为没有新的功能仍然会运行,但不像预期的那样)。” 如果您尝试使用“new”调用工厂函数或尝试使用“this”关键字分配给实例,这只是一个问题。否则,您只需创建一个新的任意对象,然后返回它。没问题,只是一种不同的、更灵活的做事方式,更少的样板,并且不会将实例化细节泄漏到 API 中。
2021-04-01 01:23:59
对我来说,Factory 函数的最大优点是可以获得更好的封装数据隐藏,这在某些应用程序中可能很有用。如果让每个实例属性和方法都公开并且用户可以轻松修改没有问题,那么我想构造函数更合适,除非您像某些人那样不喜欢“new”关键字。
2021-04-07 01:23:59
正如您已经提到的,有些人尝试使用工厂函数只是因为他们不打算留下人们忘记使用new构造函数的错误;我认为这是人们可能需要了解如何用工厂函数替换构造函数示例的地方,这就是我认为需要示例中的一致性的地方。无论如何,答案是足够的信息。这只是我想提出的一点,并不是说我在以任何方式降低答案的质量。
2021-04-09 01:23:59

使用构造函数的好处

  • 大多数书籍教你使用构造函数和 new

  • this 引用新对象

  • 有些人喜欢阅读的方式var myFoo = new Foo();

缺点

  • 实例化的细节会泄露到调用 API 中(通过new需求),因此所有调用者都与构造函数实现紧密耦合。如果您需要工厂的额外灵活性,您将不得不重构所有调用者(不可否认的是例外情况,而不是规则)。

  • 遗忘new是如此常见的错误,您应该强烈考虑添加样板检查以确保正确调用构造函数 ( if (!(this instanceof Foo)) { return new Foo() })。编辑:既然ES6(ES2015),你可别忘了newclass构造函数或构造函数将抛出一个错误。

  • 如果您进行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 方法一样 - 见好处)。

DI 适用于注入状态:配置、域对象等。这对其他一切都太过分了。
2021-03-13 01:23:59
因为任何函数都可以在 JavaScript 中返回一个新对象,而且很多函数都可以在没有new关键字的情况下返回,所以我认为new关键字实际上并没有提供任何额外的可读性。IMO,为了使呼叫者能够输入更多内容而跳过箍似乎很愚蠢。
2021-03-18 01:23:59
大惊小怪的是,要求new违反了开放/封闭原则。请参阅medium.com/javascript-scene/...进行比这些评论所允许的更大的讨论。
2021-03-29 01:23:59
在什么意义上,构造函数使调用者与其实现紧密耦合?就构造函数参数而言,这些参数甚至需要传递给工厂函数才能使用它们并在其中调用适当的构造函数。
2021-03-31 01:23:59
关于违反 Open/Closed:这不就是依赖注入吗?如果A需要B,无论是A调用new B()还是A调用BFactory.create(),都会引入耦合。另一方面,如果您在组合根中给 A 一个 B 的实例,则 A 根本不需要知道有关 B 是如何实例化的任何信息。我觉得构造函数和工厂都有它们的用途;构造函数用于简单的实例化,工厂用于更复杂的实例化。但在这两种情况下,注入您的依赖项是明智的。
2021-04-02 01:23:59

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

构造函数示例

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • new创建一个原型对象User.prototypeUser使用创建的对象作为其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属性,而是falseinstanceOf factoryFunction.

不能使用extends关键字安全地扩展工厂函数,因为扩展对象将从工厂函数prototype属性而不是从prototype工厂函数使用的构造函数的属性继承

不仅仅是“null”而且“new”也将忽略构造函数返回的任何前置数据类型。
2021-03-22 01:23:59
这是针对同一主题的此问题发布的迟到答案
2021-03-24 01:23:59

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

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

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

工厂 - 比其他任何东西都好 - spring 框架完全围绕这个想法构建。

工厂如何解决每条线都要换的问题?
2021-03-11 01:23:59