4

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

情况:

调用的函数widget()将被调用 10-15 次以初始化页面上的每个小部件。widget()包含相当多的内部方法。

每次widget()调用该函数都需要返回一个对象,该对象作为 API 对小部件进行操作。

问题

1)我是否将所有内部方法都放在Widget()其原型属性下?这没有任何意义,但主要原因是每次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;

}
4

2 回答 2

4

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

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

Doug Crockford的这篇文章很好地描述了如何为任何对象中的数据或方法创建隐私。

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

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

在我的编码中,我通常不强制隐私,我确实使用.prototypeand new。我通过以下划线开始它们的名称来指定原型上的“非公共”方法。这是一个符号约定,而不是访问强制方案。

在回答您关于避免new运算符和重新实例化方法的第二个问题时,我只想问您为什么要这样做?你有什么收获?我不知道使用new. 据我所知,您关于是否.prototype在构造函数中使用手动创建/分配方法的决定应该是关于私有方法的需要。

仅供参考,无论哪种方式,15 个对象都不会在性能上产生显着差异。我会评估您对真正隐私的需求,并据此做出决定。如果您必须强制执行隐私,请使用 Crockford 方法来实现私有方法。如果您不必拥有真正的隐私,请使用.prototype. 在这两种情况下,我都没有理由避免使用new

于 2012-04-16T16:41:24.467 回答
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();

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

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

  • 它是可扩展的。metaCtor您可以为您的“元构造函数”函数提供自己的原型,具有“扩展”、“混合”等函数属性。然后,在this.extends(BaseWidget). defineCtorAPI 永远不需要改变来实现这些。

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

* 据我所知,“元构造函数”这个词完全是虚构的。

于 2012-04-16T16:59:24.287 回答