搜尋
首頁web前端js教程深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解_基礎知識

介紹

本章,我們將說明在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中的實作。一如既往,如果有任何問題,歡迎討論。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript的角色:使網絡交互和動態JavaScript的角色:使網絡交互和動態Apr 24, 2025 am 12:12 AM

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

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

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

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

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

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

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

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

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

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

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

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

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

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

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

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

MantisBT

MantisBT

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

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器