JavaScript框架中的變動與變動偵測的圖文詳解
#進入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很大的篇幅都在說這個),儘管仍然還有一些人成功的使用著這種更複雜的方式。
「我不知道要重新繪製哪裡,你指出來吧」
第一代javascript 框架, 像Backbone.js, Ext JS,和 Dojo,第一次向瀏覽器引入了真實的資料模型,取代那些只在DOM上修飾的輕量級腳本。這也意味著,你第一次在瀏覽器上可以改變狀態。資料模型的內容改變了,然後你把這些改變反應到使用者介面上。
儘管這些框架都在架構上從模型中分離出了UI程式碼,可同步兩者還是要靠你自己來完成。當變動發生時, 你可以得到一套事件,但是應該由你指出那一部分需要重新渲染,和具體怎麼渲染。
在這種模型的性能方面留給應用開發者很大的發展空間。既然你控制什麼時候什麼內容應更新,只要你願意, 你可以很好的調優它。簡單的重新渲染頁面上的大塊區域,還是只更新頁面上的需要更新的那一小部分,這經常是需要做出一些權衡。
「因為我控制模型和視圖, 我能精確的知道那些應該被重新繪製。」
可以人工的指出什麼狀態改變了需要重新渲染,是第一代javascript應用的複雜性的主要來源。大量的框架的目的就是消除這部分問題。 Embe.js就是其中之一。
Ember 和 Backbone相似,當變動發生時,從模型往外發出事件。不同在於Ember也為事件的接受端提供一些功能。你可以把UI和資料模型綁在一起,這意味著,有一個監聽器附加到UI去偵聽變更事件。這個監聽器接到事件以後知道該更新什麼。
這就產生了一個很有效的變更機制:透過一開始設定所有的綁定,這樣以後同步的花費就變得少了。當某些東西變了,只有應用裡面的那些確實需要改變的部分會改變。
這種方式裡面,最大的折中是當資料模型發生變化,必須通知Ember知道這些變化。這就讓你的資料必須要繼承Ember的特定API,你要修改你的資料加入特別的set方法。你不能用foo.x=42 你必須用foo.set(‘x’,42),諸如此類。
將來,這個方式可能在 ECMAScript6 的到來後獲益。它可用綁定方法來讓Ember修飾一般對象,那麼所有的與這個對象交互的程式碼就不必再使用這種set的轉換。
「我不知道什麼改變了,我只是檢查需要更新的所有一切」
和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和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操作。
「我能很明確的知道那些東西沒變」
儘管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。在網路電商平台, 企業軟體開發管理方面均有豐富的經驗。目前興趣在於前端開發,數據統計分析在金融業務的應用。
#
以上是JavaScript框架中的變動與變動檢測的圖文詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!