JavaScript代码体系结构 - 使用构造函数或不使用

时间:2012-04-16 16:37:11

标签: javascript unobtrusive-javascript

请帮我决定是否应该使用函数的原型对象和“new”关键字,或者完全远离构造函数。

情况:

名为widget()的函数,将调用10-15次来初始化页面上的每个小部件。 widget()包含许多内部方法。

每次调用widget()时,函数都需要返回一个对象作为API来操作窗口小部件。

问题

1)我是否将Widget()内的所有内部方法放在其prototype属性下?它没有意义,但主要原因是每次调用widget()时都不会重新实例化内部函数。

但是如果我把内部函数放在原型中,每个实例化的w对象(w = new Widget();)都可以访问内部私有方法。

2)如果我远离构造函数和new关键字并构造我的代码如下所示,我如何修复每次widget()时重新实例化的内部函数的性能问题调用。

function widget()
{
   var returnObj = {};

   /* Add internal functions but this will be re-instantiated every time */

   return returnObj;

}

2 个答案:

答案 0 :(得分:4)

你在这里有一点权衡。正如您似乎已经了解的那样,您放在.prototype上的方法是公开可用的,但这是放置方法的最有效位置,因为它们会以非常有效的方式自动添加到该对象的所有新副本中。对方法使用.prototype时,只有一个方法副本,并且对该单个副本的引用会自动添加到该对象的所有新实例中。

但是,javascript没有内置的私有方法,唯一的解决方法是不使用.prototype或者需要调用私有方法的任何方法。

这个article by Doug Crockford非常好地描述了如何为任何对象中的数据或方法创建隐私。

在任何一种情况下,我都没有理由避免使用new关键字来创建新对象。您可以使.prototype或私有方法与new一起使用。

但是,如果您想要实现真正的私有方法,那么您不能将.prototype用于私有方法或任何需要访问它们的方法,因此您必须决定哪个对您更重要。没有一个正确的答案,因为您对隐私的需求是针对具体情况的。

在我的编码中,我通常不会强制执行隐私,而是使用.prototypenew。我通过用下划线开始他们的名字来指定原型上的“非公开”方法。这是一个符号约定,而不是访问执行方案。

在回答关于避免new运算符和重新实现方法的第二个问题时,我只想问你为什么要这样做?你有什么收获?我不知道使用new有任何缺点。我最了解您在构造函数中使用.prototype与手动创建/赋值方法的决定应该是关于私有方法的需要。

仅供参考,15件物品在这里无论如何都不会产生显着的性能差异。我会评估您对真正隐私的需求,并根据这一点做出决定。如果您必须强制实施隐私,那么请使用Crockford方法来实现私有方法。如果您没有真正的隐私,请使用.prototype。在这两种情况下,我都没有理由避免使用new

答案 1 :(得分:0)

您可以使用metaconstructor *模式解决此问题。

function defineCtor(metaCtor) {

  var proto = new metaCtor();

  var ctor = proto.hasOwnProperty('constructor') ? 
             proto.constructor : new Function();

  return (ctor.prototype = proto).constructor = ctor;

}

现在你有一个构造构造函数的函数(或者更准确地构造原型并返回构造函数)。

var Widget = defineCtor(function() {

  function doInternalStuff() {

    // ...cant see me

  }

  // this function ends up on the prototype

  this.getFoo = function() { return doInternalStuff(); };

});

// ...

var myWidget = new Widget();

<强>解释

defineCtor将一个匿名函数作为属性。它使用new调用函数,创建一个对象。它将对象指定为新构造函数的原型属性(空函数或生成的原型对象自己的constructor属性),并返回该函数。

这为您的内部函数提供了一个闭包,解决了您的问题1,并为您设置了构造函数/原型对,解决了问题2.


<强>比较

defineCtor技术与以下两个示例进行比较。

此示例使用原型,并且存在问题1:内部资源未封装。

function Widget(options) {
  this.options = options;
}

Widget.prototype = {

  getFoo: function() {
    return doInternalStuff();
  }

};

// How to encapsulate this?
function doInternalStuff() { /* ... */ }

此示例在构造函数中设置所有内容,并且存在问题2:每次构造对象时,它都会为每个属性实例化新的函数对象。

function Widget(options) {

  this.options = options;

  function doInternalStuff() { /* ... */ }

  this.getFoo = function() {
    return doInternalStuff();
  };

}

此示例使用上述技术提供封装,同时仍然利用原型:

var Widget = defineCtor(function() { 
  //                    ^
  // This function runs once, constructing the prototype.

  // In here, `this` refers to the prototype.

  // The real constructor.
  this.constructor = function(options) {

    // In function properties, `this` is an object instance 
    // with the outer `this` in its prototype chain.

    this.options = options;

  };  

  function doInternalStuff() { /* ... */ }

  this.getFoo = function() { return doInternalStuff(); };

});

// ...

var myWidget = new Widget();

这种方法有一些好处,有些比其他方法更明显。

  • 它提供封装。你可以通过在一个立即调用的函数中包装第一个“比较”示例来实现这一点,但这种方法可能更清晰,更容易在团队设置中“强制执行”。

  • 它是可扩展的。您可以使用“extends”,“mixin”等函数属性为您的“metaconstructor”函数提供自己的原型。然后,在metaCtor的正文中,您可以编写this.extends(BaseWidget)之类的内容。 defineCtor API永远不需要为此发生任何变化。

  • 它“欺骗”Google Closure Compiler,Eclipse,jsdoc等,以为您正在定义实际的构造函数而不是“元函数”。这在某些情况下非常有用(代码是以这些工具理解的方式“自我记录”的。)

*据我所知,“metaconstructor”这个词完全构成了。