JavaScript 代码架构 - 是否使用构造函数

JavaScript Code Architecture - Use Constructor Functions Or Not

本文关键字:构造函数 是否 代码 JavaScript      更新时间:2023-09-26

请帮助我决定是否应该使用函数的原型对象和"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;
}

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

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

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

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

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

在我的编码中,我通常不强制隐私,我确实使用.prototypenew。 我在原型上指定"非公共"方法,方法是以下划线开头。 这是一种表示法约定,而不是访问强制方案。

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

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

您可以使用元构造函数*模式来解决此问题。

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"等函数属性。然后,在metaCtor的正文中,可以写出类似this.extends(BaseWidget)的东西。defineCtor API 永远不需要更改即可实现任何这种情况。

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

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