在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 求值策略
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

求值策略

介紹

本章,我們將講解在 ECMAScript 向函數(shù) function 傳遞參數(shù)的策略。

計(jì)算機(jī)科學(xué)里對(duì)這種策略一般稱為“evaluation strategy”(大叔注:有的人說翻譯成求值策略,有的人翻譯成賦值策略,通看下面的內(nèi)容,我覺得稱為賦值策略更為恰當(dāng),anyway,標(biāo)題還是寫成大家容易理解的求值策略吧),例如在編程語言為求值或者計(jì)算表達(dá)式設(shè)置規(guī)則。向函數(shù)傳遞參數(shù)的策略是一個(gè)特殊的 case。

寫這篇文章的原因是因?yàn)檎搲嫌腥艘鬁?zhǔn)確解釋一些傳參的策略,我們這里給出了相應(yīng)的定義,希望對(duì)大家有所幫助。

很多程序員都確信在 JavaScript 中(甚至其它一些語言),對(duì)象是按引用傳參,而原始值類型按值傳參,此外,很多文章都說到這個(gè)“事實(shí)”,但有多人真正理解這個(gè)術(shù)語,而且又有多少是正確的?我們本篇講逐一講解。

一般理論

需要注意到,在賦值理論里一般有 2 中賦值策略:嚴(yán)格——意思是說參數(shù)在進(jìn)入程序之前是經(jīng)過計(jì)算過的;非嚴(yán)格——意思是參數(shù)的計(jì)算是根據(jù)計(jì)算要求才去計(jì)算(也就是相當(dāng)于延遲計(jì)算)。

然后,這里我們考慮基本的函數(shù)傳參策略,從 ECMAScript 出發(fā)點(diǎn)來說是非常重要的。首先需要注意的是,在 ECMAScript 中(甚至其他的語如C,JAVA,Python 和 Ruby 中)都使用了嚴(yán)格的參數(shù)傳遞策略。

另外傳遞參數(shù)的計(jì)算順序也是很重要的——在 ECMAScript 是左到右,而且其它語言實(shí)現(xiàn)的反省順序(從右向做)也是可以用的。

嚴(yán)格的傳參策略也分為幾種子策略,其中最重要的一些策略我們?cè)诒菊略敿?xì)討論。

下面討論的策略不是全部都用在 ECMAScript 中,所以在討論這些策略的具體行為的時(shí)候,我們使用了偽代碼來展示。

按值傳遞

按值傳遞,很多開發(fā)人員都很了解了,參數(shù)的值是調(diào)用者傳遞的對(duì)象值的拷貝(copy of value),函數(shù)內(nèi)部改變參數(shù)的值不會(huì)影響到外面的對(duì)象(該參數(shù)在外面的值),一般來說,是重新分配了新內(nèi)存(我們不關(guān)注分配內(nèi)存是怎么實(shí)現(xiàn)的——也是是棧也許是動(dòng)態(tài)內(nèi)存分配),該新內(nèi)存塊的值是外部對(duì)象的拷貝,并且它的值是用到函數(shù)內(nèi)部的。

bar = 10
procedure foo(barArg):
  barArg = 20;
end
foo(bar)
// foo內(nèi)部改變值不會(huì)影響內(nèi)部的bar的值
print(bar) // 10

但是,如果該函數(shù)的參數(shù)不是原始值而是復(fù)雜的結(jié)構(gòu)對(duì)象是時(shí)候,將帶來很大的性能問題,C++就有這個(gè)問題,將結(jié)構(gòu)作為值傳進(jìn)函數(shù)的時(shí)候——就是完整的拷貝。

我們來給一個(gè)一般的例子,用下面的賦值策略來檢驗(yàn)一下,想想一下一個(gè)函數(shù)接受 2 個(gè)參數(shù),第 1 個(gè)參數(shù)是對(duì)象的值,第 2 個(gè)是個(gè)布爾型的標(biāo)記,用來標(biāo)記是否完全修改傳入的對(duì)象(給對(duì)象重新賦值),還是只修改該對(duì)象的一些屬性。

// 注:以下都是偽代碼,不是JS實(shí)現(xiàn)
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)
// 按值傳遞,外部的對(duì)象不被改變
print(bar) // {x: 10, y: 20}
// 完全改變對(duì)象(賦新值)
foo(bar, true)
//也沒有改變
print(bar) // {x: 10, y: 20}, 而不是{z: 1, q: 2}

按引用傳遞

另外一個(gè)眾所周知的按引用傳遞接收的不是值拷貝,而是對(duì)象的隱式引用,如該對(duì)象在外部的直接引用地址。函數(shù)內(nèi)部對(duì)參數(shù)的任何改變都是影響該對(duì)象在函數(shù)外部的值,因?yàn)閮烧咭玫氖峭粋€(gè)對(duì)象,也就是說:這時(shí)候參數(shù)就相當(dāng)于外部對(duì)象的一個(gè)別名。

偽代碼:

procedure foo(barArg, isFullChange):
  if isFullChange:
    barArg = {z: 1, q: 2}
    exit
  end
  barArg.x = 100
  barArg.y = 200
end
// 使用和上例相同的對(duì)象
bar = {
  x: 10,
  y: 20
}
// 按引用調(diào)用的結(jié)果如下: 
foo(bar)
// 對(duì)象的屬性值已經(jīng)被改變了
print(bar) // {x: 100, y: 200}
// 重新賦新值也影響到了該對(duì)象
foo(bar, true)
// 此刻該對(duì)象已經(jīng)是一個(gè)新對(duì)象了
print(bar) // {z: 1, q: 2}

該策略可以更有效地傳遞復(fù)雜對(duì)象,例如帶有大批量屬性的大結(jié)構(gòu)對(duì)象。

按共享傳遞(Call by sharing)

上面 2 個(gè)策略大家都是知道的,但這里要講的一個(gè)策略可能大家不太了解(其實(shí)是學(xué)術(shù)上的策略)。但是,我們很快就會(huì)看到這正是它在 ECMAScript 中的參數(shù)傳遞戰(zhàn)略中起著關(guān)鍵作用的策略。

這個(gè)策略還有一些代名詞:“按對(duì)象傳遞”或“按對(duì)象共享傳遞”。

該策略是 1974 年由 Barbara Liskov 為 CLU 編程語言提出的。

該策略的要點(diǎn)是:函數(shù)接收的是對(duì)象對(duì)于的拷貝(副本),該引用拷貝和形參以及其值相關(guān)聯(lián)。

這里出現(xiàn)的引用,我們不能稱之為“按引用傳遞”,因?yàn)楹瘮?shù)接收的參數(shù)不是直接的對(duì)象別名,而是該引用地址的拷貝。

最重要的區(qū)別就是:函數(shù)內(nèi)部給參數(shù)重新賦新值不會(huì)影響到外部的對(duì)象(和上例按引用傳遞的 case),但是因?yàn)樵搮?shù)是一個(gè)地址拷貝,所以在外面訪問和里面訪問的都是同一個(gè)對(duì)象(例如外部的該對(duì)象不是想按值傳遞一樣完全的拷貝),改變?cè)搮?shù)對(duì)象的屬性值將會(huì)影響到外部的對(duì)象。

procedure foo(barArg, isFullChange):
  if isFullChange:
    barArg = {z: 1, q: 2}
    exit
  end
  barArg.x = 100
  barArg.y = 200
end
//還是使用這個(gè)對(duì)象結(jié)構(gòu)
bar = {
  x: 10,
  y: 20
}
// 按貢獻(xiàn)傳遞會(huì)影響對(duì)象 
foo(bar)
// 對(duì)象的屬性被修改了
print(bar) // {x: 100, y: 200}
// 重新賦值沒有起作用
foo(bar, true)
// 依然是上面的值
print(bar) // {x: 100, y: 200}

這個(gè)處理的假設(shè)前提是大多數(shù)語言里用到的對(duì)象,而不是原始值。

按共享傳遞是按值傳遞的特例

按共享傳遞這個(gè)策略很很多語言里都使用了:Java,ECMAScript,Python,Ruby,Visual Basic 等。此外,Python 社區(qū)已經(jīng)使用了這個(gè)術(shù)語,至于其他語言也可以用這個(gè)術(shù)語,因?yàn)槠渌拿Q往往會(huì)讓大家感覺到混亂。大多數(shù)情況下,例如在 Java,ECMAScript 或 Visual Basic 中,這一策略也稱之為按值傳遞——意味著:特殊值——引用拷貝(副本)。

一方面,它是這樣的——傳遞給函數(shù)內(nèi)部用的參數(shù)僅僅是綁定值(引用地址)的一個(gè)名稱,并不會(huì)影響外部的對(duì)象。

另一方面,如果不深入研究,這些術(shù)語真的被認(rèn)為吃錯(cuò)誤的,因?yàn)楹芏嗾搲荚谡f如何將對(duì)象傳遞給 JavaScript 函數(shù))。

一般理論確實(shí)有按值傳遞的說法:但這時(shí)候這個(gè)值就是我們所說的地址拷貝(副本),因此并沒喲破壞規(guī)則。

在 Ruby 中,這個(gè)策略稱為按引用傳遞。再說一下:它不是按照大結(jié)構(gòu)的拷貝來傳遞(例如,不是按值傳遞),而另一方面,我們沒有處理原始對(duì)象的引用,并且不能修改它;因此,這個(gè)跨術(shù)語的概念可能更會(huì)造成混亂。

理論里沒有像按值傳遞的特殊 case 一樣來面試按引用傳遞的特殊 case。

但依然有必要了解這些策略在上述提到的技術(shù)中(Java,ECMAScript,Python,Ruby,other),實(shí)際上——他們用的策略就是按共享傳遞。

按共享與指針

對(duì)于 С/С++,這個(gè)策略在思想上和按指針值傳遞是一樣的,但有一個(gè)重要的區(qū)別——該策略可以取消引用指針以及完全改變對(duì)象。但在一般情況下,分配一個(gè)值(地址)指針到新的內(nèi)存塊(即之前引用的內(nèi)存塊保持不變);通過指針改變對(duì)象屬性的話會(huì)影響阿東外部對(duì)象。

因此,和指針類別,我們可以明顯看到,這是按地址值傳遞。 在這種情況下,按共享傳遞只是“語法糖”,像指針賦值行為一樣(但不能取消引用),或者像引用一樣修改屬性(不需要取消引用操作),有時(shí)候,它可以被命名為“安全指針”。

然而,С/С++如果在沒有明顯指針的解引用的情況下,引用對(duì)象屬性的時(shí)候,還具有特殊的語法糖:

obj->x instead of (*obj).x

和 C++關(guān)系最為緊密的這種意識(shí)形態(tài)可以從“智能指針”的實(shí)現(xiàn)中看到,例如,在 boost :: shared_ptr 里,重載了賦值操作符以及拷貝構(gòu)造函數(shù),而且還使用了對(duì)象的引用計(jì)數(shù)器,通過GC刪除對(duì)象。 這種數(shù)據(jù)類型,甚至有類似的名字共享_ptr。

ECMAScript 實(shí)現(xiàn)

現(xiàn)在我們知道了 ECMAScript 中將對(duì)象作為參數(shù)傳遞的策略了——按共享傳遞:修改參數(shù)的屬性將會(huì)影響到外部,而重新賦值將不會(huì)影響到外部對(duì)象。但是,正如我們上面提到的,其中的 ECMAScript 開發(fā)人員一般都稱之為是:按值傳遞,只不過該值是引用地址的拷貝。

JavaScript 發(fā)明人布倫丹·艾希也寫到了:傳遞的是引用的拷貝(地址副本)。所以論壇里大家曾說的按值傳遞,在這種解釋下,也是對(duì)的。

更確切地說,這種行為可以理解為簡單的賦值,我們可以看到,內(nèi)部是完全不同的對(duì)象,只不過引用的是相同的值——也就是地址副本。

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]

即兩個(gè)標(biāo)識(shí)符(名稱綁定)綁定到內(nèi)存中的同一個(gè)對(duì)象, 共享這個(gè)對(duì)象:

foo value: addr(0xFF) => {x: 100, y: 200} (address 0xFF) <= bar value: addr(0xFF)

而重新賦值分配,綁定是新的對(duì)象標(biāo)識(shí)符(新地址),而不影響已經(jīng)先前綁定的對(duì)象 :

bar = {z: 1, q: 2};
alert([foo.x, foo.y]); // [100, 200] – 沒改變
alert([bar.z, bar.q]); // [1, 2] – 但現(xiàn)在引用的是新對(duì)象

即現(xiàn)在 foo 和 bar,有不同的值和不同的地址:

foo value: addr(0xFF) => {x: 100, y: 200} (address 0xFF)
bar value: addr(0xFA) => {z: 1, q: 2} (address 0xFA)

再強(qiáng)調(diào)一下,這里所說對(duì)象的值是地址(address),而不是對(duì)象結(jié)構(gòu)本身,將變量賦值給另外一個(gè)變量——是賦值值的引用。因此兩個(gè)變量引用的是同一個(gè)內(nèi)存地址。下一個(gè)賦值卻是新地址,是解析與舊對(duì)象的地址綁定,然后綁定到新對(duì)象的地址上,這就是和按引用傳遞的最重要區(qū)別。

此外,如果只考慮 ECMA-262 標(biāo)準(zhǔn)所提供的抽象層次,我們?cè)谒惴ɡ锟吹降闹挥小爸怠边@個(gè)概念,實(shí)現(xiàn)傳遞的“值”(可以是原始值,也可以是對(duì)象),但是按照我們上面的定義,也可以完全稱之為“按值傳遞”,因?yàn)橐玫刂芬彩侵怠?/p>

然而,為了避免誤解(為什么外部對(duì)象的屬性可以在函數(shù)內(nèi)部改變),這里依然需要考慮實(shí)現(xiàn)層面的細(xì)節(jié)——我們看到的按共享傳遞,或者換句話講——按安全指針傳遞,而安全指針不可能去解除引用和改變對(duì)象的,但可以去修改該對(duì)象的屬性值。

術(shù)語版本

讓我們來定義 ECMAScript 中該策略的術(shù)語版本。

可以稱之為“按值傳遞”——這里所說的值是一個(gè)特殊的 case,也就是該值是地址副本(address copy)。從這個(gè)層面我們可以說:ECMAScript 中除了異常之外的對(duì)象都是按值傳遞的,這實(shí)際上是 ECMAScript 抽象的層面。

或針對(duì)這種情況下,專門稱之為“按共享傳遞”,通過這個(gè)正好可以看到傳統(tǒng)的按值傳遞和按引用傳遞的區(qū)別,這種情況,可以分成 2 種情況:1、原始值按值傳遞;2、對(duì)象按共享傳遞。

“通過引用類型將對(duì)象到函數(shù)”這句話和 ECMAScript 無關(guān),而且它是錯(cuò)誤的。

結(jié)論

我希望這篇文章有助于宏觀上了解更多細(xì)節(jié),以及在 ECMAScript 中的實(shí)現(xiàn)。一如既往,如果有任何問題,歡迎討論。

其它參考