请帮我决定是否应该使用函数的原型对象和“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;
}
答案 0 :(得分:4)
你在这里有一点权衡。正如您似乎已经了解的那样,您放在.prototype
上的方法是公开可用的,但这是放置方法的最有效位置,因为它们会以非常有效的方式自动添加到该对象的所有新副本中。对方法使用.prototype
时,只有一个方法副本,并且对该单个副本的引用会自动添加到该对象的所有新实例中。
但是,javascript没有内置的私有方法,唯一的解决方法是不使用.prototype
或者需要调用私有方法的任何方法。
这个article by Doug Crockford非常好地描述了如何为任何对象中的数据或方法创建隐私。
在任何一种情况下,我都没有理由避免使用new
关键字来创建新对象。您可以使.prototype
或私有方法与new
一起使用。
但是,如果您想要实现真正的私有方法,那么您不能将.prototype
用于私有方法或任何需要访问它们的方法,因此您必须决定哪个对您更重要。没有一个正确的答案,因为您对隐私的需求是针对具体情况的。
在我的编码中,我通常不会强制执行隐私,而是使用.prototype
和new
。我通过用下划线开始他们的名字来指定原型上的“非公开”方法。这是一个符号约定,而不是访问执行方案。
在回答关于避免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”这个词完全构成了。