JavaScript框架中的變動和變動檢測

2016-01-22    分類:WEB開發、程式設計開發、首頁精華3人評論發表於2016-01-22

本文由碼農網 – 李炳辰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

進入2015年以後,關於JS框架,開發人員有了更多選擇了。除了 Angular, Ember, React, Backbone, 還出現了大量的競爭者,現在有太多的框架可選。

每個人可以從不同的角度對比這些框架,但是我認為最有趣的區別之一是它們管理狀態的方法。尤其,思考一下當狀態經常發生改變的時候,這些框架是如何做的,這是很一件有意義的事情,在這些框架中,他們各自都用什麼方法來反應使用者介面的變化?

管理應用的狀態和使用者介面的一致在很長的時間裡都是一個引發UI開發複雜性的根源。到目前為止, 我們有了幾個不同的處理方式,本篇文章會瀏覽一下它們之中的幾個:Ember的 資料繫結,angular 的髒檢查,React的虛擬DOM 以及它和不可變資料結構的關係。

顯示資料

我們要說的基本任務是關於程式的內部狀態以及怎麼把它放到螢幕上顯示出來的可見元素。你拿到一套物件,陣列,字串和數字,再把它們變成由一個文字組成的樹圖結構的東西,表單,連結,按鈕和圖片。在網頁開發中,前者經常表達為 JavaScript 資料結構, 後面幾個表現為DOM。

我們經常叫這個過程為“渲染”,你可以想象成是把你的資料模型”對映”成可見的使用者介面內容。當你用模板渲染資料時,你得到一個 DOM(或者HTML)來表現資料。

這個過程本身聽起來夠簡單的:儘管對映表單資料模型到UI可能不平凡, 但它畢竟是一個很直接的從輸入到輸出的轉換過程。

當我們提到資料經常改變的時候, 事情變的有挑戰了。當使用者和UI互動,或者不知道世界上什麼東西的改變更新了資料,反正UI需要反映這些變化。而且,因為重建DOM 樹的操作是一個 昂貴的操作,消耗較大,我們願意做盡可能少的工作來讓資料更新到螢幕。

相對於僅僅渲染一次UI,這裡面有一個更有難度的問題, 因為它涉及到狀態更新。這也是產生幾個不同方案的地方。

伺服器端渲染:一切重來

“沒有變化,宇宙是不可變的”

在大JavaScript紀元以前,每一個你和網頁的互動都會觸發一個伺服器端的來回互動,每次點選,每次表單的提交,意味著網頁的重新載入,一個請求被髮送到伺服器端,伺服器處理並響應一個嶄新的頁面,瀏覽器再重新渲染它。

這種情況下,前端不用管理任何狀態,每次一有什麼事情發生了,一切都結束,瀏覽器什麼也不管。無論什麼狀態, 都是伺服器端來管理。前端就是一些伺服器端產生的HTML和CSS, 可能還有淺淺的一點 javascript。

在前端角度來看,這是一個非常簡單的辦法,這樣處理也很慢。不僅僅每次互動意味著一個UI的重新渲染,也是一個資料返回遠端的資料中心,再從遠端回到前臺介面的遠端互動過程。

現在,我們大多數已經不再這麼做了。我們可以在伺服器端初始化我們的應用的狀態,然後在前端來管理這些狀態(這篇同構 JavaScript很大的篇幅都在說這個),儘管仍然還有一些人成功的使用著這種更復雜的方式

第一代 JS : 人工重繪介面

“我不知道要重新繪製哪裡,你指出來吧”

第一代javascript 框架, 像Backbone.js, Ext JS,和 Dojo,第一次向瀏覽器引入了真實的資料模型,代替那些只在DOM上修飾的輕量級指令碼。這也意味著,你第一次在瀏覽器上可以改變狀態。資料模型的內容改變了,然後你把這些改變反應到使用者介面上。

儘管這些框架都在架構上從模型中分離出了UI程式碼,可同步兩者還是要靠你自己來完成。當變動發生時, 你可以得到一套事件,但是應該由你指出那一部分需要重新渲染,和具體怎麼渲染。

在這種模型的效能方面上留給應用開發者很大的發展空間。既然你控制什麼時候什麼內容應當更新,只要你願意, 你可以很好的調優它。簡單的重新渲染頁面上的大塊區域,還是隻更新頁面上的需要更新的那一小部分,這經常是需要做出一些權衡。

Ember.js 資料繫結

“因為我控制著模型和檢視, 我能精確的知道那些應該被重新繪製。”

可以人工的指出什麼狀態改變了需要重新渲染,是第一代javascript應用的複雜性的主要來源。大量的框架的目的就是消除這部分問題。Embe.js就是其中之一。

Ember 和 Backbone相似,當變動發生時,從模型往外發出事件。不同在於Ember還為事件的接受端提供一些功能。你可以把UI和資料模型繫結在一起,這意味著,有一個監聽器附加到UI去偵聽變更事件。這個監聽器接到事件以後知道應該更新什麼。

這就產生了一個很有效的變更機制:通過一開始設定所有的繫結,這樣以後同步的花費就變得少了。當某些東西變了,只有應用裡面的那些確實需要改變的那一部分會變化。

這種方式裡面,最大的折中是當資料模型發生變化,必須通知Ember知道這些變化。這就使你的資料必須要繼承Ember的特定API,你要修改你的資料新增特別的set方法。你不能用foo.x=42 你必須用foo.set(‘x’,42),諸如此類。

將來,這個方式可能在 ECMAScript6 的到來後獲益。它可用繫結方法來讓Ember修飾一般物件,那麼所有的與這個物件互動的程式碼不必再使用這種set的轉換。

AngularJS:  髒檢查

“我不知道什麼改變了,我只是檢查需要更新的所有一切”

和Ember相似,Angular的目標也是解決有變更以後的不得不手工重新渲染的問題。可是,它用的是另一個方式。

當你參考你的Angular 模板程式碼,比如 這個表示式{{foo.x}},Angular 不僅監聽這個資料, 還建立一個這個值的觀察器。在此之後, 不論應用中什麼發生變動,Angular都檢查觀察器中的這個值和上一次比是不是變了。如果變了,就重新渲染這個值到UI。這個處理檢查觀察器的方式就叫 髒檢查。

這種檢測方式的最大好處就是,你可以在模型中隨便用什麼,angular對此沒有約束—它也不關心這個。不需要繼承基礎物件也不需要實現特定的API。

缺點方面就是既然資料模型沒有內建的探測器來告訴框架什麼變了,框架也就沒辦法知道是不是有變化,或者究竟哪裡變了。這就意味著需要通過外部來檢查模型變更,Angular就是這麼做的:不論什麼東西變了,所有的觀察器都跑一遍:點選事件的處理,HTTP響應的處理,時間超時等等,都會產生一個摘要,這就是負責執行觀察器的過程。

每次都執行所有的觀察器聽上去像是一個效能噩夢,但是它實際上是飛快的。這個通常是因為直到實際檢測出一個改變,才會有DOM訪問發生,而純java script檢查引用的消耗還是相當低的。但是當你遇到大的UI或者需要經常性的重新渲染,額外的優化措施就是必不可少的了。

象Ember,Angular也會受益於即將到來的標準:EMACScript7有Object.observe方法很適合Angular,它給你一個本地API來觀察物件的屬性改變。儘管這並不能滿足Angular所有需求,因為觀察器不僅僅觀察簡單物件屬性。

即將到來的Angular2 也會帶來一些有趣的關於前端更新檢查的更新內容,最近有篇文章說道了這個Victor Savkin發表的文章 ,也可以看看這個Victor在 ng-conf 說的話

React: 虛擬DOM

“我不知道什麼變了, 所以我將渲染一切,看看有什麼不同”

React有很多有趣的特性,其中最有趣的就是虛擬DOM。

React和Angular相似,並不強制你使用一個模型API,你可以使用認為合適的任何物件和資料結構。那麼,它是通過什麼來保持UI在改變以後的更新呢?

React做的就是讓我們退回到老的伺服器端渲染的那些日子,我們可以簡單的不管什麼狀態改變: 每當某個地方有變動發生,它就重新繪製整個UI。這個可以極大的簡化UI程式碼。你並不關心怎麼在React 元件裡面維護狀態。就像伺服器端渲染,你渲染一次就行了。當一個元件需要改變,它就再次重新渲染。在第一次渲染和以後的資料更新渲染沒有什麼不同。

這個聽上去極其沒有效率。如果React僅僅做到此, 那當然就是這樣了。然而,React使用了特殊的方式來重新渲染。

當React UI渲染的時候, 它首先渲染到一個虛擬DOM,它不是一個實際的DOM物件,而是一個輕量的純javascript的物件結構,裡面是簡單物件和陣列來表達真實的DOM物件。會有一個特殊處理過程來取得這個虛擬DOM,來建立能在螢幕上顯示的真實的DOM元素。

然後,當有改變的時候,一個新的虛擬DOM就從變化中產生。這個新的虛擬DOM反應了資料模型的新狀態。React現在有2個虛擬DOM:新的和老的。它對這兩個虛擬DOM用一個差異比較演算法得到變動的集合。這些變動,也僅僅是這些變動會被應用到真實的DOM:新增加元素,元素的屬性值改變等等。

用React一個非常大的好處, 或者至少好處之一就是你不需要跟蹤變動。無論新的結果裡面何時何處有變動,你只需要重新渲染整個UI。虛擬DOM的差異檢查方式自動為你做這些,這樣就減少了很多的昂貴DOM操作。

Om: 不可變資料結構

“我能很明確的知道那些東西沒變”

儘管React的虛擬DOM技術已經很快了,但當你想渲染的頁面很大或者很頻繁的時候(超過 每秒60次),仍然會有效能瓶頸。

重新渲染整個(虛擬的和真實的)DOM這件事是真的沒有辦法避免的,除非你在變動資料模型的時候,做一些特別的控制,就像Ember那樣。

一個控制變動的途徑是不可變的持久資料結構

他們看起來能夠很好的和React的虛擬DOM方法一起協作, 就像David Nolen的工作Om 庫演示的那樣,演示基於React 和 ClojureScript.。

關於不可變資料結構的說法是這樣,顧名思義,你不能改變一個物件,你只能產生它的一個新的版本:當你想改變一個物件的屬性的時候,如果你不能改變它,那麼你只能產生一個新的物件並且設定為這個新的屬性。因為持久化資料的工作方式,這個在實際工作中比聽起來更有效。

當React元件狀態都是用不可變資料組成的,變動檢查就會變成這樣的情形:當你重新渲染一個元件的時候,如果元件的狀態仍指向上次你渲染過的同樣的資料結構, 那麼你可以跳過這次渲染,你可以繼續使用這個元件上一次的虛擬DOM,以及整個以這個未變化元件為樹枝節點的內部元件。這時候不需要繼續深入檢測了, 因為沒有任何狀態變化。

就像Ember,那些像Om這樣的類庫不允許你在資料中使用任何老的javascript物件圖。你只能用不可變資料結構從底層開始來構建你的模型才行。我會爭辯說區別就是這一次你不用為了滿足框架的要求來做這件事。你這麼做就是簡單因為這是一個更好的管理應用狀態的辦法。使用不可變資料結構的好處不是為了提示渲染的效能,而是簡化你的應用架構。

Om和ClojureScript在組合React和 不可變資料結構上也是有用的,但他們不是必不可少的。可能僅僅使用純粹React和一個像Facebook的Immutable-js那樣的庫也就足夠了。Lee Byron,這個庫的作者,在React.js Conf上給出了一個關於這個主題的精彩介紹。

我推薦你去看看Rich Hickey的持久化資料結構和引用管理,這也是一篇關於狀態管理方法的介紹。

我在waxing poetic用過不可變資料結構有一段時間了。儘管我不能想當然地預見到它會被應用在前端UI架構。不過看起來這個事情正在發生,Angular團隊的人也在做增加支援這些內容的事情。

總結

變動檢測是UI開發中的中心問題,各種javascript框架都採用各自的辦法來給出不同的解決方式。

EmberJS在當變動發生的當時就可以檢測到改變,是因為他控制了資料模型API,當你呼叫API的做出資料改變時候,就會觸發相應的事件。

Angular.js 在變動發生以後檢測到改變,它的做法是重新跑一遍你註冊在UI上的資料繫結,然後看是不是有值發生了改變。

純React通過重新渲染整個UI到一個 新的虛擬DOM,然後和舊的虛擬DOM做比較來檢測資料改變。發現有什麼改變了,然後作為修訂傳送給真實DOM。

React和不可變資料結構可以作為純React解決方案的加強版,它可以快速的標記元件樹為未變化狀態,因為在React元件內部是不允許狀態改變的。不允許改變內部狀態並不是為了效能原因,而是這麼做對你的應用程式架構有積極影響。

譯者資訊

譯者:李炳辰,就職 HP軟體開發部門,10年以上JAVA產品開發經驗,熟悉C#, Python,  Nodejs。在網際網路電商平臺, 企業軟體開發管理方面均有豐富的經驗。目前興趣在於前端開發,資料統計分析在金融業務方面的應用。

譯文連結:http://www.codeceo.com/article/javascript-change.html
英文原文:Change And Its Detection In JavaScript Frameworks
翻譯作者:碼農網 – 李炳辰
轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]

相關文章