构造函数vs工厂函数

时间:2012-01-02 08:19:56

标签: javascript oop

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

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

8 个答案:

答案 0 :(得分:140)

基本区别在于构造函数与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(编辑:这可能是一个问题,因为没有new该功能仍将运行但不如预期的那样)。我不认为这是一个优势:new是语言的核心部分,所以对我来说故意避免它有点武断 - 也可以避免使用else之类的其他关键词。

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

答案 1 :(得分:100)

使用构造函数的好处

  • 大多数书籍教你使用构造函数和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就像任何其他方法调用一样。callapply也按预期重新分配this。如果你将原型存储在父对象上,这可能是动态换出的好方法功能,并为您的对象实例化启用非常灵活的多态性。

  • 关于是否要资本化没有歧义。别。 Lint工具会抱怨,然后您会尝试使用new,然后您将撤消上述优势。

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

缺点

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

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

答案 2 :(得分:37)

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

答案 3 :(得分:2)

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

  1. 决定合同(方法和他们将要做的事情)
  2. 创建公开这些方法的接口(在javascript中你没有接口,所以你需要提出一些方法来检查实现)
  3. 创建一个工厂,返回所需的每个接口的实现。
  4. 实现(使用new创建的实际对象)不会向工厂用户/使用者公开。这意味着工厂开发人员可以扩展并创建新的实现,只要他/她不违反合同......并且它允许工厂消费者只需从新API中受益而无需更改他们的代码......如果他们使用了新的和“新的”实现,那么他们必须去改变使用“新”的每一行使用“新”实现...在工厂他们的代码不会改变...

    工厂 - 比其他任何方面都更好 - 弹簧框架完全围绕这个想法构建。

答案 4 :(得分:1)

构造函数示例

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返回其创建的对象,除非构造函数返回一个对象值,而是返回该对象值。这是一个边缘情况,在大多数情况下可以忽略。

优点和缺点

由构造函数创建的对象从构造函数的prototype属性继承属性,并使用构造函数上的instanceOf运算符返回true。

如果在已经使用构造函数之后动态更改构造函数的prototype属性的值,上述行为就会失败。 这样做很少见,如果使用class关键字创建了构造函数,则无法更改。

可以使用extended关键字来扩展构造函数。

构造函数不能将null作为错误值返回。由于它不是对象数据类型,因此new将忽略它。

工厂功能示例

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

在没有new的情况下调用了工厂函数。如果函数的参数及其返回的对象类型,则该函数完全负责直接或间接使用。在此示例中,它返回一个简单的[Object object],该对象具有通过参数设置的一些属性。

优点和缺点

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

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

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

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

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

答案 5 :(得分:0)

工厂是一个抽象层,就像所有抽象一样,它们具有复杂性。当遇到基于工厂的API时,确定给定API的工厂是什么对API消费者来说可能是一个挑战。对于构造函数,可发现性是微不足道的。

在决策者和工厂之间做出决定时,您需要确定复杂性是否符合效益。

值得注意的是,Javascript构造函数可以通过返回除此之外或未定义的内容而成为任意工厂。所以在js中你可以充分利用这两个世界 - 可发现的API和对象池/缓存。

答案 6 :(得分:0)

对于差异,埃里克·埃利奥特(Eric Elliott)讲得很好,

但是第二个问题:

  

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

如果您来自面向对象的背景,那么构造函数对您来说看起来更自然。 这样,您就不会忘记使用new关键字。

答案 7 :(得分:0)

为之前的答案添加的一个新内容是,有一种声明构造函数的新方法。

class CreateCirle {
    constructor(radius) {
        this.radius = radius;
        this.draw = function () {
            console.log('we draw a circle with radius: ', this.radius);
        };
        this.name = "Circly";
        this.address = "Math Graph";
    }
}

您必须添加 classconstructor 关键字。

然后正常调用它,没有区别。

let cirle = new CreateCirle(5);
cirle.draw();

不要忘记使用前面提到的 PascalCase 命名约定。