[英]Assigning prototype methods *inside* the constructor function - why not?
在風格上,我更喜歡這種結構:
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;
}
在功能上,以這種方式構建我的代碼有什么缺點嗎? 將原型方法添加到構造函數體內的原型對象中(即在構造函數的表達式語句關閉之前)會導致意外的作用域問題嗎?
我之前成功地使用了第一個結構,但我想確保我不會讓自己為調試頭疼,或者由於糟糕的編碼實踐而導致其他開發人員悲傷和惡化。
在功能上,以這種方式構建我的代碼有什么缺點嗎? 將原型方法添加到構造函數體內的原型對象中(即在構造函數的表達式語句關閉之前)會導致意外的作用域問題嗎?
是的,有缺點和意外的范圍界定問題。
一遍又一遍地將原型分配給本地定義的函數,每次都重復該分配並創建一個新的函數對象。 較早的分配將被垃圾收集,因為它們不再被引用,但與第二個代碼塊相比,在構造函數的運行時執行和垃圾收集方面都是不必要的工作。
在某些情況下會出現意外的范圍界定問題。 有關明確示例,請參閱我回答末尾的Counter
例。 如果您從原型方法中引用構造函數的局部變量,那么您的第一個示例會在您的代碼中創建一個潛在的令人討厭的錯誤。
還有一些其他(更小的)差異。 您的第一個方案禁止在構造函數之外使用原型,如下所示:
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
方法來訪問最后創建的實例的構造函數的局部變量。 這是一場編程災難,因為這可能永遠不是預期的,並且很容易讓人頭疼,找出出了什么問題以及原因。
雖然其他答案都集中在從構造函數內部分配給原型的問題上,但我將重點放在你的第一個陳述上:
在風格上,我更喜歡這種結構
可能您喜歡這種表示法提供的干凈封裝- 屬於該類的所有內容都由{}
塊正確地“限定”到它的范圍內。 (當然,謬誤在於它的范圍僅限於構造函數的每次運行)。
我建議你學習 JavaScript 提供的(揭示) 模塊模式。 你會得到一個更明確的結構、獨立的構造函數聲明、類范圍的私有變量,以及正確封裝在一個塊中的所有內容:
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;
}());
第一個示例代碼忽略了原型的目的。 您將為每個實例重新創建 checkProduct 方法。 雖然它只會在原型上定義,並且不會為每個實例消耗內存,但它仍然需要時間。
如果您希望封裝該類,您可以在聲明 checkProduct 方法之前檢查該方法是否存在:
if(!Filter.prototype.checkProduct) {
Filter.prototype.checkProduct = function( product ){
// run some checks
return is_match;
}
}
還有一件事你應該考慮。 該匿名函數的閉包現在可以訪問構造函數內的所有變量,因此訪問它們可能很誘人,但這會導致您陷入困境,因為該函數只會對單個實例的閉包保密。 在您的示例中,它將是最后一個實例,而在我的示例中,它將是第一個實例。
您的代碼的最大缺點是關閉覆蓋您的方法的可能性。
如果我寫:
Filter.prototype.checkProduct = function( product ){
// run some checks
return different_result;
}
var a = new Filter(p1,p2);
a.checkProduct(product);
結果將與預期不同,因為將調用原始函數,而不是我的。
在第一個示例中, Filter
原型不會填充函數,直到至少調用一次Filter
。 如果有人試圖以原型方式繼承Filter
怎么辦? 使用任一 nodejs'
function ExtendedFilter() {};
util.inherit(ExtendedFilter, Filter);
或Object.create
:
function ExtendedFilter() {};
ExtendedFilter.prototype = Object.create(Filter.prototype);
如果忘記或不知道首先調用Filter
,則原型鏈中總是以空原型結束。
僅供參考,您也無法安全地執行此操作:
function Constr(){
const privateVar = 'this var is private';
this.__proto__.getPrivateVar = function(){
return privateVar;
};
}
原因是因為Constr.prototype === this.__proto__
,所以你會有同樣的錯誤行為。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.