本章,我們將講解在ECMAScript向函數function傳遞參數的策略。
計算機科學里對這種策略一般稱為“evaluation strategy”(大叔注:有的人說翻譯成求值策略,有的人翻譯成賦值策略,通看下面的內容,我覺得稱為賦值策略更為恰當,anyway,標題還是寫成大家容易理解的求值策略吧),例如在編程語言為求值或者計算表達式設置規則。向函數傳遞參數的策略是一個特殊的case。
http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
寫這篇文章的原因是因為論壇上有人要求準確解釋一些傳參的策略,我們這里給出了相應的定義,希望對大家有所幫助。
很多程序員都確信在JavaScript中(甚至其它一些語言),對象是按引用傳參,而原始值類型按值傳參,此外,很多文章都說到這個“事實”,但有多人真正理解這個術語,而且又有多少是正確的?我們本篇講逐一講解。
一般理論
需要注意到,在賦值理論里一般有2中賦值策略:嚴格——意思是說參數在進入程序之前是經過計算過的;非嚴格——意思是參數的計算是根據計算要求才去計算(也就是相當于延遲計算)。
然后,這里我們考慮基本的函數傳參策略,從ECMAScript出發點來說是非常重要的。首先需要注意的是,在ECMAScript中(甚至其他的語如,C,JAVA,Python和Ruby中)都使用了嚴格的參數傳遞策略。
另外傳遞參數的計算順序也是很重要的——在ECMAScript是左到右,而且其它語言實現的反省順序(從右向做)也是可以用的。
嚴格的傳參策略也分為幾種子策略,其中最重要的一些策略我們在本章詳細討論。
下面討論的策略不是全部都用在ECMAScript中,所以在討論這些策略的具體行為的時候,我們使用了偽代碼來展示。
按值傳遞
按值傳遞,很多開發人員都很了解了,參數的值是調用者傳遞的對象值的拷貝(copy of value),函數內部改變參數的值不會影響到外面的對象(該參數在外面的值),一般來說,是重新分配了新內存(我們不關注分配內存是怎么實現的——也是是棧也許是動態內存分配),該新內存塊的值是外部對象的拷貝,并且它的值是用到函數內部的。
bar = 10 procedure foo(barArg): barArg = 20;end foo(bar) // foo內部改變值不會影響內部的bar的值print(bar) // 10
復制代碼
但是,如果該函數的參數不是原始值而是復雜的結構對象是時候,將帶來很大的性能問題,C++就有這個問題,將結構作為值傳進函數的時候——就是完整的拷貝。
我們來給一個一般的例子,用下面的賦值策略來檢驗一下,想想一下一個函數接受2個參數,第1個參數是對象的值,第2個是個布爾型的標記,用來標記是否完全修改傳入的對象(給對象重新賦值),還是只修改該對象的一些屬性。
// 注:以下都是偽代碼,不是JS實現bar = { x: 10, y: 20} procedure foo(barArg, isFullChange): if isFullChange: barArg = {z: 1, q: 2} exit end barArg.x = 100 barArg.y = 200 end foo(bar) // 按值傳遞,外部的對象不被改變print(bar) // {x: 10, y: 20} // 完全改變對象(賦新值)foo(bar, true) //也沒有改變print(bar) // {x: 10, y: 20}, 而不是{z: 1, q: 2}
復制代碼
按引用傳遞
另外一個眾所周知的按引用傳遞接收的不是值拷貝,而是對象的隱式引用,如該對象在外部的直接引用地址。函數內部對參數的任何改變都是影響該對象在函數外部的值,因為兩者引用的是同一個對象,也就是說:這時候參數就相當于外部對象的一個別名。
偽代碼:
procedure foo(barArg, isFullChange): if isFullChange: barArg = {z: 1, q: 2} exit end barArg.x = 100 barArg.y = 200 end // 使用和上例相同的對象bar = { x: 10, y: 20} // 按引用調用的結果如下: foo(bar) // 對象的屬性值已經被改變了print(bar) // {x: 100, y: 200} // 重新賦新值也影響到了該對象foo(bar, true) // 此刻該對象已經是一個新對象了print(bar) // {z: 1, q: 2}
復制代碼
該策略可以更有效地傳遞復雜對象,例如帶有大批量屬性的大結構對象。
按共享傳遞(Call by sharing)
上面2個策略大家都是知道的,但這里要講的一個策略可能大家不太了解(其實是學術上的策略)。但是,我們很快就會看到這正是它在ECMAScript中的參數傳遞戰略中起著關鍵作用的策略。
這個策略還有一些代名詞:“按對象傳遞”或“按對象共享傳遞”。
該策略是1974年由Barbara Liskov為CLU編程語言提出的。
該策略的要點是:函數接收的是對象對于的拷貝(副本),該引用拷貝和形參以及其值相關聯。
這里出現的引用,我們不能稱之為“按引用傳遞”,因為函數接收的參數不是直接的對象別名,而是該引用地址的拷貝。
最重要的區別就是:函數內部給參數重新賦新值不會影響到外部的對象(和上例按引用傳遞的case),但是因為該參數是一個地址拷貝,所以在外面訪問和里面訪問的都是同一個對象(例如外部的該對象不是想按值傳遞一樣完全的拷貝),改變該參數對象的屬性值將會影響到外部的對象。
procedure foo(barArg, isFullChange): if isFullChange: barArg = {z: 1, q: 2} exit end barArg.x = 100 barArg.y = 200 end//還是使用這個對象結構bar = { x: 10, y: 20} // 按貢獻傳遞會影響對象 foo(bar) // 對象的屬性被修改了print(bar) // {x: 100, y: 200} // 重新賦值沒有起作用foo(bar, true) // 依然是上面的值print(bar) // {x: 100, y: 200}
復制代碼
這個處理的假設前提是大多數語言里用到的對象,而不是原始值。
按共享傳遞是按值傳遞的特例
按共享傳遞這個策略很很多語言里都使用了:Java, ECMAScript, Python, Ruby, Visual Basic等。此外,Python社區已經使用了這個術語,至于其他語言也可以用這個術語,因為其他的名稱往往會讓大家感覺到混亂。大多數情況下,例如在Java,ECMAScript或Visual Basic中,這一策略也稱之為按值傳遞——意味著:特殊值——引用拷貝(副本)。
原文轉自:http://www.anti-gravitydesign.com