摘要:整個(gè)這個(gè)雷區(qū)面板都是由的和組成的,最后由的方法對(duì)其進(jìn)行不可變化處理剩下的主要邏輯部分就是掃雷了,傳入掃雷游戲?qū)ο笠粋€(gè)不可變結(jié)構(gòu)做為第一個(gè)參數(shù),以及要掃的那個(gè)雷區(qū)塊對(duì)象,最后返回新的掃雷游戲?qū)嵗?/p>
不可變性(Immutability)是函數(shù)式編程的核心原則,在面向?qū)ο缶幊汤镆灿写罅繎?yīng)用。在這篇文章里,我會(huì)給大家秀一下到底什么是不可變性(Immutability)、她為什么還這么屌、以及在JavaScript中怎么應(yīng)用。
什么是不可變性(Immutability)?還是先來(lái)看看關(guān)于可變性(Mutability)的教條式定義:“l(fā)iable or subject to change or alteration(譯者注:真他媽難翻,就簡(jiǎn)單理解成"易于改變的"吧)”。在編程領(lǐng)域里,我們用可變性(Mutability)來(lái)描述這樣一種對(duì)象,它在創(chuàng)建之后狀態(tài)依舊可被改變。那當(dāng)我們說(shuō)不可變(Immutable)時(shí),就是可變(Mutable)的對(duì)立面了(譯者注:原諒我翻的廢話又多起來(lái)) - 意思是,創(chuàng)建之后,就再也不能被修改了。
如果我說(shuō)的又讓你感到詭異了,原諒我小小的提醒一下,其實(shí)我們平時(shí)使用的很多東西事實(shí)上都是不可變的哦!
var statement = "I am an immutable value"; var otherStr = statement.slice(8, 17);
我猜沒(méi)人會(huì)吃驚,statement.slice(8, 17)并沒(méi)有改變statement變量吧(譯者注:如果你吃驚了,趕緊去補(bǔ)基本知識(shí)吧)?事實(shí)上,string對(duì)象上的所有方法里,沒(méi)有一個(gè)會(huì)修改原string,它們一律返回新的string。原因簡(jiǎn)單了,因?yàn)?b>string就是是不可變的(Immutable) - 它們不能被修改,我們能做的就是基于原string操作后得到一個(gè)新string。
注意了,string可不是JavaScript里唯一內(nèi)置的不可變(Immutable)數(shù)據(jù)類型哦。number也是不可變(Immutable)的。否則的話,你試想下這個(gè)表達(dá)式2 + 3,如果2的含義能被修改,那代碼該怎么寫啊|_|。聽起來(lái)荒謬吧,但我們?cè)诰幊讨袇s常常對(duì)object和array做出這種事兒。
JavaScript充滿變化在JavaScript中,string和number從設(shè)計(jì)之初就是不可變(Immutable)的。但是,看看下面這個(gè)關(guān)于array例子:
var arr = []; var v2 = arr.push(2);
來(lái)我問(wèn)你,v2的值是什么?如果array和string、number一樣也是不可變(Immutable)的,那此時(shí)v2必定是一個(gè)包含了一個(gè)數(shù)字2的新array。事實(shí)上,還真就不是那樣的。這里arr引用的array被修改了,里面添了一個(gè)數(shù)字2,這時(shí)v2的值(也就是arr.push(2)的返回值),其實(shí)是arr此時(shí)的長(zhǎng)度 - 就是1。
試想我們擁有一個(gè)不可變的數(shù)組(ImmutableArray)。就像string、number那樣,她應(yīng)該能像如下這樣被使用:
var arr = new ImmutableArray([1, 2, 3, 4]); var v2 = arr.push(5); arr.toArray(); // [1, 2, 3, 4] v2.toArray(); // [1, 2, 3, 4, 5]
類似的,也可以有一個(gè)不可變的Map(ImmutableMap),理論上可以替代object應(yīng)該于多數(shù)場(chǎng)景,她應(yīng)該有一個(gè)set方法,不過(guò)這個(gè)set方法不會(huì)塞任何東西到原Map里,而是返回一個(gè)包含了塞入值的新Map:
var person = new ImmutableMap({name: "Chris", age: 32}); var olderPerson = person.set("age", 33); person.toObject(); // {name: "Chris", age: 32} olderPerson.toObject(); // {name: "Chris", age: 33}
就像2 + 3這個(gè)表達(dá)式里,我們不可能改變2或是3所代表的含義,一個(gè)person在慶祝他33歲的生日,并不會(huì)影響他曾經(jīng)是32歲的事實(shí)。
JavaScript不可變性(Immutability)實(shí)戰(zhàn)JavaScript里目前還沒(méi)有不可變的list和map,所以暫時(shí)我們還是需要三方庫(kù)的幫助。有兩個(gè)很不錯(cuò)的,一個(gè)是Mori - 她把ClojureScript里持久化數(shù)據(jù)結(jié)構(gòu)的API支持帶到了JavaScript里;另一個(gè)是Facebook出品的immutable.js。后面的示例里,我將使用immutable.js,因?yàn)樗腁PI對(duì)于JavaScript開發(fā)者更友好一些。
下面的例子里,我們使用不可變(Immutable)知識(shí)來(lái)構(gòu)建一個(gè)掃雷小游戲。掃雷的游戲面板我們用一個(gè)不可變的map來(lái)構(gòu)建,其中tiles(雷區(qū)區(qū)塊)部分值得關(guān)注哦,它是一個(gè)由不可變map組成的不可變list(譯者注:又開始繞了),其中每一個(gè)不可變的map表示一個(gè)tile(雷區(qū)塊)。整個(gè)這個(gè)雷區(qū)面板都是由JavaScript的object和array組成的,最后由immutable.js的fromJS方法對(duì)其進(jìn)行不可變化處理:
function createGame(options) { return Immutable.fromJS({ cols: options.cols, rows: options.rows, tiles: initTiles(options.rows, options.cols, options.mines) }); }
剩下的主要邏輯部分就是“掃雷”了,傳入掃雷游戲?qū)ο?一個(gè)不可變結(jié)構(gòu))做為第一個(gè)參數(shù),以及要“掃”的那個(gè)tile(雷區(qū)塊)對(duì)象,最后返回新的掃雷游戲?qū)嵗R韵挛覀兙鸵v到這個(gè)revealTile函數(shù)。當(dāng)它被調(diào)用時(shí),tile(雷區(qū)塊)的狀態(tài)就要被重置為“掃過(guò)”的狀態(tài)。如果是可變編程,代碼很簡(jiǎn)單:
function revealTile(game, tile) { game.tiles[tile].isRevealed = true; }
然后再來(lái)看看如果用上面介紹的不可變數(shù)據(jù)結(jié)構(gòu)來(lái)編碼,坦白講,一開始代碼變得都點(diǎn)丑了:
function revealTile(game, tile) { var updatedTile = game.get("tiles").get(tile).set("isRevealed", true); var updatedTiles = game.get("tiles").set(tile, updatedTile); return game.set("tiles", updatedTiles); }
我去,丑爆了有木有!
萬(wàn)幸,不可變性不止于此,一定有得救!這種需求很常見,所以工具早就考慮到了,可以這么操作:
function revealTile(game, tile) { return game.setIn(["tiles", tile, "isRevealed"], true); }
現(xiàn)在revealTile返回一個(gè)新的實(shí)例了,新實(shí)例里其中一個(gè)tile(雷區(qū)塊)的isRevealed就和之前那個(gè)game實(shí)例里的不一樣了。這里面用到的setIn是一個(gè)null-safe(空值安全)的函數(shù),任意keyPath中的key不存在時(shí),都會(huì)在這個(gè)位置創(chuàng)建一個(gè)新的不可變map(譯者注:這句略繞,個(gè)人認(rèn)為既然這里不是主講immutable.js,那就沒(méi)必要非提一下它的這個(gè)特性,反而不清不楚,原作沒(méi)細(xì)說(shuō),那我也就不多說(shuō)了,有興趣的可以來(lái)這里自己揣摩)。這個(gè)null-safe特性對(duì)于我們現(xiàn)在掃雷游戲這個(gè)例子并不合適,因?yàn)椤皰摺币粋€(gè)不存在的tile(雷區(qū)塊)表示我們正在試圖掃雷區(qū)以外的地方,那顯然不對(duì)!這里需要多做一步檢查,通過(guò)getIn方法檢查tile(雷區(qū)塊)是否存在,然后再“掃”它:
function revealTile(game, tile) { return game.getIn(["tiles", tile]) ? game.setIn(["tiles", tile, "isRevealed"], true) : game; }
如果tile(雷區(qū)塊)不存在,我們就返回原掃雷游戲?qū)嵗_@就是個(gè)可迅速上手的關(guān)于不可變性(Immutability)的練習(xí),想深入了解的可以看codepen,完整的實(shí)現(xiàn)都在里面了。
Performance怎么樣?你可能覺得,這他媽Performance應(yīng)該low爆了吧,我只能說(shuō)某些情況下你是對(duì)的。每當(dāng)你想添加點(diǎn)東西到一個(gè)不可變(Immutable)對(duì)象里時(shí),她一定是先拷貝以存在值到新實(shí)例里,然后再給新實(shí)例添加內(nèi)容,最后返回新實(shí)例。相比可變對(duì)象,這勢(shì)必會(huì)有更多內(nèi)存、計(jì)算量消耗。
因?yàn)椴豢勺?Immutable)對(duì)象永遠(yuǎn)不變,實(shí)際上有一種實(shí)現(xiàn)策略叫“結(jié)構(gòu)共享”,使得她的內(nèi)存消耗遠(yuǎn)比你想象的少。雖然和內(nèi)置的array、object的“變化”相比仍然會(huì)有額外的開銷,但這個(gè)開始恒定,絕對(duì)可以被不可變性(Immutability)帶來(lái)的其它眾多優(yōu)勢(shì)所消磨、減少。在實(shí)踐中,不可變性(Immutability)帶來(lái)的優(yōu)勢(shì)可以極大的優(yōu)化程序的整體性能,即使其中的某些個(gè)別操作開銷變大了。
改進(jìn)變更追蹤各種UI框架里,最難的部分永遠(yuǎn)是變更追蹤(譯者注:或者叫“臟檢查”)。這是JavaScript社區(qū)里的普遍問(wèn)題,所以EcmaScript 7里提供了多帶帶的API在保證Performance的前提下可以追蹤變化:Object.observe()。很多人為之激動(dòng),但也有不少人認(rèn)為這個(gè)API然并卵。他們認(rèn)為,在任何情況下,這個(gè)API都沒(méi)很好的解決變更追蹤問(wèn)題:
var tiles = [{id: 0, isRevealed: false}, {id: 1, isRevealed: true}]; Object.observe(tiles, function () { /* ... */ }); tiles[0].id = 2;
上面例子里,tiles[0]的變更并沒(méi)有觸發(fā)observer,所以其實(shí)這個(gè)提案即便是最簡(jiǎn)單的變更追蹤也沒(méi)做到。那不可變性(Immutability)又是怎么解決的?假設(shè)有一個(gè)應(yīng)用狀態(tài)a,然后它內(nèi)部有值被改變了,于是就得到了一個(gè)新的實(shí)例b:
if (a === b) { // 數(shù)據(jù)沒(méi)變,停止操作 }
如果應(yīng)用狀態(tài)a沒(méi)有被修改,那b就是a,它們指向同一個(gè)實(shí)例,===就夠了,不用做其他事兒。當(dāng)然這需要我們追蹤應(yīng)用狀態(tài)的引用,但整個(gè)問(wèn)題的復(fù)雜度被大大簡(jiǎn)化了,現(xiàn)在只要判斷一下它們是否同一個(gè)實(shí)例的引用就好了,真心不用再去深入調(diào)查里面的某某字段是不是變了。
結(jié)束語(yǔ)希望本文能某種程度上幫你了解不可變性(Immutability)是如何幫我們優(yōu)化/改進(jìn)代碼的,也希望這些例子從實(shí)踐角度說(shuō)清楚了使用方式。不可變性(Immutability)的熱度在持續(xù)增高,我確定這絕不是你今年看到的關(guān)于不可變性(Immutability)的最后一文。同志們,是時(shí)候來(lái)一發(fā)了,我相信你用過(guò)后一定會(huì)high至的,就像我現(xiàn)在一樣^^。
原文地址:Immutability in JavaScript
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/79167.html
摘要:突變引起狀態(tài)的改變。純函數(shù)和副作用純函數(shù)是接受輸入并返回值而不修改其范圍之外的任何數(shù)據(jù)的函數(shù)副作用。如果它們不同,則調(diào)用函數(shù),以更新新狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000018524546?w=1280&h=834); 作者:Chidume Nnamdi 英文原文:https://blog.bitsrc.io/...
摘要:前端日?qǐng)?bào)精選劉海打理指北中的錯(cuò)誤處理模式與反模式譯圖解和譯你并不知道中文裝飾器讓你的代碼更簡(jiǎn)潔眾成翻譯第期每個(gè)程序員第一份工作前應(yīng)該知道的件事中的不變性眾成翻譯寫的一次小結(jié)掘金內(nèi)部機(jī)制探秘和文末附彩蛋和源碼前端雜談開發(fā)實(shí)戰(zhàn) 2017-09-30 前端日?qǐng)?bào) 精選 iPhone X 劉海打理指北React16中的錯(cuò)誤處理ES6 Promise:模式與反模式「譯」圖解 ArrayBuffer...
摘要:函數(shù)式編程的目標(biāo)是盡量寫更多的純函數(shù),并將其與程序的其他部分隔離開來(lái)。在函數(shù)式編程中,是非法的。函數(shù)式編程使用參數(shù)保存狀態(tài),最好的例子就是遞歸。函數(shù)式編程使用遞歸進(jìn)行循環(huán)。在函數(shù)式編程中,函數(shù)是一級(jí)公民。 showImg(https://segmentfault.com/img/bVblxCO?w=1600&h=710); 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等...
摘要:一些開發(fā)人員特別是新手們會(huì)認(rèn)為這兩個(gè)功能的工作方式是一樣的,但其實(shí)并不是。的問(wèn)題使用聲明的對(duì)象僅能阻止其重新分配,但是并不能使其聲明的對(duì)象具有不可變性能夠阻止更改其屬性。因此,當(dāng)具有嵌套屬性的對(duì)象時(shí),并不能完全凍結(jié)對(duì)象。 原文:The differences between Object.freeze() vs Const in JavaScript?作者:Bolaji Ayodeji...
摘要:并發(fā)設(shè)計(jì)模式一模式的使用表示線程本地存儲(chǔ)模式。為不同的任務(wù)創(chuàng)建不同的線程池,這樣能夠有效的避免死鎖問(wèn)題。兩階段終止,即將線程的結(jié)束分為了兩個(gè)階段,第一個(gè)階段是一個(gè)線程向另一個(gè)線程發(fā)送終止指令,第二個(gè)階段是線程響應(yīng)終止指令。 Java 并發(fā)設(shè)計(jì)模式 一、Thread Local Storage 模式 1. ThreadLocal 的使用 Thread Local Storage 表示線程...
閱讀 4002·2023-04-26 02:13
閱讀 2244·2021-11-08 13:13
閱讀 2729·2021-10-11 10:59
閱讀 1732·2021-09-03 00:23
閱讀 1301·2019-08-30 15:53
閱讀 2275·2019-08-28 18:22
閱讀 3050·2019-08-26 10:45
閱讀 727·2019-08-23 17:58