2015-01-31 23 views
4

風格上,我更喜歡這樣的結構:在構造函數內部指定原型方法*爲什麼不能?

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; 
} 

功能,還有什麼缺點構建我的代碼這種方式?將原型方法添加到構造函數體內的原型對象(即在構造函數的表達式語句關閉之前)會導致意外的範圍問題?

我以前使用過第一個結構,但是我想確保我不會讓自己陷入調試頭痛之中,或者由於錯誤的編碼習慣而導致開發人員的悲傷和惡化。

+2

我想到的是Filter的原型在Filter被調用之前一直是空的。如果有人希望繼承Filter過濾器原型但不先調用Filter?但我不確定這是否值得完整答案。 :) – 2015-01-31 20:09:56

+2

您正在重新運行每次調用構造函數時設置原型的所有代碼。 – Pointy 2015-01-31 20:10:10

+4

每次構造函數被調用時,它會一遍又一遍地運行相同的賦值,所以在這方面有點浪費。也許別人可以想到一個實際上會導致功能問題的情況,但我不能。 – jfriend00 2015-01-31 20:10:17

回答

11

功能上,這樣構造我的代碼有沒有什麼缺點? 將原型方法添加到 構造函數的正文中(即構造函數的 表達式語句關閉之前)是否會導致意外的作用域問題?

是的,有缺陷和意想不到的範圍界定問題。

  1. 遍地本地定義的功能分配的原型,無論重複該分配,並創建一個新的功能對象中的每個的時間。之前的作業將被垃圾收集,因爲它們不再被引用,但在構造函數的運行時執行和垃圾收集方面與第二個代碼塊相比,這是不必要的。

  2. 在某些情況下會出現意外的範圍界定問題。請參閱我的答案末尾的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方法可以訪問創建的最後一個實例的構造函數的局部變量。這是一場程序設計的災難,因爲這可能從來都不是想要的,而且很容易就能成爲頭腦清醒的人,弄清楚出了什麼問題以及爲什麼。

+1

你的'Counter'的例子和解釋很棒。謝謝! – 2015-01-31 20:44:03

2

第一個示例代碼錯過了原型的目的。您將爲每個實例重新創建checkProduct方法。雖然它只會在原型上定義,並且不會爲每個實例消耗內存,但仍然需要時間。

如果要封裝你可以檢查方法的存在該類陳述checkProduct方法之前:

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

有你應該考慮一兩件事。這個匿名函數的閉包現在可以訪問構造函數中的所有變量,因此它可能很容易訪問它們,但那會導致你陷入一個兔子洞,因爲這個函數只會關注單個實例的閉包。在你的例子中,它將是最後一個例子,在我的例子中它將是第一個例子。

0

在第一個示例中,Filter原型沒有填充函數,直到Filter被調用至少一次。如果有人試圖繼承Filter原型?無論使用的NodeJS'

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

Object.create

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

如果忘記或不知道調用Filter先用原型鏈空原型總是結束。

+0

給出你的第一點,這是否意味着通過使用函數聲明和函數表達式可以更好地服務第一個結構? – 2015-01-31 20:24:10

+0

那麼,只要你調用你的'ExtendedFilter'構造函數,它就會調用'Filter',並且這個方法「奇蹟般地」出現在你的原型上。它可以很好地工作,原型最初是空的,這對繼承無關緊要。 – Bergi 2015-01-31 20:29:50

+0

只有當程序員決定寫'ExtendedFilter'主體時,它纔會自動調用'Filter'(這可能是不好的做法,但仍然...)。我不認爲這個答案值得downvote,因爲OP詢問這種做法是否可能「導致開發人員的悲傷和惡化」,我不覺得在JavaScript世界中感到舒服,原型在第一個構造函數調用之前是空的。 – 2015-01-31 20:36:13

1

您的代碼最大的缺點是關閉覆蓋您的方法的可能性。

如果我寫:

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

var a = new Filter(p1,p2); 

a.checkProduct(product); 

其結果將是不同於預期的那樣原始函數將被調用,而不是我的。

3

而其他的答案都集中在那些錯誤從構造函數中分配給原型的事情,我會專注於自己的第一條語句:

風格上,我更喜歡這種結構

也許你喜歡這種符號提供的乾淨的encapsulation - 屬於該類的所有東西都可以通過{}區塊正確「限定範圍」。 (當然,謬誤在於它是的作用域到每個運行的構造函數)。

我建議你參考JavaScript提供的(揭示)module patterns。你得到一個更加明確的結構,獨立的構造函數聲明,類範圍的私有變量,一切正常封裝在一個模塊:

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; 
}()); 
+0

對於代碼風格問題,IIFE是效率低下的解決方案,在這種情況下,第一行和最後兩行可以被刪除,沒有任何實際操作被改變,並且一些關閉被避免。通常,它將用於分配給* Filter.prototype *的函數根據某種邏輯而不同的情況,比如特性測試。另一種方法是使用標籤和塊:'Filter:{/ * code here * /}'。或者只是評論。 – RobG 2015-01-31 21:02:01

+0

@RobG:是的,這個小例子中模塊模式是不必要的。但是,當代碼增長時,它還有其他一些優點。例如你可以爲'Filter.prototype'引入一個本地別名,以避免重複。我會避免使用塊,因爲函數聲明在語法上是無效的。 – Bergi 2015-01-31 22:34:52

+0

我想它會成爲一個命名的函數表達式,但這可能不正確。它適用於我測試過的每個瀏覽器 - 不是一個詳盡的列表,但它包括IE 6. – RobG 2015-02-01 08:23:26

0

僅供參考,你不能做到這一點安全之一:

function Constr(){ 

    const privateVar = 'this var is private'; 

    this.__proto__.getPrivateVar = function(){ 
     return privateVar; 
    }; 

} 

原因是因爲Constr.prototype === this.__proto__,所以你會有同樣的不良行爲。

相關問題