2011-09-16 53 views
7
// Case A 
function Constructor() { 
    this.foo = function() { 
    ... 
    }; 
    ... 
} 

// vs 
// Case B 
function Constructor() { 
    ... 
}; 

Constructor.prototype.foo = function() { 
    ... 
} 

其中一個主要原因人建議使用原型是.foo是在爲this.foo使用其他方法時創建多次原型的情況下創建一次。是否創建函數佔用更多的內存

然而,人們會期望口譯員可以優化這一點。因此,只有一個函數foo的副本在情況下爲A.

當然,由於閉包,每個對象仍然具有唯一的範圍上下文,但是每個對象的新函數的開銷較小。

現代JS解釋器是否優化了案例A,因此只有函數foo的一個副本?

回答

8

是的,創建函數使用更多的內存。

...並且,不,解釋者不會將案例A優化爲單一功能。

原因是the JS scope chain需要函數的每個實例來捕獲它創建時可用的變量。也就是說,modern interpreters對於案例A比過去是better,但很大程度上是因爲封閉函數的性能是幾年前的一個已知問題。

由於這個原因,Mozilla對avoid unnecessary closures說,但是閉包是JS開發人員工具箱中最強大和最常用的工具之一。

更新:剛跑this test創建構造的1M '實例',瞭解如何使用Node.js(這是V8的JS解釋器在Chrome)。隨着caseA = true我得到這個內存的使用情況:

{ rss: 212291584, 
vsize: 3279040512, 
heapTotal: 203424416, 
heapUsed: 180715856 } 

而且隨着caseA = false我得到這個內存使用:

{ rss: 73535488, 
vsize: 3149352960, 
heapTotal: 74908960, 
heapUsed: 56308008 } 

所以關閉功能肯定消耗顯著更多的內存,幾乎3倍。但從絕對意義上講,我們只是在談論每個實例〜140-150個字節的差異。 (但是,這可能會增加,具體取決於創建函數時的範圍內變量的數量)。

+0

我們可以有一些參考文獻來定義「更好」和「現代解釋器」 – Raynos

+0

您的測試與我在我的測試中發現的相符---我還在函數內部添加了一些大代碼塊來測試是否使內存氣球更快 - 它並沒有...功能內的代碼不需要額外的內存。 – gnarf

+0

哦 - 我還在測試前後記錄了內存使用情況,以便在創建對象之前測量差異 – gnarf

0

javascript解釋器也沒有優化原型對象。它僅僅是每個類型只有一個類型的情況(即多個實例引用)。另一方面,構造函數會創建新的實例和在其中定義的方法。所以根據定義,這實際上不是解釋者「優化」的問題,而僅僅是理解正在發生的事情。

另一方面,如果解釋器試圖合併實例方法,如果您決定更改特定實例中某個值的值,您會遇到問題(我寧願頭痛不會添加到該語言中):)

+0

編譯器確實對函數進行了優化,但它並沒有優化scopecontext。 – Raynos

+2

雖然每個解釋器如何處理重複並不像是問題,但解釋器需要建立多個實例方法之間的差異,並且這種差異會消耗更多的內存 – Marlin

+1

對於情況A,我希望編譯器能夠看到存在在構造函數中沒有局部變量,因此不需要將其變量對象保留在實例的作用域鏈上。如果使用局部變量,除非它足夠聰明,如果內部函數不引用它們,則優化變量對象。無論如何,我會使用原型方法作爲它的整潔和更容易維護(當然IMO)。 – RobG

2

我相信,在節點進行一些簡短的測試之後,在情況A和B中,內存中功能foo的實際代碼只有一個副本。

案例A - 爲存儲對函數代碼的引用及其當前執行範圍的每次執行都創建一個函數對象。案例B - 只有一個範圍,一個功能對象,通過原型共享。

+0

你可以發佈你的測試代碼嗎?我確信解釋器內部會進行一些優化,以避免每次都解析函數代碼,但是每次通過構造函數都必須捕獲對任何範圍內變量的引用,以便在函數是調用。 – broofa

+0

@broofa - 將我的代碼與你的代碼進行比較,它基本上是一樣的...... :) - 我只是傾倒了40行代碼,來自函數內部的其他代碼來測試... – gnarf

相關問題