摘要:修改的節點和該父級鏈路上都變成新的對象顯然是最優方案。如果你對比的兩個中,一個被過,另一個數據又是由其衍生出來的,那效率將是最高的算法的原理與優化檢測本地中是否存在已過當前對象字符串。
Immutable原理解析 簡介 what is Immutable
1.不可變,一成不變的
2.對immutable數據的每次修改操作都會返回一個新的data
掏出一副老生常談的圖
immutable的優點1.歷史回退(同時不浪費內存),時間旅行之類的easy!
2.函數式編程
3.降低代碼的復雜度
數據類型List: 類Array
Map:類Object/Map
Set:類Set
OrderMap/Set:有序Map/Set
....還有些不常用的數據類型
API fromJS/toJS對傳入對象或數組進行deepImmutable,array轉成List,Object轉成Map
const a = Immutable.fromJS({a:1,b:2}) console.log(a) //Map {size: 2, _root: ArrayMapNode, __ownerID: undefined, __hash: 1014196085, __altered: false} //定制化fromJS,根據key索引和value決定你想將他淺immutable還是深immutable,或者轉換成其他immutable類型 const b = Immutable.fromJS({a:["a","b"],b:2},(key,value)=>{ const isIndexed = Immutable.Iterable.isIndexed(value); return isIndexed ? value.toList() : value.toOrderedMap(); }) a.toJS() // {a:1,b:2}Map/List Map
語法上同時兼容了ES6 Map,支持[key,value]形式傳入
const MapA = Immutable.Map([["a",1],["b","2"]]) const MapB = Immutable,Map({a:1}) console.log(MapA.toJS(),MapB.toJS()) // {a:1,b:2} {a:1}List
const ListA = Immutable.List([["a",1],["b","2"]]) ListA.toJS() // [["a",1],["b","2"]]size
獲取大小
const ListA = Immutable.List([["a",1],["b","2"]]) const MapA = Immutable.Map({a:{a:1}}) ListA.size // 2 MapA.size // 1get/getIn
使用方式:get(key:any, notSetValue) / getIn(keyPath:array,notSeValue)
const obj = Immutable.fromJS({a:{a:8}}) console.log(obj.get("a"),obj.getIn(["a","a"])) //Map.... 8 console.log(obj.get("b","joker"),obj.getIn(["b","b","b"],"joker")) //joker joker const array = Immutable.fromJS([{a:1},"2"]) array.get(0).toJS() // {a:1} array.getIn([0,"a"]) // 1
從此優雅寫代碼
以前的我們 if(a && a.data && a.data.productList && a.data.productList.length > 0) 現在的我們 $immutable.getIn(["data","productList"],List()).size > 0
immutable除了對嵌套形式的數據進行分離外,對于同一層級的數據也進行了分割,見下文_tail+__root區間存儲
set/setInconst ListA = Immutable.from({a:{a:1}}) const ListB = ListA.set("a",{o:77}) // {a:{o:77}} ListB === ListA // false ListA.setIn(["a","a"],"7777") // {a:{a:777}}
set/setIn是我們最常用的api,其內部實現和update/updateIn一樣。也是immutable之所以immutable的核心所在
在文章剛開始提到的immutable原理圖中,為什么immutable在改變一個節點后,該父節點的鏈路上都變成了新的節點,一方面和實際需要有關,一方面也與set方法的實現有關。
從實際需要的角度,數據如果想immutable化,即前后完全是兩個對象,同時為了避免deepClone的性能問題,達到不變數據內存的盡可能復用。修改的節點和該父級鏈路上都變成新的對象顯然是最優方案。
從實現角度來說,我們修改一個層級很深的節點,一般會調用immutable提供的setIn(["a","a"],xx)/update(["a","a"],xxx)這樣的方法。
實際immutable的整個一套修改流程是這樣的
假設我們操作的數據是{a:{a:1}} 執行 setIn(["a","a"],"XXX")操作
["a","a"]這是一個keyPath,immutable會按照順序一層層往里找 找到指定節點那塊的時候,開始修改值 得到一個修改完的{a:xxx}后,再原路向上set每一級,會先將每一級淺拷貝一遍,然后更新淺拷貝后的對象,將修改完的再吐給上一層,重復這樣的操作,最后返回了一個新的immutable對象
// 因為obj在immutable里的存儲格式也是數組類型(類Map),所以也可以使用arrCopy function arrCopy(arr, offset) { offset = offset || 0; var len = Math.max(0, arr.length - offset); var newArr = new Array(len); for (var ii = 0; ii < len; ii++) { newArr[ii] = arr[ii + offset]; } return newArr; }
// 實際的更新邏輯 function updateInDeeply( inImmutable, existing, keyPath, i, notSetValue, updater ) { const wasNotSet = existing === NOT_SET; if (i === keyPath.length) { //根據傳進的keyPath進行迭代 const existingValue = wasNotSet ? notSetValue : existing; const newValue = updater(existingValue); return newValue === existingValue ? existing : newValue; } if (!wasNotSet && !isDataStructure(existing)) { throw new TypeError( "Cannot update within non-data-structure value in path [" + keyPath.slice(0, i).map(quoteString) + "]: " + existing ); } const key = keyPath[i]; const nextExisting = wasNotSet ? NOT_SET : get(existing, key, NOT_SET); //get到每一層的Data const nextUpdated = updateInDeeply( nextExisting === NOT_SET ? inImmutable : isImmutable(nextExisting), nextExisting, keyPath, i + 1, notSetValue, updater ); return nextUpdated === nextExisting ? existing : nextUpdated === NOT_SET ? remove(existing, key) : set( //最核心的地方 將change后的結果set到每一層 wasNotSet ? (inImmutable ? emptyMap() : {}) : existing, key, nextUpdated ); }merge/mergeDeep
對對象進行merge,支持傳入immutable對象和普通對象
const objA = Immutable.fromJS({a:1,b:{a:2}}) const objB = Immutable.fromJS({a:3,b:{h:2}}) objA.merge({a:3,b:{h:2}}) // {a:3,b:{h:2}} objA.merge(objB) // {a:3,b:{h:2}} objA.mergeDeep({a:3,b:{h:2}}) // {a:3,b:{a:2,h:2}} // 通常我們reducer中對于action,state處理都會這樣 return { ...state, ...action.payload } // 現在我們可以這么寫 return state.merge(action.payload)is
對兩個immutable對象進行diff
const immutableA = Immutable.fromJS({a:{a:1}}) const immutableB = immutableA.fromJS({a:{a:1}}) immutableA === immutableB // false is(immutableA, immutableB) //true
is不支持淺immutable Data的對比,不支持普通對象的對比
常用操作1.List:pop,push,shift,unshift,slice,forEach,Map,filter
與原生用法幾乎一致,但是有兩點需要注意:所有修改型操作必定返回一個新的Data。foreach是返回迭代數
Immutable.fromJS([1, 2, 3, 4, 5, {a: 123}]).forEach((value, index, array)=>{ return value < 5; }); // 5
2.Map:同時也支持forEach之類的遍歷,因為其存儲方式以Array存儲。特有方法的話mapKeys/mapEntries
常用api其實不想多說,網上有大把的資源 百度 必應 谷歌
Hash將immutable對象hash化,在其屬性_hash上掛載,
const obj1 = immutable.fromJS({a:{a:1}}) const obj2 = immutable.Map({a:{a:1}}) Immutable.hash(obj1) Immutable.hash(obj2) obj1.__hash === obj2.__hash // false 具體原理見下文Hash原理剖析withMutation&asMutable/asImutable
const ListA = Immutable.List(["a","b"]) ListA.push("gg") .pop() .shift()
按照immutable每個操作必定返回新的對象的這種說法,上述代碼產生了很多冗余的List,而針對這點immutable給出了兩種解決方案
//withMutation const ListA = Immutable.List(["a","b"]) const ListB = ListA.withMutations(($list)=>{ $list.push("gg") .pop() .shift() }) //asMutable/asImutable const ListA = Immutable.List(["a","b"]) const ListB = ListA.asMutable() console.log(ListA === ListB,Immutable.is(ListA,ListB)) // false true const ListC = ListB.pop() console.log(ListB,ListC === ListB,Immutable.is(ListC,ListB)) // ["a"] true true const ListFinally = ListC.asImmutable() //asMutable/asImutable必須同時成對出現
而immutable是怎么實現這個的呢??
仔細觀察immutable對象,嗯,你會發現有個__ownerID,嗯,然后呢,就沒有然后了。。。然后你就要看源碼了
//asMutable源碼 function asMutable() { return this.__ownerID ? this : this.__ensureOwner(new OwnerID()); } //當我們修改節點時都會類似觸發一個editableVNode這樣的函數 function editableVNode(node, ownerID) { if (ownerID && node && ownerID === node.ownerID) { return node; } return new VNode(node ? node.array.slice() : [], ownerID); // }
通過實例函數的方式獲得唯一ID,這點還是很細膩的
immutable優點及使用技巧 1.高效的存取方案 __root + __tail如果說immutable他要轉換一個length 1000的array,他會怎么做呢,存儲上他會將1000按length32為單位進行存儲,放置在_root中,剩下的扔進_tail。同理,immutable在進行get/set操作時,扔進去一個索引100,首先做的事是,確認這個100在那個索引區,然后再去那個32的array中拿數據。
// List.set let newTail = list._tail; let newRoot = list._root; const didAlter = MakeRef(DID_ALTER); if (index >= getTailOffset(list._capacity)) { newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter); } else { newRoot = updateVNode( newRoot, list.__ownerID, list._level, index, value, didAlter ); } 以32位劃分存儲分區 const SHIFT = 5; const SIZE = 1 << SHIFT; function getTailOffset(size) { return size < SIZE ? 0 : ((size - 1) >>> 5) << 5; }2.is
is其實就是immutable中Map/List對象的deepDiff,而實際真正的diff過程就是hash與漫長的迭代diff。如果你對比的兩個immutable中,一個data被hash過,另一個數據又是由其衍生出來的,那diff效率將是最高的
3.Hash算法的原理與優化1.檢測本地weakMap/stringHashCache中是否存在已hash過當前對象/字符串。
一方面通過WeakMap的弱引用,讓這些作為key的obj可以被gc,另一方面對于數據的hash過程只會是越來越快
2.對于immutable Data的特殊對象如何Hash?如DOMElement,非immutable Obj
對于DOMElement
首先檢測是否為IE 低版本 IE對于每一個DOM都賦予了唯一的node.uniqueID
function getIENodeHash(node) { if (node && node.nodeType > 0) { switch (node.nodeType) { case 1: // Element return node.uniqueID; case 9: // Document return node.documentElement && node.documentElement.uniqueID; } } }
若為非IE
手動維護一個遞增的hashWeakMap,Symbol私有化后放在prototype中
let UID_HASH_KEY = "__immutablehash__"; if (typeof Symbol === "function") { UID_HASH_KEY = Symbol(UID_HASH_KEY); } hashed = ++objHashUID; if (objHashUID & 0x40000000) { objHashUID = 0; } Object.defineProperty(obj, UID_HASH_KEY, { enumerable: false, configurable: false, writable: false, value: hashed, });
對于非immutable Data(Map淺immutable后里的深層嵌套數據)
代碼同上,維護一個WeakMap,key是obj,Value是遞增的objHashUID
3.Hash沖突?merge KeyHash+ValueHash
對于純數組,immutable的hash方案是hash所有索引下的value然后進行疊加
對于object,immutable對每一個object單元以Hash(key)+Hash(value)最后進行疊加
function hashCollection(collection) { if (collection.size === Infinity) { return 0; } const ordered = isOrdered(collection); const keyed = isKeyed(collection); let h = ordered ? 1 : 0; const size = collection.__iterate( keyed ? ordered ? (v, k) => { h = (31 * h + hashMerge(hash(v), hash(k))) | 0; } : (v, k) => { h = (h + hashMerge(hash(v), hash(k))) | 0; } : ordered ? v => { h = (31 * h + hash(v)) | 0; } : v => { h = (h + hash(v)) | 0; } ); return murmurHashOfSize(size, h); }
使用技巧
1.盡早提前hash的時間點,在一些ajax請求,launch加載的時候,這樣在進行長列表render的時候可以很大程度上優化性能,同時安利一波biz-decorator,集成autobind,debounce,throttle,pureRender裝飾器
2.如果想用hash去做diff,要仔細考慮immutable是否Deep
Deep&Hash immutable時間長 初始hash時間長 diff速度快(與層次有關)
!Deep&Hash immutable時間短 初始hash時間短 diff速度快
!Deep&!Hash immutable時間短 無hash時間 diff速度快
結論:
Deep&Hash 耗時長,但是可以給hashMap提供更多的hash樣本,前提是這個數據樣本會頻繁被用到
diff時無需對元數據衍生出來的數據hash化,并不會優化diff時間
//我們對一個5MB的商品數據進行immutable const Map = Immutable.Map(MockData) // 3.489013671875ms Immutable.hash(Map) // 1.677001953125ms const fromJS = Immutable.fromJS(MockData) // 962.42724609375ms Immutable.hash(fromJS) // 306.51318359375ms const Map2 = Map.setIn(["data","data",10,"state"],"5"); Immutable.is(Map2,Map) //3.2197265625ms const fromJS2 = fromJS.setIn(["data","data",10,"state"],"5"); Immutable.is(fromJS2,fromJS) //10.624267578125ms //相比之前fromJS的Immutable hash 時間成本節省了一個數量級 Immutable.hash(fromJS2); //16.772216796875ms //diff時間上并沒有顯著的提升 Immutable.is(fromJS2,fromJS) //7.08203125msimmutable缺點與解決方案
1.請求或存入LS時都需要轉成通用對象,但是仍然可以使用JSON.stringify,也可以toJS()
2.語法上基本兼容以前api(類ES6 Map/Set),但是寫法上有很大轉變(建議新項目或外部依賴較少的項目切immutable)
3.提供api較為基礎,或達不到使用目的,可以在原有基礎上擴展
4.基本常用類型多為Map,List,可對immutable針對性的閹割,或者自行實行一套
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89990.html
摘要:例如維護一份在內部,來判斷是否有變化,下面這個例子就是一個構造函數,如果將它的實例傳入對象作為第一個參數,就能夠后面的處理對象中使用其中的方法上面這個構造函數相比源代碼省略了很多判斷的部分。 showImg(https://segmentfault.com/img/bV27Dy?w=1400&h=544); 博客鏈接:下一代狀態管理工具 immer 簡介及源碼解析 JS 里面的變量類...
摘要:下面我們會向大家解釋清楚為什么這個這么重要,以及它和的響應式數據流有什么關系。源碼前面鋪墊這么多就是希望大家能理解接下來要講的響應式數據流。總結講到這里大家應該都能夠明白的響應式數據流是如何實現的。 Vue、React介紹 目前前端社區比較推崇的框架有Vue 和 React,公司內部許多端都自發的將原有的老技術方案(widget + jQuery)遷移到 Vue / React上了。我...
摘要:往往純的單頁面應用一般不會太復雜,所以這里不引入和等等,在后面復雜的跨平臺應用中我會將那些技術一擁而上。構建極度復雜,超大數據的應用。 showImg(https://segmentfault.com/img/bVbvphv?w=1328&h=768); React為了大型應用而生,Electron和React-native賦予了它構建移動端跨平臺App和桌面應用的能力,Taro則賦...
摘要:往往純的單頁面應用一般不會太復雜,所以這里不引入和等等,在后面復雜的跨平臺應用中我會將那些技術一擁而上。構建極度復雜,超大數據的應用。 showImg(https://segmentfault.com/img/bVbvphv?w=1328&h=768); React為了大型應用而生,Electron和React-native賦予了它構建移動端跨平臺App和桌面應用的能力,Taro則賦...
摘要:往往純的單頁面應用一般不會太復雜,所以這里不引入和等等,在后面復雜的跨平臺應用中我會將那些技術一擁而上。構建極度復雜,超大數據的應用。 showImg(https://segmentfault.com/img/bVbvphv?w=1328&h=768); React為了大型應用而生,Electron和React-native賦予了它構建移動端跨平臺App和桌面應用的能力,Taro則賦...
閱讀 1325·2021-11-24 09:38
閱讀 3263·2021-11-22 12:03
閱讀 4189·2021-11-11 10:59
閱讀 2327·2021-09-28 09:36
閱讀 1038·2021-09-09 09:32
閱讀 3430·2021-08-05 10:00
閱讀 2538·2021-07-23 15:30
閱讀 2981·2019-08-30 13:12