JavaScript 代码架构 - 是否使用构造函数
JavaScript Code Architecture - Use Constructor Functions Or Not
请帮助我决定是否应该使用函数的原型对象和"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
用于私有方法或任何需要访问它们的方法,所以你必须决定哪个对你更重要。 没有单一的正确答案,因为您对隐私的需求是特定于情况的。
在我的编码中,我通常不强制隐私,我确实使用.prototype
和new
。 我在原型上指定"非公共"方法,方法是以下划线开头。 这是一种表示法约定,而不是访问强制方案。
在回答您关于避免使用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等,让你认为你正在定义实际的构造函数而不是"元函数"。 这在某些情况下可能很有用(代码以这些工具理解的方式"自我记录")。
*据我所知,"元构造函数"这个词完全是虚构的。
- ES6类是否与构造函数相同
- 如何检查DOM节点是否继承自构造函数
- 是否可以测试javascript函数是否是构造函数
- 正在检测是否在构造函数内部使用了新关键字
- 是否可以在函数构造函数中识别哪个对象调用它,并在错误的对象调用时中止创建
- 在定义了构造函数之后,是否可以将实例属性添加到JavaScript原型中
- 构造函数的“实例”是否有严格的定义
- 是否需要初始化在 Javascript 中作为“类”构造函数参数传递的变量
- 是否存在替换Javascript构造函数的问题'原型,而不是添加到原型中
- MongoDB 的集合构造函数是否仅在集合不存在时才创建集合
- 在 JavaScript ES2015 中,在扩展类时,我们是否需要显式创建一个新的构造函数,或者我们是否可以只使用父类
- jQuery构造函数中的函数是否执行任何不同操作
- 高级Javascript:检测当前函数是否由构造函数调用
- 是否可以在 TypeScript 中使用构造函数作为另一个函数的参数类型
- 是否可以在没有构造函数参数的情况下实例化空对象/对象
- 函数是否无法返回构造函数并创建新实例
- backbone.js构造函数是否可以返回子类
- 检查类构造函数是否继承了另一个类
- 如果修改构造函数的原型对象,现有构造函数是否不受影响?
- 如何验证构造函数是否是使用 sinon 调用的