介紹
本章,我們將說明在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中,此策略也稱之為按值傳遞-意味著:特殊值-引用拷貝(副本)。
一方面,它是這樣的-傳遞給函數內部用的參數只是綁定值(引用位址)的一個名稱,並不會影響外部的物件。
另一方面,如果不深入研究,這些術語真的被認為吃錯誤的,因為很多論壇都在說如何將物件傳遞給JavaScript函數)。
一般理論確實有按值傳遞的說法:但這時候這個值就是我們所說的位址拷貝(副本),因此並沒喲破壞規則。
在Ruby中,這個策略稱為依引用傳遞。再說一下:它不是按照大結構的拷貝來傳遞(例如,不是按值傳遞),而另一方面,我們沒有處理原始對象的引用,並且不能修改它;因此,這個跨術語的概念可能更會造成混亂。
理論裡沒有像按值傳遞的特殊case一樣來面試按引用傳遞的特殊case。
但仍有必要了解這些策略在上述提到的技術中(Java, ECMAScript, Python, Ruby, other),實際上-他們使用的策略就是依共享傳遞。
依共用與指標
對於С/С ,這個策略在思想上和按指針值傳遞是一樣的,但有一個重要的區別——該策略可以取消引用指針以及完全改變對象。但在一般情況下,分配一個值(位址)指標到新的記憶體區塊(即先前引用的記憶體區塊保持不變);透過指標改變物件屬性的話會影響阿東外部物件。
因此,和指標類別,我們可以明顯看到,這是按位址值傳遞。 在這種情況下,按共享傳遞只是“語法糖”,像指針賦值行為一樣(但不能取消引用),或者像引用一樣修改屬性(不需要取消引用操作),有時候,它可以被命名為“安全指針」。
然而,С/С 如果在沒有明顯指標的解引用的情況下,引用物件屬性的時候,還具有特殊的語法糖:
obj->x instead of (*obj).x
和C 關係最緊密的這種意識形態可以從「智慧指標」的實現中看到,例如,在boost :: shared_ptr裡,重載了賦值操作符以及拷貝建構函數,而且還使用了物件的引用計數器,透過GC刪除對象。 這種資料類型,甚至有類似的名字- 共享_ptr。
ECMAScript實作
現在我們知道了ECMAScript中將物件作為參數傳遞的策略了-依共用傳遞:修改參數的屬性將會影響到外部,而重新賦值將不會影響外部物件。但是,正如我們上面提到的,其中的ECMAScript開發人員一般都稱之為是:按值傳遞,只不過該值是引用地址的拷貝。
JavaScript發明者布倫丹·艾希也寫到了:傳遞的是引用的拷貝(地址副本)。所以論壇裡大家曾說的值傳遞,在這種解釋下,也是對的。
更確切地說,這種行為可以理解為簡單的賦值,我們可以看到,內部是完全不同的對象,只不過引用的是相同的值——也就是地址副本。
ECMAScript程式碼:
var foo = {x: 10, y: 20};
var bar = foo;
alert(bar === foo); // true
bar.x = 100;
bar.y = 200;
alert([foo.x, foo.y]); // [100, 200]
即兩個識別符(名稱綁定)綁定到記憶體中的同一個對象, 共享這個對象:
foo value: addr(0xFF) => {x: 100, y: 200} (address 0xFF)
而重新賦值分配,綁定是新的物件識別碼(新位址),而不影響已經先前綁定的物件 :
bar = {z: 1, q: 2};
alert([foo.x, foo.y]); // [100, 200] – 沒改變
alert([bar.z, bar.q]); // [1, 2] – 但現在引用的是新物件
即現在foo和 bar,有不同的值和不同的位址:
foo value: addr(0xFF) => {x: 100, y: 200} (address 0xFF)
bar value: addr(0xFA) => {z: 1, q: 2} (address 0xFA)
再強調一下,這裡所說物件的值是位址(address),而不是物件結構本身,將變數賦值給另外一個變數──是賦值值的參考。因此兩個變數引用的是同一個記憶體位址。下一個賦值卻是新位址,是解析與舊物件的位址綁定,然後綁定到新物件的位址上,這就是和按引用傳遞的最重要差異。
此外,如果只考慮ECMA-262標準所提供的抽象層次,我們在演算法裡看到的只有「值」這個概念,實現傳遞的「值」(可以是原始值,也可以是物件),但是按照我們上面的定義,也可以完全稱之為“按值傳遞”,因為引用地址也是值。
然而,為了避免誤解(為什麼外部物件的屬性可以在函數內部改變),這裡依然需要考慮實現層面的細節——我們看到的按共享傳遞,或者換句話說——按安全指標傳遞,而安全性指標不可能去解除引用和改變物件的,但可以去修改該物件的屬性值。
術語版本
讓我們來定義ECMAScript中該策略的術語版本。
可以稱為「以值傳遞」-這裡所說的值是一個特殊的case,也就是該值是地址副本(address copy)。從這個層面我們可以說:ECMAScript中除了異常之外的物件都是按值傳遞的,這實際上是ECMAScript抽象的層面。
或針對這種情況下,專門稱之為“按共享傳遞”,透過這個正好可以看到傳統的按值傳遞和按引用傳遞的區別,這種情況,可以分成2個種情況:1 :原始值按值傳遞;2:物件按共享傳遞。
「透過引用型別將物件到函數」這句話和ECMAScript無關,而且它是錯的。
結論
我希望這篇文章有助於宏觀了解更多細節,以及在ECMAScript中的實作。一如既往,如果有任何問題,歡迎討論。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

禪工作室 13.0.1
強大的PHP整合開發環境

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

SublimeText3漢化版
中文版,非常好用

Atom編輯器mac版下載
最受歡迎的的開源編輯器