在风格上,我更喜欢这种结构:
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;
}
从功能上讲,以这种方式构建代码有什么缺点吗?将原型方法添加到构造函数体内的原型对象(即在构造函数的表达式语句关闭之前)会导致意外的范围问题吗?
我之前已经成功使用了第一个结构,但是我想确保自己没有为调试带来麻烦,或者由于编码错误而导致开发人员悲痛和恶化。
答案 0 :(得分:11)
从功能上讲,以这种方式构建代码有什么缺点吗? 将一个原型方法添加到内部的原型对象中 构造函数的主体(即在构造函数之前) 表达式语句关闭)引起意外的范围问题?
是的,有缺点和意外的范围问题。
将原型一遍又一遍地分配给本地定义的函数,两者都重复该赋值并每次创建一个新的函数对象。之前的赋值将被垃圾收集,因为它们不再被引用,但与第二个代码块相比,它在构造函数的运行时执行和垃圾收集方面都是不必要的工作。
在某些情况下存在意外的范围问题。有关明确示例,请参阅我的答案末尾的Counter
示例。如果从prototype方法引用构造函数的局部变量,那么您的第一个示例会在代码中创建一个潜在的讨厌错误。
还有一些其他(更小的)差异。您的第一个方案禁止在构造函数之外使用原型,如:
Filter.prototype.checkProduct.apply(someFilterLikeObject, ...)
当然,如果有人使用:
Object.create(Filter.prototype)
没有运行Filter
构造函数,这也会产生不同的结果,这可能不太可能,因为期望使用Filter
原型的东西运行Filter
是合理的构造函数,以达到预期的结果。
从运行时性能的角度来看(在对象上调用方法的性能),你最好还是这样:
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“专家”声称不再需要使用原型节省内存(几天前我观看了关于此的视频讲座)所以是时候开始直接使用更好的方法了对象而不是原型。我不知道我是否准备好自己提倡,但这是一个值得思考的有趣点。
我能想到的第一种方法的最大缺点是,制作一个讨厌的编程错误确实非常容易。如果您碰巧认为您可以利用原型方法现在可以看到构造函数的局部变量这一事实,只要您有多个对象实例,就会很快将自己射入脚中。想象一下这种情况:
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
方法形成了一个闭包并且可以访问构造函数的局部变量的实例变量,但它在实践中并不是那样的。因为所有实例共享相同的原型对象,所以Counter
对象的每个新实例都会创建get
函数的新实例(可以访问刚创建的实例的构造函数局部变量)并分配它到原型,所以现在所有实例都有一个get
方法访问创建的最后一个实例的构造函数的局部变量。这是一个编程灾难,因为这可能永远不会是预期的,并且可能很容易成为弄清楚出了什么问题以及原因的头脑。
答案 1 :(得分:3)
虽然其他答案都集中在从构造函数内部分配原型的错误上,但我会关注你的第一个陈述:
在风格上,我更喜欢这种结构
可能你喜欢这种符号所提供的干净的encapsulation - 属于这个类的所有东西都是正确的"范围"它由{}
块组成。 (当然,谬误是作用域到构造函数的每个 run )。
我建议您参加JavaScript提供的({3}}。您将获得更加明确的结构,独立的构造函数声明,类范围的私有变量以及正确封装在块中的所有内容:
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;
}());
答案 2 :(得分:2)
第一个示例代码类型错过了原型的目的。您将为每个实例重新创建checkProduct方法。虽然它只在原型上定义,并且不会消耗每个实例的内存,但仍需要时间。
如果您希望封装类,可以在说明checkProduct方法之前检查方法是否存在:
if(!Filter.prototype.checkProduct) {
Filter.prototype.checkProduct = function( product ){
// run some checks
return is_match;
}
}
还有一件事你应该考虑。匿名函数的闭包现在可以访问构造函数中的所有变量,因此它可能很容易访问它们,但这会导致你陷入一个兔子洞,因为该函数只能识别单个实例的闭包。在您的示例中,它将是最后一个实例,在我的示例中,它将是第一个实例。
答案 3 :(得分:1)
代码的最大缺点是可能会覆盖您的方法。
如果我写:
Filter.prototype.checkProduct = function( product ){
// run some checks
return different_result;
}
var a = new Filter(p1,p2);
a.checkProduct(product);
结果将与预期的不同,因为将调用原始函数,而不是我的。
答案 4 :(得分:0)
在第一个示例中,Filter
原型未填充函数,直到Filter
被调用至少一次。如果有人试图原型继承Filter
怎么办?使用nodejs'
function ExtendedFilter() {};
util.inherit(ExtendedFilter, Filter);
或Object.create
:
function ExtendedFilter() {};
ExtendedFilter.prototype = Object.create(Filter.prototype);
如果忘记或者不知道首先调用Filter
,总是会在原型链中找到空原型。
答案 5 :(得分:0)
仅供参考,你不能安全地做到这一点:
function Constr(){
const privateVar = 'this var is private';
this.__proto__.getPrivateVar = function(){
return privateVar;
};
}
原因是因为Constr.prototype === this.__proto__
,所以你会有同样的错误行为。