国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

翻譯連載 | JavaScript輕量級(jí)函數(shù)式編程-第6章:值的不可變性 |《你不知道的JS》姊妹篇

ysl_unh / 2411人閱讀

摘要:但在開始之前應(yīng)該心中有數(shù)值的不可變性并不是說(shuō)我們不能在程序編寫時(shí)不改變某個(gè)值。這些都是對(duì)值的不可變這個(gè)概念的誤解。程序的其他部分不會(huì)影響的賦值。

原文地址:Functional-Light-JS

原文作者:Kyle Simpson-《You-Dont-Know-JS》作者

關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),是 JavaScript 中最嚴(yán)謹(jǐn)?shù)倪壿嫛=?jīng)過(guò)捶打磨練,成就了本書的中文版。本書包含了函數(shù)式編程之精髓,希望可以幫助大家在學(xué)習(xí)函數(shù)式編程的道路上走的更順暢。比心。

譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao

第 6 章:值的不可變性

在第 5 章中,我們探討了減少副作用的重要性:副作用是引起程序意外狀態(tài)改變的原因,同時(shí)也可能會(huì)帶來(lái)意想不到的驚喜(bugs)。這樣的暗雷在程序中出現(xiàn)的越少,開發(fā)者對(duì)程序的信心無(wú)疑就會(huì)越強(qiáng),同時(shí)代碼的可讀性也會(huì)越高。本章的主題,將繼續(xù)朝減少程序副作用的方向努力。

如果編程風(fēng)格冪等性是指定義一個(gè)數(shù)據(jù)變更操作以便只影響一次程序狀態(tài),那么現(xiàn)在我們將注意力轉(zhuǎn)向?qū)⑦@個(gè)影響次數(shù)從 1 降為 0。

現(xiàn)在我們開始探索值的不可變性,即只在我們的程序中使用不可被改變的數(shù)據(jù)。

原始值的不可變性

原始數(shù)據(jù)類型(numberstringbooleannullundefined)本身就是不可變的;無(wú)論如何你都沒(méi)辦法改變它們。

// 無(wú)效,且毫無(wú)意義
2 = 2.5;

然而 JS 確實(shí)有一個(gè)特性,使得看起來(lái)允許我們改變?cè)紨?shù)據(jù)類型的值, 即“boxing”特性。當(dāng)你訪問(wèn)原始類型數(shù)據(jù)時(shí) —— 特別是 numberstringboolean —— 在這種情況下,JS 會(huì)自動(dòng)的把它們包裹(或者說(shuō)“包裝”)成這個(gè)值對(duì)應(yīng)的對(duì)象(分別是 NumberString 以及 Boolean)。

思考下面的代碼:

var x = 2;

x.length = 4;

x;                // 2
x.length;        // undefined

數(shù)值本身并沒(méi)有可用的 length 屬性,因此 x.length = 4 這個(gè)賦值操作正試圖添加一個(gè)新的屬性,不過(guò)它靜默地失敗了(也可以說(shuō)是這個(gè)操作被忽略了或被拋棄了,這取決于你怎么看);變量 x 繼續(xù)承載那個(gè)簡(jiǎn)單的原始類型數(shù)據(jù) —— 數(shù)值 2

但是 JS 允許 x.length = 4 這條語(yǔ)句正常執(zhí)行的事實(shí)著實(shí)令人困惑。如果這種現(xiàn)象真的無(wú)緣無(wú)故出現(xiàn),那么代碼的閱讀者無(wú)疑會(huì)摸不著頭腦。好消息是,如果你使用了嚴(yán)格模式("use strict";),那么這條語(yǔ)句就會(huì)拋出異常了。

那么如果嘗試改變那些明確被包裝成對(duì)象的值呢?

var x = new Number( 2 );

// 沒(méi)問(wèn)題
x.length = 4;

這段代碼中的 x 保存了一個(gè)對(duì)象的引用,因此可以正常地添加或修改自定義屬性。

number 這樣的原始數(shù)型,值的不可變性看起來(lái)相當(dāng)明顯,但字符串呢?JS 開發(fā)者有個(gè)共同的誤解 —— 字符串和數(shù)組很像,所以應(yīng)該是可變的。JS 使用 [] 訪問(wèn)字符串成員的語(yǔ)法甚至還暗示字符串真的就像數(shù)組。不過(guò),字符串的確是不可變的:

var s = "hello";

s[1];                // "e"

s[1] = "E";
s.length = 10;

s;                    // "hello"

盡管可以使用 s[1] 來(lái)像訪問(wèn)數(shù)組元素一樣訪問(wèn)字符串成員,JS 字符串也并不是真的數(shù)組。s[1] = "E"s.length = 10 這兩個(gè)賦值操作都是失敗的,就像剛剛的 x.length = 4 一樣。在嚴(yán)格模式下,這些賦值都會(huì)拋出異常,因?yàn)?1length 這兩個(gè)屬性在原始數(shù)據(jù)類型字符串中都是只讀的。

有趣的是,即便是包裝后的 String 對(duì)象,其值也會(huì)(在大部分情況下)表現(xiàn)的和非包裝字符串一樣 —— 在嚴(yán)格模式下如果改變已存在的屬性,就會(huì)拋出異常:

"use strict";

var s = new String( "hello" );

s[1] = "E";            // error
s.length = 10;        // error

s[42] = "?";        // OK

s;                    // "hello"
從值到值

我們將在本節(jié)詳細(xì)展開從值到值這個(gè)概念。但在開始之前應(yīng)該心中有數(shù):值的不可變性并不是說(shuō)我們不能在程序編寫時(shí)不改變某個(gè)值。如果一個(gè)程序的內(nèi)部狀態(tài)從始至終都保持不變,那么這個(gè)程序肯定相當(dāng)無(wú)趣!它同樣不是指變量不能承載不同的值。這些都是對(duì)值的不可變這個(gè)概念的誤解。

值的不可變性是指當(dāng)需要改變程序中的狀態(tài)時(shí),我們不能改變已存在的數(shù)據(jù),而是必須創(chuàng)建和跟蹤一個(gè)新的數(shù)據(jù)。

例如:

function addValue(arr) {
    var newArr = [ ...arr, 4 ];
    return newArr;
}

addValue( [1,2,3] );    // [1,2,3,4]

注意我們沒(méi)有改變數(shù)組 arr 的引用,而是創(chuàng)建了一個(gè)新的數(shù)組(newArr),這個(gè)新數(shù)組包含數(shù)組 arr 中已存在的值,并且新增了一個(gè)新值 4

使用我們?cè)诘?5 章討論的副作用的相關(guān)概念來(lái)分析 addValue(..)。它是純的嗎?它是否具有引用透明性?給定相同的數(shù)組作為輸入,它會(huì)永遠(yuǎn)返回相同的輸出嗎?它無(wú)副作用嗎?答案是肯定的。

設(shè)想這個(gè)數(shù)組 [1, 2, 3], 它是由先前的操作產(chǎn)生,并被我們保存在一個(gè)變量中,它代表著程序當(dāng)前的狀態(tài)。我們想要計(jì)算出程序的下一個(gè)狀態(tài),因此調(diào)用了 addValue(..)。但是我們希望下一個(gè)狀態(tài)計(jì)算的行為是直接的和明確的,所以 addValue(..) 操作簡(jiǎn)單的接收一個(gè)直接輸入,返回一個(gè)直接輸出,并通過(guò)不改變 arr 引用的原始數(shù)組來(lái)避免副作用。

這就意味著我們既可以計(jì)算出新?tīng)顟B(tài) [1, 2, 3, 4],也可以掌控程序的狀態(tài)變換。程序不會(huì)出現(xiàn)過(guò)早的過(guò)渡到這個(gè)狀態(tài)或完全轉(zhuǎn)變到另一個(gè)狀態(tài)(如 [1, 2, 3, 5])這樣的意外情況。通過(guò)規(guī)范我們的值并把它視為不可變的,我們大幅減少了程序錯(cuò)誤,使我們的程序更易于閱讀和推導(dǎo),最終使程序更加可信賴。

arr 所引用的數(shù)組是可變的,只是我們選擇不去改變他,我們實(shí)踐了值不可變的這一精神。

同樣的,可以將“以拷貝代替改變”這樣的策略應(yīng)用于對(duì)象,思考下面的代碼:

function updateLastLogin(user) {
    var newUserRecord = Object.assign( {}, user );
    newUserRecord.lastLogin = Date.now();
    return newUserRecord;
}

var user = {
    // ..
};

user = updateLastLogin( user );
消除本地影響

下面的代碼能夠體現(xiàn)不可變性的重要性:

var arr = [1,2,3];

foo( arr );

console.log( arr[0] );

從表面上講,你可能認(rèn)為 arr[0] 的值仍然為 1。但事實(shí)是否如此不得而知,因?yàn)?foo(..) 可能會(huì)改變你傳入其中的 arr 所引用的數(shù)組。

在之前的章節(jié)中,我們已經(jīng)見(jiàn)到過(guò)用下面這種帶有欺騙性質(zhì)的方法來(lái)避免意外:

var arr = [1,2,3];

foo( arr.slice() );            // 哈!一個(gè)數(shù)組副本!

console.log( arr[0] );        // 1

當(dāng)然,使得這個(gè)斷言成立的前提是 foo 函數(shù)不會(huì)忽略我們傳入的參數(shù)而直接通過(guò)相同的 arr 這個(gè)自由變量詞法引用來(lái)訪問(wèn)源數(shù)組。

對(duì)于防止數(shù)據(jù)變化負(fù)面影響,稍后我們會(huì)討論另一種策略。

重新賦值

在進(jìn)入下一個(gè)段落之前先思考一個(gè)問(wèn)題 —— 你如何描述“常量”?

你可能會(huì)脫口而出“一個(gè)不能改變的值就是常量”,“一個(gè)不能被改變的變量”等等。這些回答都只能說(shuō)接近正確答案,但卻并不是正確答案。對(duì)于常量,我們可以給出一個(gè)簡(jiǎn)潔的定義:一個(gè)無(wú)法進(jìn)行重新賦值(reassignment)的變量。

我們剛剛在“常量”概念上的吹毛求疵其實(shí)是很有必要的,因?yàn)樗吻辶顺A颗c值無(wú)關(guān)的事實(shí)。無(wú)論常量承載何值,該變量都不能使用其他的值被進(jìn)行重新賦值。但它與值的本質(zhì)無(wú)關(guān)。

思考下面的代碼:

var x = 2;

我們剛剛討論過(guò),數(shù)據(jù) 2 是一個(gè)不可變的原始值。如果將上面的代碼改為:

const x = 2;

const 關(guān)鍵字的出現(xiàn),作為“常量聲明”被大家熟知,事實(shí)上根本沒(méi)有改變 2 的本質(zhì),因?yàn)樗旧砭鸵呀?jīng)不可改變了。

下面這行代碼會(huì)拋出錯(cuò)誤,這無(wú)可厚非:

// 嘗試改變 x,祝我好運(yùn)!
x = 3;        // 拋出錯(cuò)誤!

但再次重申,我們并不是要改變這個(gè)數(shù)據(jù),而是要對(duì)變量 x 進(jìn)行重新賦值。數(shù)據(jù)被卷進(jìn)來(lái)純屬偶然。

為了證明 const 和值的本質(zhì)無(wú)關(guān),思考下面的代碼:

const x = [ 2 ];

這個(gè)數(shù)組是一個(gè)常量嗎?并不是。 x 是一個(gè)常量,因?yàn)樗鼰o(wú)法被重新賦值。但下面的操作是完全可行的:

x[0] = 3;

為何?因?yàn)楸M管 x 是一個(gè)常量,數(shù)組卻是可變的。

關(guān)于 const 關(guān)鍵字和“常量”只涉及賦值而不涉及數(shù)據(jù)語(yǔ)義的特性是個(gè)又臭又長(zhǎng)的故事。幾乎所有語(yǔ)言的高級(jí)開發(fā)者都踩 const 地雷。事實(shí)上,Java 最終不贊成使用 const 并引入了一個(gè)全新的關(guān)鍵詞 final 來(lái)區(qū)分“常量”這個(gè)語(yǔ)義。

拋開混亂之后開始思考,如果 const 并不能創(chuàng)建一個(gè)不可變的值,那么它對(duì)于函數(shù)式編程者來(lái)說(shuō)又還有什么重要的呢?

意圖

const 關(guān)鍵字可以用來(lái)告知閱讀你代碼的讀者該變量不會(huì)被重新賦值。作為一個(gè)表達(dá)意圖的標(biāo)識(shí),const 被加入 JavaScript 不僅常常受到稱贊,也普遍提高了代碼可讀性。

在我看來(lái),這是夸大其詞,這些說(shuō)法并沒(méi)有太大的實(shí)際意義。我只看到了使用這種方法來(lái)表明意圖的微薄好處。如果使用這種方法來(lái)聲明值的不可變性,與已使用幾十年的傳統(tǒng)方式相比,const 簡(jiǎn)直太弱了。

為了證明我的說(shuō)法,讓我們來(lái)做一個(gè)實(shí)踐。const 創(chuàng)建了一個(gè)在塊級(jí)作用域內(nèi)的變量,這意味著該變量只能在其所在的代碼塊中被訪問(wèn):

// 大量代碼

{
    const x = 2;

    // 少數(shù)幾行代碼
}

// 大量代碼

通常來(lái)說(shuō),代碼塊的最佳實(shí)踐是用于僅包裹少數(shù)幾行代碼的場(chǎng)景。如果你有一個(gè)包含了超過(guò) 10 行的代碼塊,那么大多數(shù)開發(fā)者會(huì)建議你重構(gòu)這一段代碼。因此 const x = 2 只作用于下面的9行代碼。

程序的其他部分不會(huì)影響 x 的賦值。

我要說(shuō)的是:上述程序的可讀性與下面這樣基本相同:

// 大量代碼

{
    let x = 2;

    // 少數(shù)幾行代碼
}

// 大量代碼

其實(shí)只要查看一下在 let x = 2; 之后的幾行代碼,就可以判斷出 x 這個(gè)變量是否被重新賦值過(guò)了。對(duì)我來(lái)說(shuō),“實(shí)際上不進(jìn)行重新賦值”相對(duì)“使用容易迷惑人的 const 關(guān)鍵字告訴讀者‘不要重新賦值’”是一個(gè)更明確的信號(hào)

此外,讓我們思考一下,乍看這段代碼起來(lái)可能給讀者傳達(dá)什么:

const magicNums = [1,2,3,4];

// ..

讀者可能會(huì)(錯(cuò)誤地)認(rèn)為,這里使用 const 的用意是你永遠(yuǎn)不會(huì)修改這個(gè)數(shù)組 —— 這樣的推斷對(duì)我來(lái)說(shuō)合情合理。想象一下,如果你的確允許 magicNums 這個(gè)變量所引用的數(shù)組被修改,那么這個(gè) const 關(guān)鍵詞就極具混淆性了 —— 的很確容易發(fā)生意外,不是嗎?

更糟糕的是,如果你在某處故意修改了 magicNums,但對(duì)讀者而言不夠明顯呢?讀者會(huì)在后面的代碼里(再次錯(cuò)誤地)認(rèn)為 magicNums 的值仍然是 [1, 2, 3, 4]。因?yàn)樗麄儾聹y(cè)你之前使用 const 的目的就是“這個(gè)變量不會(huì)改變”。

我認(rèn)為你應(yīng)該使用 varlet 來(lái)聲明那些你會(huì)去改變的變量,它們確實(shí)相比 const 來(lái)說(shuō)是一個(gè)更明確的信號(hào)

const 所帶來(lái)的問(wèn)題還沒(méi)講完。還記得我們?cè)诒菊麻_頭所說(shuō)的嗎?值的不可變性是指當(dāng)需要改變某個(gè)數(shù)據(jù)時(shí),我們不應(yīng)該直接改變它,而是應(yīng)該使用一個(gè)全新的數(shù)據(jù)。那么當(dāng)新數(shù)組創(chuàng)建出來(lái)后,你會(huì)怎么處理它?如果你使用 const 聲明變量來(lái)保存引用嗎,這個(gè)變量的確沒(méi)法被重新賦值了,那么……然后呢?

從這方面來(lái)講,我認(rèn)為 const 反而增加了函數(shù)式編程的困難度。我的結(jié)論是:const 并不是那么有用。它不僅造成了不必要的混亂,也以一種很不方便的形式限制了我們。我只用 const 來(lái)聲明簡(jiǎn)單的常量,例如:

const PI = 3.141592;

3.141592 這個(gè)值本身就已經(jīng)是不可變的,并且我也清楚地表示說(shuō)“PI 標(biāo)識(shí)符將始終被用于代表這個(gè)字面量的占位符”。對(duì)我來(lái)說(shuō),這才是 const 所擅長(zhǎng)的。坦白講,我在編碼時(shí)并不會(huì)使用很多這樣的聲明。

我寫過(guò)很多,也閱讀過(guò)很多 JavaScript 代碼,我認(rèn)為由于重新賦值導(dǎo)致大量的 bug 這只是個(gè)想象中的問(wèn)題,實(shí)際并不存在。

我們應(yīng)該擔(dān)心的,并不是變量是否被重新賦值,而是值是否會(huì)發(fā)生改變。為什么?因?yàn)橹凳强杀粩y帶的,但詞法賦值并不是。你可以向函數(shù)中傳入一個(gè)數(shù)組,這個(gè)數(shù)組可能會(huì)在你沒(méi)意識(shí)到的情況下被改變。但是你的其他代碼在預(yù)期之外重新給變量賦值,這是不可能發(fā)生的。

凍結(jié)

這是一種簡(jiǎn)單廉價(jià)的(勉強(qiáng))將像對(duì)象、數(shù)組、函數(shù)這樣的可變的數(shù)據(jù)轉(zhuǎn)為“不可變數(shù)據(jù)”的方式:

var x = Object.freeze( [2] );

Object.freeze(..) 方法遍歷對(duì)象或數(shù)組的每個(gè)屬性和索引,將它們?cè)O(shè)置為只讀以使之不會(huì)被重新賦值,事實(shí)上這和使用 const 聲明屬性相差無(wú)幾。Object.freeze(..) 也會(huì)將屬性標(biāo)記為“不可配置(non-reconfigurable)”,并且使對(duì)象或數(shù)組本身不可擴(kuò)展(即不會(huì)被添加新屬性)。實(shí)際上,而就可以將對(duì)象的頂層設(shè)為不可變。

注意,僅僅是頂層不可變!

var x = Object.freeze( [ 2, 3, [4, 5] ] );

// 不允許改變:
x[0] = 42;

// oops,仍然允許改變:
x[2][0] = 42;

Object.freeze(..) 提供淺層的、初級(jí)的不可變性約束。如果你希望更深層的不可變約束,那么你就得手動(dòng)遍歷整個(gè)對(duì)象或數(shù)組結(jié)構(gòu)來(lái)為所有后代成員應(yīng)用 Object.freeze(..)

const 相反,Object.freeze(..) 并不會(huì)誤導(dǎo)你,讓你得到一個(gè)“你以為”不可變的值,而是真真確確給了你一個(gè)不可變的值。

回顧剛剛的例子:

var arr = Object.freeze( [1,2,3] );

foo( arr );

console.log( arr[0] );            // 1

可以非常確定 arr[0] 就是 1

這是非常重要的,因?yàn)檫@可以使我們更容易的理解代碼,當(dāng)我們將值傳遞到我們看不到或者不能控制的地方,我們依然能夠相信這個(gè)值不會(huì)改變。

性能

每當(dāng)我們開始創(chuàng)建一個(gè)新值(數(shù)組、對(duì)象等)取代修改已經(jīng)存在的值時(shí),很明顯迎面而來(lái)的問(wèn)題就是:這對(duì)性能有什么影響?

如果每次想要往數(shù)組中添加內(nèi)容時(shí),我們都必須創(chuàng)建一個(gè)全新的數(shù)組,這不僅占用 CPU 時(shí)間并且消耗額外的內(nèi)存。不再存在任何引用的舊數(shù)據(jù)將會(huì)被垃圾回收機(jī)制回收;更多的 CPU 資源消耗。

這樣的取舍能接受嗎?視情況而定。對(duì)代碼性能的優(yōu)化和討論都應(yīng)該有個(gè)上下文

如果在你的程序中,只會(huì)發(fā)生一次或幾次單一的狀態(tài)變化,那么扔掉一個(gè)舊對(duì)象或舊數(shù)組完全沒(méi)必要擔(dān)心。性能損失會(huì)非常非常小 —— 頂多只有幾微秒 —— 對(duì)你的應(yīng)用程序影響甚小。追蹤和修復(fù)由于數(shù)據(jù)改變引起的 bug 可能會(huì)花費(fèi)你幾分鐘甚至幾小時(shí)的時(shí)間,這么看來(lái)那幾微秒簡(jiǎn)直沒(méi)有可比性。

但是,如果頻繁的進(jìn)行這樣的操作,或者這樣的操作出現(xiàn)在應(yīng)用程序的核心邏輯中,那么性能問(wèn)題 —— 即性能和內(nèi)存 —— 就有必要仔細(xì)考慮一下了。

以數(shù)組這樣一個(gè)特定的數(shù)據(jù)結(jié)構(gòu)來(lái)說(shuō),我們想要在每次操作這個(gè)數(shù)組時(shí)使每個(gè)更改都隱式地進(jìn)行,就像結(jié)果是一個(gè)新數(shù)組一樣,但除了每次都真的創(chuàng)建一個(gè)數(shù)組之外,還有什么其他辦法來(lái)完成這個(gè)任務(wù)呢?像數(shù)組這樣的數(shù)據(jù)結(jié)構(gòu),我們期望除了能夠保存其最原始的數(shù)據(jù),然后能追蹤其每次改變并根據(jù)之前的版本創(chuàng)建一個(gè)分支。

在內(nèi)部,它可能就像一個(gè)對(duì)象引用的鏈表樹,樹中的每個(gè)節(jié)點(diǎn)都表示原始值的改變。從概念上來(lái)說(shuō),這和 git 的版本控制原理類似。

想象一下使用這個(gè)假設(shè)的、專門處理數(shù)組的數(shù)據(jù)結(jié)構(gòu):

var state = specialArray( 1, 2, 3, 4 );

var newState = state.set( 42, "meaning of life" );

state === newState;                    // false

state.get( 2 );                        // 3
state.get( 42 );                    // undefined

newState.get( 2 );                    // 3
newState.get( 42 );                    // "meaning of life"

newState.slice( 1, 3 );                // [2,3]

specialArray(..) 這個(gè)數(shù)據(jù)結(jié)構(gòu)會(huì)在內(nèi)部追蹤每個(gè)數(shù)據(jù)更新操作(例如 set(..)),類似 diff,因此不必要為原始的那些值(1234)重新分配內(nèi)存,而是簡(jiǎn)單的將 "meaning of life" 這個(gè)值加入列表。重要的是,statenewState 分別指向兩個(gè)“不同版本”的數(shù)組,因此值的不變性這個(gè)語(yǔ)義得以保留

發(fā)明你自己的性能優(yōu)化數(shù)據(jù)結(jié)構(gòu)是個(gè)有趣的挑戰(zhàn)。但從實(shí)用性來(lái)講,找一個(gè)現(xiàn)成的庫(kù)會(huì)是個(gè)更好的選擇。Immutable.js(http://facebook.github.io/imm...) 是一個(gè)很棒的選擇,它提供多種數(shù)據(jù)結(jié)構(gòu),包括 List(類似數(shù)組)和 Map(類似普通對(duì)象)。

思考下面的 specialArray 示例,這次使用 Immutable.List

var state = Immutable.List.of( 1, 2, 3, 4 );

var newState = state.set( 42, "meaning of life" );

state === newState;                    // false

state.get( 2 );                        // 3
state.get( 42 );                    // undefined

newState.get( 2 );                    // 3
newState.get( 42 );                    // "meaning of life"

newState.toArray().slice( 1, 3 );    // [2,3]

像 Immutable.js 這樣強(qiáng)大的庫(kù)一般會(huì)采用非常成熟的性能優(yōu)化。如果不使用庫(kù)而是手動(dòng)去處理那些細(xì)枝末節(jié),開發(fā)的難度會(huì)相當(dāng)大。

當(dāng)改變值這樣的場(chǎng)景出現(xiàn)的較少且不用太關(guān)心性能時(shí),我推薦使用更輕量級(jí)的解決方案,例如我們之前提到過(guò)的內(nèi)置的 Object.freeze(..)

以不可變的眼光看待數(shù)據(jù)

如果我們從函數(shù)中接收了一個(gè)數(shù)據(jù),但不確定這個(gè)數(shù)據(jù)是可變的還是不可變的,此時(shí)該怎么辦?去修改它試試看嗎?不要這樣做。 就像在本章最開始的時(shí)候所討論的,不論實(shí)際上接收到的值是否可變,我們都應(yīng)以它們是不可變的來(lái)對(duì)待,以此來(lái)避免副作用并使函數(shù)保持純度。

回顧一下之前的例子:

function updateLastLogin(user) {
    var newUserRecord = Object.assign( {}, user );
    newUserRecord.lastLogin = Date.now();
    return newUserRecord;
}

該實(shí)現(xiàn)將 user 看做一個(gè)不應(yīng)該被改變的數(shù)據(jù)來(lái)對(duì)待;user 是否真的不可變完全不會(huì)影響這段代碼的閱讀。對(duì)比一下下面的實(shí)現(xiàn):

function updateLastLogin(user) {
    user.lastLogin = Date.now();
    return user;
}

這個(gè)版本更容易實(shí)現(xiàn),性能也會(huì)更好一些。但這不僅讓 updateLastLogin(..) 變得不純,這種方式改變的值使閱讀該代碼,以及使用它的地方變得更加復(fù)雜。

應(yīng)當(dāng)總是將 user 看做不可變的值,這樣我們就沒(méi)必要知道數(shù)據(jù)從哪里來(lái),也沒(méi)必要擔(dān)心數(shù)據(jù)改變會(huì)引發(fā)潛在問(wèn)題。

JavaScript 中內(nèi)置的數(shù)組方法就是一些很好的例子,例如 concat(..)slice(..) 等:

var arr = [1,2,3,4,5];

var arr2 = arr.concat( 6 );

arr;                    // [1,2,3,4,5]
arr2;                    // [1,2,3,4,5,6]

var arr3 = arr2.slice( 1 );

arr2;                    // [1,2,3,4,5,6]
arr3;                    // [2,3,4,5,6]

其他一些將參數(shù)看做不可變數(shù)據(jù)且返回新數(shù)組的原型方法還有:map(..)filter(..) 等。reduce(..) / reduceRight(..) 方法也會(huì)盡量避免改變參數(shù),盡管它們并不默認(rèn)返回新數(shù)組。

不幸的是,由于歷史問(wèn)題,也有一部分不純的數(shù)組原型方法:splice(..)pop(..)push(..)shift(..)unshift(..)reverse(..) 以及 fill(..)

有些人建議禁止使用這些不純的方法,但我不這么認(rèn)為。因?yàn)橐恍┬阅苊娴脑颍承﹫?chǎng)景下你仍然可能會(huì)用到它們。不過(guò)你也應(yīng)當(dāng)注意,如果一個(gè)數(shù)組沒(méi)有被本地化在當(dāng)前函數(shù)的作用域內(nèi),那么不應(yīng)當(dāng)使用這些方法,避免它們所產(chǎn)生的副作用影響到代碼的其他部分。

不論一個(gè)數(shù)據(jù)是否是可變的,永遠(yuǎn)將他們看做不可變。遵守這樣的約定,你程序的可讀性和可信賴度將會(huì)大大提升。

總結(jié)

值的不可變性并不是不改變值。它是指在程序狀態(tài)改變時(shí),不直接修改當(dāng)前數(shù)據(jù),而是創(chuàng)建并追蹤一個(gè)新數(shù)據(jù)。這使得我們?cè)谧x代碼時(shí)更有信心,因?yàn)槲覀兿拗屏藸顟B(tài)改變的場(chǎng)景,狀態(tài)不會(huì)在意料之外或不易觀察的地方發(fā)生改變。

由于其自身的信號(hào)和意圖,const 關(guān)鍵字聲明的常量通常被誤認(rèn)為是強(qiáng)制規(guī)定數(shù)據(jù)不可被改變。事實(shí)上,const 和值的不可變性聲明無(wú)關(guān),而且使用它所帶來(lái)的困惑似乎比它解決的問(wèn)題還要大。另一種思路,內(nèi)置的 Object.freeze(..) 方法提供了頂層值的不可變性設(shè)定。大多數(shù)情況下,使用它就足夠了。

對(duì)于程序中性能敏感的部分,或者變化頻繁發(fā)生的地方,處于對(duì)計(jì)算和存儲(chǔ)空間的考量,每次都創(chuàng)建新的數(shù)據(jù)或?qū)ο螅ㄌ貏e是在數(shù)組或?qū)ο蟀芏鄶?shù)據(jù)時(shí))是非常不可取的。遇到這種情況,通過(guò)類似 Immutable.js 的庫(kù)使用不可變數(shù)據(jù)結(jié)構(gòu)或許是個(gè)很棒的主意。

值不變?cè)诖a可讀性上的意義,不在于不改變數(shù)據(jù),而在于以不可變的眼光看待數(shù)據(jù)這樣的約束。

【上一章】翻譯連載 | JavaScript輕量級(jí)函數(shù)式編程-第5章:減少副作用 |《你不知道的JS》姊妹篇

iKcamp原創(chuàng)新書《移動(dòng)Web前端高效開發(fā)實(shí)戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開售。

>> 滬江Web前端上海團(tuán)隊(duì)招聘【W(wǎng)eb前端架構(gòu)師】,有意者簡(jiǎn)歷至:zhouyao@hujiang.com <<

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/88381.html

相關(guān)文章

  • 翻譯連載 |《你不知道JS妹篇 |《JavaScript 量級(jí)函數(shù)編程》- 引言&前言

    摘要:我稱之為輕量級(jí)函數(shù)式編程。序眾所周知,我是一個(gè)函數(shù)式編程迷。函數(shù)式編程有很多種定義。本書是你開啟函數(shù)式編程旅途的絕佳起點(diǎn)。事實(shí)上,已經(jīng)有很多從頭到尾正確的方式介紹函數(shù)式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、...

    2bdenny 評(píng)論0 收藏0
  • 全本 | iKcamp翻譯 | 《JavaScript 量級(jí)函數(shù)編程》|《你不知道JS妹篇

    摘要:本書主要探索函數(shù)式編程的核心思想。我們?cè)谥袘?yīng)用的僅僅是一套基本的函數(shù)式編程概念的子集。我稱之為輕量級(jí)函數(shù)式編程。通常來(lái)說(shuō),關(guān)于函數(shù)式編程的書籍都熱衷于拓展閱讀者的知識(shí)面,并企圖覆蓋更多的知識(shí)點(diǎn)。,本書統(tǒng)稱為函數(shù)式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后)...

    paney129 評(píng)論0 收藏0
  • 翻譯連載 | JavaScript量級(jí)函數(shù)編程-7: 閉包vs對(duì)象 |《你不知道JS姊妹

    摘要:相像閉包和對(duì)象之間的關(guān)系可能不是那么明顯。一個(gè)沒(méi)有對(duì)象的編程語(yǔ)言可以用閉包來(lái)模擬對(duì)象。事實(shí)上,表達(dá)一個(gè)對(duì)象為閉包形式,或閉包為對(duì)象形式是相當(dāng)簡(jiǎn)單的。簡(jiǎn)而言之,閉包和對(duì)象是狀態(tài)的同構(gòu)表示及其相關(guān)功能。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,...

    leanxi 評(píng)論0 收藏0
  • 翻譯連載 | 9 :遞歸(上)-《JavaScript量級(jí)函數(shù)編程》 |《你不知道JS

    摘要:一旦我們滿足了基本條件值為,我們將不再調(diào)用遞歸函數(shù),只是有效地執(zhí)行了。遞歸深諳函數(shù)式編程之精髓,最被廣泛引證的原因是,在調(diào)用棧中,遞歸把大部分顯式狀態(tài)跟蹤換為了隱式狀態(tài)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;...

    MasonEast 評(píng)論0 收藏0
  • 翻譯連載 |《你不知道JS妹篇 |《JavaScript 量級(jí)函數(shù)編程》- 1

    摘要:所以我覺(jué)得函數(shù)式編程領(lǐng)域更像學(xué)者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過(guò)了深入的研究和審查,并且可以被驗(yàn)證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會(huì)認(rèn)為形式主義本身有助于學(xué)習(xí)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液...

    omgdog 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

ysl_unh

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<