摘要:與持久化工程師花了年時間打造,與同期出現(xiàn)。有持久化數(shù)據(jù)結(jié)構(gòu),如等,并發(fā)安全。總結(jié)篇幅有限,時間也比較晚了,關(guān)于前端數(shù)據(jù)的扁平化與持久化處理先講這么多了,有興趣的同學(xué)可以關(guān)注下,后面有時間會多整理分享。
(PS: 時間就像海綿里的水,擠到?jīng)]法擠,只能擠擠睡眠時間了~ 知識點(diǎn)還是需要整理的,付出總會有收獲,tired but fulfilled~)
前言最近業(yè)務(wù)開發(fā),從零搭建網(wǎng)頁生成器,支持網(wǎng)頁的可視化配置。為了滿足這種需求,需要將各種頁面抽象成類似地模塊,再將每個模塊抽象成各個可配置的組件,有些組件還包含一些小部件。這樣一來,頁面配置的JSON數(shù)據(jù)就會深層級地嵌套,那么修改一個小組件的配置,要怎樣來更新頁面樹的數(shù)據(jù)?用id一層一層遍歷?這樣做法當(dāng)然是不推薦的,不僅性能差,代碼寫起來也麻煩。因此,就考慮能否像數(shù)據(jù)庫一樣,把數(shù)據(jù)范式化,將嵌套的數(shù)據(jù)展開,每條數(shù)據(jù)對應(yīng)一個id,通過id直接操作。Normalizr 就幫你做了這樣一件事情。
另外考慮到頁面編輯,就需要支持 撤銷 與 重做的功能,那么要怎樣來保存每一步的數(shù)據(jù)?頁面編輯的數(shù)據(jù)互相關(guān)聯(lián),對象的可變性會帶來很大的隱患。雖然JS中的const(es6)、Object.freeze(es5) 可以防止數(shù)據(jù)被修改,但它們都是shallow處理,遇到嵌套多和深的結(jié)構(gòu)就需要遞歸處理,而遞歸又存在性能上的問題。這時,用過React的童鞋就知道了,React借助 Immutable 來減少DOM diff的比對,它就能夠很好地解決上面這兩個問題。Immutable 實(shí)現(xiàn)的原理是 Persistent Data Structure(持久化數(shù)據(jù)結(jié)構(gòu)),也就是使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時,要保證舊數(shù)據(jù)同時可用且不變。
那么為什么在JS中,諸如對象這樣的數(shù)據(jù)類型是可變的呢?我們先來了解一下JS的數(shù)據(jù)類型。
JS數(shù)據(jù)類型JS的數(shù)據(jù)類型包括基本類型和引用類型。基本類型包括String、Number、 Boolean、Null、Undefined,引用類型主要是對象(包括Object、Function、Array、Data等)?;A(chǔ)類型的值本身無法被改變,而引用類型,如Object,是可以被改變的。本文討論的數(shù)據(jù)不可變,就是指保持對象的狀態(tài)不變。來看看下面的例子:
// 基本類型 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的值卻跟著變化了。這是因?yàn)镴S對象中的賦值是“引用賦值”,即在賦值的過程中,傳遞的是在內(nèi)存中的引用。這也是JS中對象為什么有深拷貝和淺拷貝的用法,只有深拷貝后,對新對象的修改才不會改變原來的對象。
淺拷貝只會將對象的各個屬性進(jìn)行依次復(fù)制,并不會進(jìn)行遞歸復(fù)制,而 JavaScript 存儲對象都是存地址的。上面代碼中,只是執(zhí)行了淺拷貝,結(jié)果導(dǎo)致 obj1 和 obj2指向同一塊內(nèi)存地址。所以修改obj2.arr,obj1.arr的值也變了。如果是深拷貝(如Lodash的cloneDeep)則不同,它不僅將原對象的各個屬性逐個復(fù)制出去,而且將原對象各個屬性所包含的對象也依次采用深拷貝的方法遞歸復(fù)制到新對象上,也就不會存在上面 obj1 和 obj2 中的 arr 屬性指向同一個內(nèi)存對象的問題。
為了更清晰地理解這個問題,還是得來了解下javascript變量的存儲方式。
數(shù)據(jù)類型的存儲程序的運(yùn)行都需要內(nèi)存,JS語言把數(shù)據(jù)分配到內(nèi)存的棧(stack)和堆(heap)進(jìn)行各種調(diào)用(注:內(nèi)存中除了棧和堆,還有常量池)。JS這樣分配內(nèi)存,與它的垃圾回收機(jī)制有關(guān),可以使程序運(yùn)行時占用的內(nèi)存最小。
在JS中,每個方法被執(zhí)行時,都會建立自己的內(nèi)存棧,這個方法內(nèi)定義的變量就會一一被放入這個棧中。等到方法執(zhí)行結(jié)束,它的內(nèi)存棧也自然地銷毀了。因此,所有在方法中定義的變量都是放在棧內(nèi)存中的。當(dāng)我們在程序中創(chuàng)建一個對象時,這個對象將被保存到運(yùn)行時數(shù)據(jù)區(qū)中,以便反復(fù)利用(因?yàn)閷ο蟮膭?chuàng)建成本通常較大),這個運(yùn)行時數(shù)據(jù)區(qū)就是堆內(nèi)存。堆內(nèi)存中的對象不會隨方法的結(jié)束而銷毀,即使方法結(jié)束后,這個對象還可能被另一個引用變量所引用。只有當(dāng)一個對象沒有任何引用變量引用它時,系統(tǒng)的垃圾回收機(jī)制才會在核實(shí)的時候回收它。
總的來說,棧中存儲的是基礎(chǔ)變量以及一些對象的引用變量,基礎(chǔ)變量的值是存儲在棧中,而引用變量存儲在棧中的是指向堆中的對象的地址,這就是修改引用類型總會影響到其他指向這個地址的引用變量的原因。堆是運(yùn)行時動態(tài)分配內(nèi)存的,存取速度較慢,棧的優(yōu)勢是存取速度比堆要快,并且棧內(nèi)的數(shù)據(jù)可以共享,但是棧中數(shù)據(jù)的大小與生存期必須是確定的,缺乏靈活性。
Normalizr與范式化范式化(Normalization)是數(shù)據(jù)庫設(shè)計(jì)中的一系列原理和技術(shù),以減少數(shù)據(jù)庫中數(shù)據(jù)冗余,增進(jìn)數(shù)據(jù)的一致性。直觀地描述就是尋找對象之間的關(guān)系,通過某種方式將關(guān)系之間進(jìn)行映射,減少數(shù)據(jù)之間的冗余,優(yōu)化增刪改查操作。Normalizr庫本身的解釋就是Normalizes nested JSON according to a schema),一種類似于關(guān)系型數(shù)據(jù)庫的處理方法,通過建表建立數(shù)據(jù)關(guān)系,把深層嵌套的數(shù)據(jù)展開,更方便靈活的處理和操作數(shù)據(jù)。
來看個官網(wǎng)的例子,理解一下:
{ "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "commenter": { "id": "2", "name": "Nicole" } } ] }
這是一份博客的數(shù)據(jù),一篇文章article有一個作者author, 一個標(biāo)題title, 多條評論,每條評論有一個評論者commenter,每個commenter又有自己的id和name。這樣如果我們要獲取深層級的數(shù)據(jù),如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] });
然后調(diào)用一下 Normalize,就可以得到扁平化后的數(shù)據(jù),如下:
{ "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" }
這樣每個作者、每條評論、每篇文章都有對應(yīng)的id, 我們就不需要遍歷,可以直接拿對應(yīng)的id進(jìn)行修改。
再來看下我們在項(xiàng)目中的示例代碼:
分別定義element、section 和 page三張表,并指定它們之間的關(guān)系。這樣范式化后,想對某個頁面某個模塊或者某個元素進(jìn)行增刪查改,就直接拿對應(yīng)的id,不需要再耗性能去遍歷了。
Immutable與持久化Facebook工程師Lee Byron花了3年時間打造Immutable,與 React 同期出現(xiàn)。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 就是一旦創(chuàng)建,就不能再被更改的數(shù)據(jù)。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象。Immutable 實(shí)現(xiàn)的原理是 Persistent Data Structure(持久化數(shù)據(jù)結(jié)構(gòu)),也就是使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時,要保證舊數(shù)據(jù)同時可用且不變。Immutable 使用了 Structural Sharing(結(jié)構(gòu)共享),即如果對象樹中一個節(jié)點(diǎn)發(fā)生變化,只修改這個節(jié)點(diǎn)和受它影響的父節(jié)點(diǎn),其它節(jié)點(diǎn)則進(jìn)行共享,這樣就避免了深拷貝帶來的性能損耗。
我們通過圖片來理解一下:
Immutable 內(nèi)部實(shí)現(xiàn)了一套完整的持久化數(shù)據(jù)結(jié)構(gòu),有很多易用的數(shù)據(jù)類型,如Collection、List、Map、Set、Record、Seq(Seq是借鑒了Clojure、Scala、Haskell這些函數(shù)式編程語言,引入的一個特殊結(jié)構(gòu))。它有非常全面的map、filter、groupBy、reduce、find等函數(shù)式操作方法。它的Api很強(qiáng)大,大家有興趣可以去看下。這里簡單列舉 updateIn/getIn 來展示它帶來的一些便捷操作:
var obj = { a: { b: { list: [1, 2, 3] } } }; var map = Immutable.fromJS(obj); // 注意 fromJS這里實(shí)現(xiàn)了深轉(zhuǎn)換 var map2 = Immutable.updateIn(["a", "b", "list"], (list) => { return list.push(4); }); console.log(map2.getIn(["a", "b", "list"])) // List [ 1, 2, 3, 4 ]
代碼中我們要改變數(shù)組List的值,不必一層一層獲取數(shù)據(jù),而是直接傳入對應(yīng)的路徑修改就行。這種操作在數(shù)據(jù)嵌套越深時,優(yōu)勢更加明顯。來看下我們業(yè)務(wù)代碼的示例吧。
這里在多個頁面的模塊配置中,要更新某個頁面的某個模塊的數(shù)據(jù),我們只需要在updateIn傳入對應(yīng)的path和value,就可以達(dá)到預(yù)想的效果。篇幅有限,更多的示例請自行查看api。
熟悉React的同學(xué)也基于它結(jié)構(gòu)的不可變性和共享性,用它來能夠快速進(jìn)行數(shù)據(jù)的比較。原本React中使用PureRenderMixin來做DOM diff比較,但只是淺比較,當(dāng)數(shù)據(jù)結(jié)構(gòu)比較深的時候,依然會存在多余的diff過程。這里只提個點(diǎn),不深入展開了,感興趣的同學(xué)可以自行g(shù)oogle。
與 Immutable.js 類似的,還有個seamless-immutable,它的代碼庫非常小,壓縮后下載只有 2K。而 Immutable.js 壓縮后下載有16K。大家各取所需,根據(jù)實(shí)際情況,自己斟酌下使用哪個比較適合。
優(yōu)缺點(diǎn)什么事物都有利弊,代碼庫也不例外。這里列舉下它們的優(yōu)缺點(diǎn),大家權(quán)衡利弊,一起來看下:
Normalizr 可以將數(shù)據(jù)扁平化處理,方便對深層嵌套的數(shù)據(jù)進(jìn)行增刪查改,但是文檔不是很清晰,大家多查多理解,引入庫文件也會增大。Immutable 有持久化數(shù)據(jù)結(jié)構(gòu),如List/Map等,并發(fā)安全。其次,它支持結(jié)構(gòu)共享,比cloneDeep 性能更優(yōu),節(jié)省內(nèi)存。第三,它借鑒了Clojure、Scala、Haskell這些函數(shù)式編程語言,引入了特殊結(jié)構(gòu)Seq,支持Lazy operation。Undo/Redo,Copy/Paste,甚至?xí)r間旅行這些功能對它來說都是小菜一碟。缺點(diǎn)方面,Immutable源文件過大,壓縮后有15kb。而且它侵入性強(qiáng),與原生api容易混淆。此外,類型轉(zhuǎn)換比較繁瑣,尤其是與服務(wù)器交互頻繁時,這種缺點(diǎn)就更加明顯。當(dāng)然,也可以根據(jù)業(yè)務(wù)需求,衡量下是否用seamless-immutable,它使用 Object.defineProperty (因此只能在 IE9 及以上使用) 擴(kuò)展了 JavaScript 的 Array 和 Object 對象來實(shí)現(xiàn),只支持 Array 和 Object 兩種數(shù)據(jù)類型。但是代碼庫非常小,壓縮后下載只有 2K。
總結(jié)篇幅有限,時間也比較晚了,關(guān)于前端數(shù)據(jù)的扁平化與持久化處理先講這么多了,有興趣的同學(xué)可以關(guān)注下,后面有時間會多整理分享。
參考資料前端數(shù)據(jù)范式化
Immutable詳解及React中實(shí)踐
為什么需要Immutable.js
facebook immutable.js 意義何在,使用場景?
一些鏈接, 關(guān)于不可變數(shù)據(jù)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97315.html
摘要:同源策略同源策略是一種約定,由公司年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到等攻擊。 一、Vue變化檢測 背景 初始化對象,屬性未知;某些事件觸發(fā)時,對象改變(新增屬性),Vue監(jiān)聽不到 原因 Vue.js 不能檢測到對象屬性的添加或刪除,因?yàn)閂ue.js 在初始化實(shí)例時將屬性轉(zhuǎn)為 getter/setter,所以屬性必須在 dat...
摘要:故障處理設(shè)計(jì)微服務(wù)架構(gòu)所帶來的一個后果就是必須考慮每個服務(wù)的失敗容錯機(jī)制。因此,微服務(wù)非常重視建立架構(gòu)及相關(guān)業(yè)務(wù)指標(biāo)的實(shí)時監(jiān)控和日志機(jī)制。 微服務(wù)架構(gòu)入門 1. 微服務(wù)簡介 微服務(wù)是一種架構(gòu)風(fēng)格,一個大型的復(fù)雜軟件由一個或多個微服務(wù)組成。系統(tǒng)中每個微服務(wù)都可以被獨(dú)立部署,各個微服務(wù)之間是松耦合的。每個微服務(wù)僅關(guān)注于完成一件任務(wù)并很好地完成任務(wù)。在所有情況下,每個任務(wù)代表這一個小的業(yè)務(wù)能...
摘要:前端芝士樹如何完成數(shù)組的扁平化問題描述輸入一個嵌套型數(shù)組輸出扁平化后的數(shù)組如果只是兩層的數(shù)據(jù)如果是多層嵌套的數(shù)組 【前端芝士樹】如何完成數(shù)組的扁平化 Array flattern? 問題描述 輸入:一個嵌套型數(shù)組輸出:扁平化后的數(shù)組 let array = [1, [2, 3, 4]]; let arrayDeeper = [1, [2, [3, 4]]]; 如果只是兩層的數(shù)據(jù) fun...
閱讀 3411·2023-04-25 20:37
閱讀 3152·2021-09-07 09:59
閱讀 1675·2019-08-29 12:43
閱讀 1195·2019-08-28 18:27
閱讀 489·2019-08-26 13:50
閱讀 2041·2019-08-26 10:33
閱讀 3602·2019-08-23 18:39
閱讀 2411·2019-08-23 18:09