在构造函数里面分配原型方法*为什么不呢?

在风格上,我更喜欢这种结构:

var Filter = function( category, value ){ this.category = category; this.value = value; // product is a JSON object Filter.prototype.checkProduct = function( product ){ // run some checks return is_match; } }; 

对于这个结构:

 var Filter = function( category, value ){ this.category = category; this.value = value; };// var Filter = function(){...} Filter.prototype.checkProduct = function( product ){ // run some checks return is_match; } 

在function上,这样构build我的代码有没有什么缺点? 将原型方法添加到构造函数体内的原型对象(即在构造函数的expression式语句closures之前)会导致意外的范围问题?

我以前使用过第一个结构,但是我想确保我不会让自己陷入debugging困境,或者由于不好的编码习惯而导致开发人员的悲痛和激动。

在function上,这样构build我的代码有没有什么缺点? 将原型方法添加到构造函数体内的原型对象(即在构造函数的expression式语句closures之前)会导致意外的范围问题?

是的,有缺陷和意想不到的范围界定问题。

  1. 将原型反复分配给本地定义的函数,都重复该分配并每次创build一个新的函数对象。 之前的任务将被垃圾收集,因为它们不再被引用,但是与第二个代码块相比,在构造函数的运行时执行和垃圾收集方面都是不必要的。

  2. 在某些情况下有意想不到的范围界定问题。 在我的答案结尾处查看Counter示例。 如果您从原型方法引用构造函数的局部variables,那么您的第一个示例会在您的代码中创build一个潜在的恶意错误。

还有一些(更小的)差异。 您的第一个scheme禁止在构造函数之外使用原型,如下所示:

 Filter.prototype.checkProduct.apply(someFilterLikeObject, ...) 

当然,如果有人使用:

 Object.create(Filter.prototype) 

而不运行Filter构造函数,这也会创build一个不同的结果,这可能不太可能,因为期望使用Filter原型的某些东西应该运行Filter构造函数以实现预期的结果是合理的。


从运行时性能的angular度来看(在对象上调用方法的性能),你最好用这个:

 var Filter = function( category, value ){ this.category = category; this.value = value; // product is a JSON object this.checkProduct = function( product ){ // run some checks return is_match; } }; 

有一些Javascript“专家”声称,不再需要使用原型的内存节省(我前几天观看了一个video讲座),所以现在是时候开始直接在对象上使用更好的方法性能了比原型。 我不知道我是否准备好自己提倡这一点,但这是一个值得思考的问题。


我能想到的第一种方法最大的缺点是,编写一个令人讨厌的编程错误真的很容易。 如果您碰巧认为您可以利用原型方法现在可以看到构造函数的局部variables这一事实,只要您有多个对象实例,就会立即将自己拍摄下来。 想象一下这种情况:

 var Counter = function(initialValue){ var value = initialValue; // product is a JSON object Counter.prototype.get = function() { return value++; } }; var c1 = new Counter(0); var c2 = new Counter(10); console.log(c1.get()); // outputs 10, should output 0 

示范问题: http : //jsfiddle.net/jfriend00/c7natr3d/

这是因为,虽然看起来get方法形成一个闭包,并且可以访问构造函数的局部variables的实例variables,但在实践中却不这样。 由于所有实例共享相同的原型对象,所以Counter对象的每个新实例都会创build一个get函数的新实例(可以访问刚刚创build的实例的构造函数局部variables)并将其分配给原型,所以现在所有实例有一个get方法来访问创build的最后一个实例的构造函数的局部variables。 这是一个编程的灾难,因为这可能从来没有打算,可能很容易成为一个头痛的人找出什么地方出了问题,为什么。

第一个示例代码types错过了原型的目的。 您将重新创build每个实例的checkProduct方法。 虽然它只会在原型上定义,并且不会消耗每个实例的内存,但仍然需要时间。

如果你想封装类,你可以在声明checkProduct方法之前检查方法的存在:

 if(!Filter.prototype.checkProduct) { Filter.prototype.checkProduct = function( product ){ // run some checks return is_match; } } 

还有一件事你应该考虑。 这个匿名函数的闭包现在可以访问构造函数中的所有variables,所以它可能是诱人的访问它们,但是这会导致你陷入一个兔子洞,因为这个函数只会closures一个实例的闭包。 在你的例子中,这将是最后一个例子,在我的例子中,它将是第一个。

虽然其他答案都集中在从构造函数中分配给原型的问题上,但我会把重点放在第一个语句上:

在风格上,我更喜欢这种结构

大概你喜欢这个符号提供的干净的封装 – 属于这个类的所有东西都被{}块正确地“限制”了。 (当然,这个谬误是它的构造函数每次运行范围 )。

我build议你参考JavaScript提供的(揭示) 模块模式 。 你得到一个更加明确的结构,独立的构造函数声明,类作用域的私有variables,以及正确地封装在一个块中的所有东西:

 var Filter = (function() { function Filter(category, value) { // the constructor this.category = category; this.value = value; } // product is a JSON object Filter.prototype.checkProduct = function(product) { // run some checks return is_match; }; return Filter; }()); 

你的代码最大的缺点是closures覆盖你的方法的可能性。

如果我写:

 Filter.prototype.checkProduct = function( product ){ // run some checks return different_result; } var a = new Filter(p1,p2); a.checkProduct(product); 

结果将会不同于预期的原函数将被调用,而不是我的。

在第一个例子中, Filter原型没有被函数填充,直到Filter被调用至less一次。 如果有人试图inheritance原型Filter ? 使用nodejs'

 function ExtendedFilter() {}; util.inherit(ExtendedFilter, Filter); 

Object.create

 function ExtendedFilter() {}; ExtendedFilter.prototype = Object.create(Filter.prototype); 

如果忘记或者不知道先调用Filter总是会以原型链中的空白原型结束。

只是FYI,你不能安全地做到这一点:

  function Constr(){ const privateVar = 'this var is private'; this.__proto__.getPrivateVar = function(){ return privateVar; }; } 

原因是因为Constr.prototype === this.__proto__ ,所以你会有同样的不良行为。