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