构造函数vs工厂函数

Sin*_*nan 138 javascript oop

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

何时使用一个而不是另一个?

nnn*_*nnn 143

基本区别在于构造函数与new关键字一起使用(这会导致JavaScript自动创建一个新对象,this在该对象的函数内设置,并返回该对象):

var objFromConstructor = new ConstructorFunction();
Run Code Online (Sandbox Code Playgroud)

工厂函数被称为"常规"函数:

var objFromFactory = factoryFunction();
Run Code Online (Sandbox Code Playgroud)

但是要将它视为"工厂",它需要返回某个对象的新实例:如果它只返回一个布尔值或其他东西,则不会将其称为"工厂"函数.这不会像自动一样自动发生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;
}
Run Code Online (Sandbox Code Playgroud)

当然,你可以使工厂功能比这个简单的例子复杂得多.

有些人喜欢将工厂功能用于所有事情,因为他们不喜欢不记得使用new(编辑:这可能是一个问题,因为没有new该功能仍将运行但不如预期).我不认为这是一个优势:new是语言的核心部分所以对我来说故意避免它有点武断 - 不妨避免使用其他关键词else.

工厂功能的一个优点是,根据某些参数,要返回的对象可能有几种不同的类型.

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

Eri*_*ott 104

使用构造函数的好处

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

  • this 指新对象

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

缺点

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

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

  • 如果您进行instanceof检查,则会对是否new需要进行模糊处理.在我看来,它不应该.你已经有效地缩短了new要求,这意味着你可以消除缺点#1.但是,除了名称之外,你只有一个工厂函数,还有额外的样板,大写字母和不太灵活的this上下文.

构造函数打破了开放/封闭原则

但我主要担心的是它违反了开放/封闭原则.您开始导出构造函数,用户开始使用构造函数,然后您意识到您需要工厂的灵活性,而不是(例如,将实现切换为使用对象池,或跨执行上下文实例化,或者使用原型OO具有更多的继承灵活性.

但是你被卡住了.如果不破坏调用构造函数的所有代码,则无法进行更改new.例如,您无法切换到使用对象池来提高性能.

此外,使用构造函数会为您提供一种instanceof在执行上下文中不起作用的欺骗性,并且如果您的构造函数原型被换出,则不起作用.如果this从构造函数开始返回,然后切换到导出任意对象,它也将失败,您必须这样做才能在构造函数中启用类似工厂的行为.

使用工厂的好处

  • 更少的代码 - 无需样板.

  • 您可以返回任意对象,并使用任意原型 - 为您提供更多灵活性来创建实现相同API的各种类型的对象.例如,可以创建HTML5和Flash播放器实例的媒体播放器,或者可以发出DOM事件或Web套接字事件的事件库.工厂还可以跨执行上下文实例化对象,利用对象池,并允许更灵活的原型继承模型.

  • 您永远不需要从工厂转换为构造函数,因此重构永远不会成为问题.

  • 没有关于使用的歧义new.别.(它会使this表现很糟糕,见下一点).

  • this行为与通常一样 - 所以你可以使用它来访问父对象(例如,在内部player.create(),this引用player,就像任何其他方法调用一样.call并且applythis按预期重新分配.如果你将原型存储在父对象上,那么可以是动态交换功能的好方法,并为对象实例化启用非常灵活的多态性.

  • 没有关于是否资本化的含糊不清.别.Lint工具会抱怨,然后你会试图尝试使用new,然后你将撤消上述的好处.

  • 有些人喜欢这种方式var myFoo = foo();var myFoo = foo.create();阅读.

缺点

  • new行为不符合预期(见上文).解决方案:不要使用它.

  • this不引用新对象(相反,如果使用点表示法或方括号表示法调用构造函数,例如foo.bar() - this引用foo- 就像所有其他JavaScript方法一样 - 请参阅优点).

  • 在什么意义上你的意思是构造函数使调用者与其实现紧密耦合?就构造函数参数而言,这些参数甚至需要传递给工厂函数才能使用它们并在其中调用适当的构造函数。 (2认同)
  • 关于违反开放/封闭:这不是关于依赖注入吗?如果A需要B,则A调用新的B()或A调用BFactory.create(),它们都引入了耦合.另一方面,如果在组合根中给A一个B实例,A根本不需要知道B如何实例化.我觉得建造者和工厂都有它们的用途; 构造函数用于简单实例化,工厂用于更复杂的实例化.但在这两种情况下,注入依赖项都是明智之举. (2认同)
  • 因为任何函数都可以在JavaScript中返回一个新对象,而且很多都没有使用`new`关键字,所以我不相信`new`关键字确实提供了任何额外的可读性.IMO,为了让呼叫者输入更多内容,跳过篮球似乎很愚蠢. (2认同)

Ign*_*ams 38

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


tra*_*r53 5

构造函数示例

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

let user = new User("Jack");
Run Code Online (Sandbox Code Playgroud)
  • new创建一个原型对象,User.prototypeUser使用创建的对象作为其this值进行调用。

  • new 将其操作数的自变量表达式视为可选:

       let user = new User;
    
    Run Code Online (Sandbox Code Playgroud)

    会导致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);
Run Code Online (Sandbox Code Playgroud)

在这里,工厂函数不带new。如果函数的参数及其返回的对象类型,则该函数完全负责直接或间接使用。在此示例中,它返回一个简单的[Object object],其中包含通过参数设置的一些属性。

利弊

轻松向调用者隐藏对象创建的实现复杂性。这对于浏览器中的本机代码功能特别有用。

工厂函数不必总是返回相同类型的对象,甚至可以null作为错误指示符返回。

在简单的情况下,工厂功能的结构和含义可能很简单。

返回的对象通常不会从工厂函数的prototype属性继承,而false从继承instanceOf factoryFunction

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