摘要:與持久化工程師花了年時間打造,與同期出現。有持久化數據結構,如等,并發安全。總結篇幅有限,時間也比較晚了,關于前端數據的扁平化與持久化處理先講這么多了,有興趣的同學可以關注下,后面有時間會多整理分享。
(PS: 時間就像海綿里的水,擠到沒法擠,只能擠擠睡眠時間了~ 知識點還是需要整理的,付出總會有收獲,tired but fulfilled~)
前言最近業務開發,從零搭建網頁生成器,支持網頁的可視化配置。為了滿足這種需求,需要將各種頁面抽象成類似地模塊,再將每個模塊抽象成各個可配置的組件,有些組件還包含一些小部件。這樣一來,頁面配置的JSON數據就會深層級地嵌套,那么修改一個小組件的配置,要怎樣來更新頁面樹的數據?用id一層一層遍歷?這樣做法當然是不推薦的,不僅性能差,代碼寫起來也麻煩。因此,就考慮能否像數據庫一樣,把數據范式化,將嵌套的數據展開,每條數據對應一個id,通過id直接操作。Normalizr 就幫你做了這樣一件事情。
另外考慮到頁面編輯,就需要支持 撤銷 與 重做的功能,那么要怎樣來保存每一步的數據?頁面編輯的數據互相關聯,對象的可變性會帶來很大的隱患。雖然JS中的const(es6)、Object.freeze(es5) 可以防止數據被修改,但它們都是shallow處理,遇到嵌套多和深的結構就需要遞歸處理,而遞歸又存在性能上的問題。這時,用過React的童鞋就知道了,React借助 Immutable 來減少DOM diff的比對,它就能夠很好地解決上面這兩個問題。Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變。
那么為什么在JS中,諸如對象這樣的數據類型是可變的呢?我們先來了解一下JS的數據類型。
JS數據類型JS的數據類型包括基本類型和引用類型?;绢愋桶⊿tring、Number、 Boolean、Null、Undefined,引用類型主要是對象(包括Object、Function、Array、Data等)。基礎類型的值本身無法被改變,而引用類型,如Object,是可以被改變的。本文討論的數據不可變,就是指保持對象的狀態不變。來看看下面的例子:
// 基本類型 var a = 1; var b = a; b = 3; console.log(a); // 1 console.log(b); // 3 // 引用類型 var obj1 = {}; obj1.arr = [2,3,4]; var obj2 = obj1; obj2.arr.push(5); console.log(obj1.arr); // [2, 3, 4, 5] console.log(obj2.arr); // [2, 3, 4, 5]
上面例子中,b的值改變后,a的值不會隨著改變;而obj2.arr被修改后,obj1.arr的值卻跟著變化了。這是因為JS對象中的賦值是“引用賦值”,即在賦值的過程中,傳遞的是在內存中的引用。這也是JS中對象為什么有深拷貝和淺拷貝的用法,只有深拷貝后,對新對象的修改才不會改變原來的對象。
淺拷貝只會將對象的各個屬性進行依次復制,并不會進行遞歸復制,而 JavaScript 存儲對象都是存地址的。上面代碼中,只是執行了淺拷貝,結果導致 obj1 和 obj2指向同一塊內存地址。所以修改obj2.arr,obj1.arr的值也變了。如果是深拷貝(如Lodash的cloneDeep)則不同,它不僅將原對象的各個屬性逐個復制出去,而且將原對象各個屬性所包含的對象也依次采用深拷貝的方法遞歸復制到新對象上,也就不會存在上面 obj1 和 obj2 中的 arr 屬性指向同一個內存對象的問題。
為了更清晰地理解這個問題,還是得來了解下javascript變量的存儲方式。
數據類型的存儲程序的運行都需要內存,JS語言把數據分配到內存的棧(stack)和堆(heap)進行各種調用(注:內存中除了棧和堆,還有常量池)。JS這樣分配內存,與它的垃圾回收機制有關,可以使程序運行時占用的內存最小。
在JS中,每個方法被執行時,都會建立自己的內存棧,這個方法內定義的變量就會一一被放入這個棧中。等到方法執行結束,它的內存棧也自然地銷毀了。因此,所有在方法中定義的變量都是放在棧內存中的。當我們在程序中創建一個對象時,這個對象將被保存到運行時數據區中,以便反復利用(因為對象的創建成本通常較大),這個運行時數據區就是堆內存。堆內存中的對象不會隨方法的結束而銷毀,即使方法結束后,這個對象還可能被另一個引用變量所引用。只有當一個對象沒有任何引用變量引用它時,系統的垃圾回收機制才會在核實的時候回收它。
總的來說,棧中存儲的是基礎變量以及一些對象的引用變量,基礎變量的值是存儲在棧中,而引用變量存儲在棧中的是指向堆中的對象的地址,這就是修改引用類型總會影響到其他指向這個地址的引用變量的原因。堆是運行時動態分配內存的,存取速度較慢,棧的優勢是存取速度比堆要快,并且棧內的數據可以共享,但是棧中數據的大小與生存期必須是確定的,缺乏靈活性。
Normalizr與范式化范式化(Normalization)是數據庫設計中的一系列原理和技術,以減少數據庫中數據冗余,增進數據的一致性。直觀地描述就是尋找對象之間的關系,通過某種方式將關系之間進行映射,減少數據之間的冗余,優化增刪改查操作。Normalizr庫本身的解釋就是Normalizes nested JSON according to a schema),一種類似于關系型數據庫的處理方法,通過建表建立數據關系,把深層嵌套的數據展開,更方便靈活的處理和操作數據。
來看個官網的例子,理解一下:
{ "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "commenter": { "id": "2", "name": "Nicole" } } ] }
這是一份博客的數據,一篇文章article有一個作者author, 一個標題title, 多條評論,每條評論有一個評論者commenter,每個commenter又有自己的id和name。這樣如果我們要獲取深層級的數據,如commenter時,就需要層層遍歷。這時候,如果使用Normalizr,就可以這樣定義Schema:
import { schema } from "normalizr"; const user = new schema.Entity("users"); const comment = new schema.Entity("comments", { commenter: user }); const article = new schema.Entity("articles", { author: user, comments: [comment] });
然后調用一下 Normalize,就可以得到扁平化后的數據,如下:
{ "entities": { "users": { "1": { "id": "1", "name": "Paul" }, "2": { "id": "2", "name": "Nicole" } }, "comments": { "324": { "id": "324", "commenter": "2" } }, "articles": { "123": { "id": "123", "author": "1", "title": "My awesome blog post", "comments": ["324"] } } }, "result": "123" }
這樣每個作者、每條評論、每篇文章都有對應的id, 我們就不需要遍歷,可以直接拿對應的id進行修改。
再來看下我們在項目中的示例代碼:
分別定義element、section 和 page三張表,并指定它們之間的關系。這樣范式化后,想對某個頁面某個模塊或者某個元素進行增刪查改,就直接拿對應的id,不需要再耗性能去遍歷了。
Immutable與持久化Facebook工程師Lee Byron花了3年時間打造Immutable,與 React 同期出現。Immutable Data,維基百科上是這樣定義的:
In computing, a persistent data structure is a data structure that always preserves the previous version of itself when it is modified. Such data structures are effectively immutable, as their operations do not (visibly) update the structure in-place, but instead always yield a new updated structure.
簡單來說,Immutable Data 就是一旦創建,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象。Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變。Immutable 使用了 Structural Sharing(結構共享),即如果對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享,這樣就避免了深拷貝帶來的性能損耗。
我們通過圖片來理解一下:
Immutable 內部實現了一套完整的持久化數據結構,有很多易用的數據類型,如Collection、List、Map、Set、Record、Seq(Seq是借鑒了Clojure、Scala、Haskell這些函數式編程語言,引入的一個特殊結構)。它有非常全面的map、filter、groupBy、reduce、find等函數式操作方法。它的Api很強大,大家有興趣可以去看下。這里簡單列舉 updateIn/getIn 來展示它帶來的一些便捷操作:
var obj = { a: { b: { list: [1, 2, 3] } } }; var map = Immutable.fromJS(obj); // 注意 fromJS這里實現了深轉換 var map2 = Immutable.updateIn(["a", "b", "list"], (list) => { return list.push(4); }); console.log(map2.getIn(["a", "b", "list"])) // List [ 1, 2, 3, 4 ]
代碼中我們要改變數組List的值,不必一層一層獲取數據,而是直接傳入對應的路徑修改就行。這種操作在數據嵌套越深時,優勢更加明顯。來看下我們業務代碼的示例吧。
這里在多個頁面的模塊配置中,要更新某個頁面的某個模塊的數據,我們只需要在updateIn傳入對應的path和value,就可以達到預想的效果。篇幅有限,更多的示例請自行查看api。
熟悉React的同學也基于它結構的不可變性和共享性,用它來能夠快速進行數據的比較。原本React中使用PureRenderMixin來做DOM diff比較,但只是淺比較,當數據結構比較深的時候,依然會存在多余的diff過程。這里只提個點,不深入展開了,感興趣的同學可以自行google。
與 Immutable.js 類似的,還有個seamless-immutable,它的代碼庫非常小,壓縮后下載只有 2K。而 Immutable.js 壓縮后下載有16K。大家各取所需,根據實際情況,自己斟酌下使用哪個比較適合。
優缺點什么事物都有利弊,代碼庫也不例外。這里列舉下它們的優缺點,大家權衡利弊,一起來看下:
Normalizr 可以將數據扁平化處理,方便對深層嵌套的數據進行增刪查改,但是文檔不是很清晰,大家多查多理解,引入庫文件也會增大。Immutable 有持久化數據結構,如List/Map等,并發安全。其次,它支持結構共享,比cloneDeep 性能更優,節省內存。第三,它借鑒了Clojure、Scala、Haskell這些函數式編程語言,引入了特殊結構Seq,支持Lazy operation。Undo/Redo,Copy/Paste,甚至時間旅行這些功能對它來說都是小菜一碟。缺點方面,Immutable源文件過大,壓縮后有15kb。而且它侵入性強,與原生api容易混淆。此外,類型轉換比較繁瑣,尤其是與服務器交互頻繁時,這種缺點就更加明顯。當然,也可以根據業務需求,衡量下是否用seamless-immutable,它使用 Object.defineProperty (因此只能在 IE9 及以上使用) 擴展了 JavaScript 的 Array 和 Object 對象來實現,只支持 Array 和 Object 兩種數據類型。但是代碼庫非常小,壓縮后下載只有 2K。
總結篇幅有限,時間也比較晚了,關于前端數據的扁平化與持久化處理先講這么多了,有興趣的同學可以關注下,后面有時間會多整理分享。
參考資料前端數據范式化
Immutable詳解及React中實踐
為什么需要Immutable.js
facebook immutable.js 意義何在,使用場景?
一些鏈接, 關于不可變數據
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97315.html
摘要:前端芝士樹如何完成數組的扁平化問題描述輸入一個嵌套型數組輸出扁平化后的數組如果只是兩層的數據如果是多層嵌套的數組 【前端芝士樹】如何完成數組的扁平化 Array flattern? 問題描述 輸入:一個嵌套型數組輸出:扁平化后的數組 let array = [1, [2, 3, 4]]; let arrayDeeper = [1, [2, [3, 4]]]; 如果只是兩層的數據 fun...
閱讀 3383·2023-04-25 20:37
閱讀 3142·2021-09-07 09:59
閱讀 1663·2019-08-29 12:43
閱讀 1184·2019-08-28 18:27
閱讀 479·2019-08-26 13:50
閱讀 2024·2019-08-26 10:33
閱讀 3591·2019-08-23 18:39
閱讀 2390·2019-08-23 18:09