午夜国产狂喷潮在线观看|国产AⅤ精品一区二区久久|中文字幕AV中文字幕|国产看片高清在线

    深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解
    來源:易賢網(wǎng) 閱讀:742 次 日期:2015-03-11 15:38:54
    溫馨提示:易賢網(wǎng)小編為您整理了“深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解”,方便廣大網(wǎng)友查閱!

    這篇文章主要介紹了深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解,本文講解了一般理論、按值傳遞、按引用傳遞、按共享傳遞(Call by sharing)、按共享傳遞是按值傳遞的特例等內(nèi)容,需要的朋友可以參考下

    介紹

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

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

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

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

    一般理論

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

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

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

    嚴(yán)格的傳參策略也分為幾種子策略,其中最重要的一些策略我們在本章詳細(xì)討論。

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

    按值傳遞

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

    代碼如下:

    bar = 10

    procedure foo(barArg):

    barArg = 20;

    end

    foo(bar)

    // foo內(nèi)部改變值不會影響內(nèi)部的bar的值

    print(bar) // 10

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

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

    代碼如下:

    // 注:以下都是偽代碼,不是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)

    // 按值傳遞,外部的對象不被改變

    print(bar) // {x: 10, y: 20}

    // 完全改變對象(賦新值)

    foo(bar, true)

    //也沒有改變

    print(bar) // {x: 10, y: 20}, 而不是{z: 1, q: 2}

    按引用傳遞

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

    偽代碼:

    代碼如下:

    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

    }

    // 按引用調(diào)用的結(jié)果如下:

    foo(bar)

    // 對象的屬性值已經(jīng)被改變了

    print(bar) // {x: 100, y: 200}

    // 重新賦新值也影響到了該對象

    foo(bar, true)

    // 此刻該對象已經(jīng)是一個新對象了

    print(bar) // {z: 1, q: 2}

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

    按共享傳遞(Call by sharing)

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

    這個策略還有一些代名詞:“按對象傳遞”或“按對象共享傳遞”。

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

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

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

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

    代碼如下:

    procedure foo(barArg, isFullChange):

    if isFullChange:

    barArg = {z: 1, q: 2}

    exit

    end

    barArg.x = 100

    barArg.y = 200

    end

    //還是使用這個對象結(jié)構(gòu)

    bar = {

    x: 10,

    y: 20

    }

    // 按貢獻(xiàn)傳遞會影響對象

    foo(bar)

    // 對象的屬性被修改了

    print(bar) // {x: 100, y: 200}

    // 重新賦值沒有起作用

    foo(bar, true)

    // 依然是上面的值

    print(bar) // {x: 100, y: 200}

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

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

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

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

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

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

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

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

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

    按共享與指針

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

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

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

    代碼如下:

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

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

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

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

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

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

    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]

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

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

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

    代碼如下:

    bar = {z: 1, q: 2};

    alert([foo.x, foo.y]); // [100, 200] – 沒改變

    alert([bar.z, bar.q]); // [1, 2] – 但現(xiàn)在引用的是新對象

    即現(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)一下,這里所說對象的值是地址(address),而不是對象結(jié)構(gòu)本身,將變量賦值給另外一個變量——是賦值值的引用。因此兩個變量引用的是同一個內(nèi)存地址。下一個賦值卻是新地址,是解析與舊對象的地址綁定,然后綁定到新對象的地址上,這就是和按引用傳遞的最重要區(qū)別。

    此外,如果只考慮ECMA-262標(biāo)準(zhǔn)所提供的抽象層次,我們在算法里看到的只有“值”這個概念,實(shí)現(xiàn)傳遞的“值”(可以是原始值,也可以是對象),但是按照我們上面的定義,也可以完全稱之為“按值傳遞”,因?yàn)橐玫刂芬彩侵怠?/P>

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

    術(shù)語版本

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

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

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

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

    結(jié)論

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

    更多信息請查看IT技術(shù)專欄

    更多信息請查看腳本欄目
    由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

    2025國考·省考課程試聽報名

    • 報班類型
    • 姓名
    • 手機(jī)號
    • 驗(yàn)證碼
    關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 新媒體/短視頻平臺 | 手機(jī)站點(diǎn) | 投訴建議
    工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
    云南網(wǎng)警備案專用圖標(biāo)
    聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
    咨詢QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)
    云南網(wǎng)警報警專用圖標(biāo)