JavaScript框架對比

如今JavaScript框架三分天下,該學哪些和如何學呢?要知道這些問題,必須要將其進行對比。本文介紹了一種比較方式。本文翻譯自Change And Its Detection In JavaScript Frameworks。PS:因爲原文寫於2015年,有些地方在如今看來有些過時或者改變,但原文思考框架的角度仍然使我感到受益。儘管原文有些長,翻譯起來有些麻煩,但我還是堅持翻下來了。但本人知識有限,所以如您發現有翻譯的不對的地方,請不吝指正。

JavaScript框架中的狀態變化及其檢測

在2015年,當談到JavaScript框架時,我們完全感覺不到匱乏。Angular,Ember,React,Backbone還有它們的一衆競爭對手,有太多選擇了。

我們可以在很多方面比較這些框架,但我認爲它們之間的很重要的不同是管理狀態的方式。特別地,瞭解這些框架在狀態隨時間改變時做了什麼是很有用的。而它們又爲我們提供了什麼工具來將變化映射到用戶界面上呢?

管理應用狀態和UI的同步很長時間以來都成爲制約UI發展的主要因素,而我們如今有多種方式來處理這個問題。這篇文章將探討幾種方式:Ember的數據綁定,Angular的髒檢測,React的虛擬DOM,以及他們同不變數據結構之間的關係。

映射數據

我們討論的是獲取程序內部的數據並把它映射到屏幕的可見部分。我們使用對象、數組、字符串、數組,而最終它們會變成一棵文本段落、表單、鏈接、按鈕和圖像樹。在web中,前者通常表現爲JavaScript數據結構,而後者表現爲DOM(文檔對象模型)。

我們通常把這個過程叫做渲染,也可以把它視爲數據模型到可見的用戶界面的映射。當用數據渲染模板時,我們就會得到那些數據的DOM(或HTML)表現。

Model and DOM

它本身是很簡單的:雖然將數據模型映射到UI可能並不總是微不足道的,但它基本上仍然是一個直接輸入輸出的函數。

但當數據隨時間變化時,事情就不那麼簡單了。這可能發生在用戶同界面進行交互,或要更新數據的情況。UI需要映射這次改變。另外,因爲渲染DOM樹的代價是很昂貴的,所以我們需要儘可能只做一點小事就能把改變的數據在屏幕上顯示出來。

Model and DOM changed

因爲涉及了狀態改變,所以這可比只渲染UI一次更困難。解決這個問題的方法也是各個框架的差異之處。

服務器端渲染:重置一切

不存在改變,一切都是永恆的。

在大JavaScript時代之前,我們每次同web應用的交互都會觸發一次到服務器的往返。每次點擊、表單的提交都意味着網頁卸載、向服務器發送請求、服務器返回新的頁面,然後瀏覽器進行渲染。

這裏寫圖片描述

這種方式使得前端並不需要做什麼狀態管理。就瀏覽器而言,每次數據變化,都意味着世界的終結。無論數據怎麼變化那都是後臺需要考慮的事。前端只需要生成HTML和CSS,再添加一點JavaScript潤色。

然而這種在前端看來很簡單的方式是很慢的。不僅因爲每次交互都會產生一次UI的完整的重渲染,而且需要一次完整的從遙遠的數據中心的往返導致的遠程重渲染。

大部分的應用不會這樣做了。我們只是在服務器端初始化狀態,然後轉換爲在前端管理狀態。不過仍然有人再做這種模式的改進。

第一代JS:手動重渲染

我不知道應該重渲染什麼,你來決定吧。

第一代JS框架,像Backbone.js、Ext JS和Dojo,首次在瀏覽器中引入了真實數據模型,以代替綁定在DOM上的一些輕量級的腳本。這也意味着我們第一次能夠在瀏覽器中更改狀態。數據模型的內容可以改變,我們能將這些改變映射到用戶界面中。

雖然這些框架可以使我們把UI代碼從模型中分離出來,但還需要我們來管理它們之間的同步。當改變發生時我們可以獲取到某些事件,但仍需要我們決定重渲染什麼和怎麼做。

這裏寫圖片描述

作爲應用開發者,我們需要仔細考慮這種模型的性能。因爲我們可以按照喜歡的方式控制什麼時候更新和更新什麼。我們是爲了簡單而重渲染頁面的大幅篇章,還是爲了性能只是重渲染更新部分,這值得權衡。

Emberjs:數據綁定

因爲我控制模型和視圖,所以我準確的知道什麼改變了和應該重渲染什麼。

因爲在應用狀態改變時不得不手動判斷,這也是導致第一代JavaScript應用有時很複雜的主要因素。很多框架致力於消除這個特定問題。Ember.js就是其中之一。

Ember像Backbone那樣在數據發生改變時會從數據模型中發出事件。不同點在於Ember爲事件的接收端提供了某些東西。我們把UI綁定道數據模型上,那也意味着有一個連接到UI的更新事件的監聽器。當收到一個事件時,監聽器知道要做什麼更新。

這裏寫圖片描述

這是一種相當有效的機制:儘管初始化時需要作部分綁定工作,但這卻讓後來的保持同步更簡單。當有東西改變時,app中只有確實需要做出改變的部分將激活。

這種方式的最大折中之處在於Ember必須注意數據模型中發生的變化。那意味着我們要將數據保存在繼承Ember的API的特殊的對象中,也需要用特殊的setter方法來改變數據。例如我們不能用foo.x=42,而必須要用foo.set('x',42),等等。

未來可能會因爲ECMAScript 6 中Proxies的到來獲得改善。這讓Ember可以用其綁定代碼裝飾普通對象,以使得與那些對象交互的代碼不一定需要遵守setter慣例。

AngularJS:Dirty Checking

我不清楚什麼改變了,所以我就檢查所有可能需要更新的地方

同Ember一樣,Angular也致力於解決當某些地方更新時不得不手動重渲染的問題。然而,它是從另一個角度解決的。

當在Angular模板中關聯數據時,如表達式{{foo.x}},Angular不僅會渲染數據,而且會爲那個特定值創建監視器。之後,無論應用中在什麼時候發生了改變,Angular都會檢測監視器中的值是否從上一次發生了更改。如果發生了改變,他就會重渲染UI中的值。運行這種監視器的過程叫做髒檢測。

這裏寫圖片描述

這種類型的改變檢測最大的好處是我們可以在數據模型中用任何內容了。Angular對此並沒有限制——它並不在乎。沒有要擴展的對象,也沒有要實現的API。

缺點是因爲數據模型中沒有任何能告知框架改變信息的內置接口,所以框架就不知道變化是否發生和發生在什麼地方。那意味着模型需要被外部改變檢測到,Angular也確實是這樣做的:所有的監視器每次發生任何事情都會運行。點擊事件處理,HTTP響應處理和超時,所有這些都會觸發digest(負責運行所有監視器的過程)。

時刻運行所有監視器聽起來像是性能噩夢,但它出人意料的快。這主要是因爲在實際檢測到改變發生時並不會發生DOM訪問,而純JavaScript相等檢測是廉價的。但當涉及到大型UI或要經常渲染時,額外的性能優化還是需要的。

同Ember類似,Angular會受益於即將到來的標準:特別是,ECMAScript7的Object.observe特性將非常適合Angular,因爲它爲我們提供了一個原生的API來檢測對象屬性的改變。儘管它不會覆蓋Angular所有需要支持的情況,因爲Angular的監視器不只是檢測簡單的對象屬性。

即將到來的Angular2會爲變化檢測方面帶來一些有意思的更新,正如Victor Savkin文章中介紹的那樣。2015.7.3更新:詳情見 Victor’s ng-conf talk

React:Virtual DOM

我不清楚什麼改變了,所以我重渲染一切,看現在有什麼不同。

React有許多有意思的特性,但在本文中將主要關注虛擬DOM。

React,像Angular一樣,沒有將數據模型API強加於我們,它允許我們使用用任何感覺合適的對象和數據結構。那它又是怎麼解決的在發生變化時保持UI和數據同步的問題呢?

React做的事情實際上又將我們帶回以前並不關注狀態改變的服務端渲染的時代:每次當有更新時,都會重新渲染整個UI。這顯然能簡化UI代碼。我們不必太關心React組件中狀態的保持。就像服務器端渲染那樣,我們渲染一次,就完了。當組件發生改變時,它會再次渲染。因爲數據改變而更新組件和初次渲染組件並沒有什麼不同。

這聽起來效率很低,如果就這麼結束了,那它確實如此。然而,React的重渲染用了一種特別的方式。

當React UI渲染時,它會首先渲染到虛擬DOM中,虛擬DOM不是真實DOM對象模型,而是一個輕量級的,用普通對象、數組構成的純JavaScript數據結構,它是真實DOM模型的表現。一個單獨的進程之後會根據這個虛擬的DOM結構創建在屏幕上顯示的相應的真實的DOM節點。

這裏寫圖片描述

然後,當發生改變時,會從頭到尾重新創建虛擬DOM。這個新的虛擬DOM會映射當前新的數據模型的狀態。React現在就有了新舊兩個虛擬DOM數據結構。他之後會對這兩個虛擬DOM做比較,來得到發生變化的集合。也僅有那些改變纔會被應用到真實DOM中:添加元素、改變屬性等。

這裏寫圖片描述

所以React的最大益處或者說其中一個就是我們不需要追蹤改變了。我們只是在每次改變時重新渲染整個UI。虛擬DOM比較算法使得昂貴的DOM操作儘可能的減少。

Om:不變的數據結構

我準確地知道什麼不會改變

儘管React的虛擬DOM足夠快,但當UI變大、渲染頻繁(比如每秒60次)時仍然會遇到性能瓶頸。

原因是每次更新都不得不重新渲染整個(虛擬)DOM,除非像Ember那樣在數據模型中引入一些控件來控制變化的發生。

控制變化的一種方式是支持不變的、持久化的數據結構。這似乎很適合React的虛擬DOM,正如Dabid Nolen在基於React和Clojure的Om庫中所做的工作所示。

永恆不變的數據正如它的名字顯示的那樣,我們不能改變它,只能產生一個新的版本。如果想改變對象的某個屬性,因爲不能直接在存在的對象中更改,所以需要、造一個包含新屬性的對象。由於持久化數據結構的工作方式,它事實上比聽起來更有效率。

這在變化檢測方面意味着,當React組件的狀態只包含不變數據時,有一個突破口:當渲染組件時,若本次狀態和上次指向相同的數據結構,那麼可以跳過重渲染。因爲可以利用組件之前的虛擬DOM,和源於其的整個組件樹。不需要進一步查看,因爲狀態沒發生改變。

這裏寫圖片描述

像Ember那樣,像Om這樣的庫不能讓我們在數據中使用普通的JavaScript對象。要想從中獲利,我們必須要使用不可變數據結構構建模型。這樣做不是因爲這是框架的要求,而是因爲這樣做確實能使管理應用狀態變得簡單。使用不可變數據結構的主要益處不是爲了獲取渲染性能,而是簡化應用程序架構。

儘管Om和ClojureScript可作爲整合React和不可變數據結構的方式,但這也不是唯一的方式。完全可以使用純粹的React和像Facebook Immutable-js這樣的庫。該庫的作者Lee Byron,在最近的React.js大會上做了完美的介紹

我也推薦大家看一下Rich Hickey的持久化數據結構和管理引用,它對這種狀態管理方式做了介紹。

我最近也在研究不可變數據,儘管我不能準確預見到它在前端UI架構的到來。但這似乎在發生,Angular團隊就在努力增加對其的支持

總結

變化檢測是UI發展中的關鍵問題,JavaScript框架也使用很多方式來嘗試解決這個問題。

EmberJS可在變化發生時檢測到,因爲它能控制應用的數據模型接口,並能在調用時觸發事件。

Angular.js能檢測到改變,是通過註冊在UI上的所有數據綁定來得之值是否改變。

普通React通過將整個UI重渲染成虛擬DOM然後同之前的作比較來檢測到數據改變。改變了什麼,就在真實DOM上做相應更新。

不可變數據結構和React配合可以快速標記未改變的組件樹,因爲組件狀態是不允許變動的,從而增強了React的性能。然而這並不是出於性能原因,而是因爲它會爲我們的應用架構帶來積極影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章