「譯」JavaScript框架的探索與變遷

歡迎來我的博客閱讀:「「譯」JavaScript框架的探索與變遷

譯者言

近幾年可謂是 JavaScript 的大爆炸紀元,各種框架類庫層出不窮,它們給前端帶來一個又一個的新思想。從以前我們用的 jQuery 直接操作 DOM,到 BackboneJS、Dojo 提供監聽器的形式,在到 Ember.js、AngularJS 數據綁定的理念,再到現在的 React、Vue 虛擬 DOM 的思想。都是在當前 Web 應用日益復雜的時代,對于如何處理「應用狀態」與「用戶界面」之間如何更新的問題,帶來更先進的解決方案。

本文是一篇從技術上,以數據變更和UI同步為方向,循序漸進的講述 JavaScript 框架如何演進過來的。

本篇文章,給了我一個更加高緯度的視角,來看待 JavaScript 這些個框架。

正文

在 2015 年,JavaScript 框架的選擇并不少。在 Angular,Ember,React,Backbone 以及它們眾多的競爭者中,有足夠多的選擇。

雖然可以通過不少方面來對比這些框架的不同,但是最讓人感興趣的是它們分別如何管理狀態(state)的。特別的,通過思考這些框架分別如何處理狀態變化是很有用的。它們都提供了什么樣的工具讓你把這些變化呈現給用戶?

如何處理應用狀態(app state)與用戶界面(user interface)之間的同步,長期以來都是用戶界面開發如此復雜的主要原因?,F在,我們有幾個不同的處理方案。本文探索以下:Ember 的數據綁定,Angular 的臟檢查、React 的虛擬DOM以及它與不可變數據結構(immutable data structures)之間的聯系。

數據映射 Projecting Data

我們首先討論程序內部的狀態與屏幕所看到的內容之間的映射。你把各種諸如 object,arrays,strings,以及 numbers 轉換成一顆由諸如 texts、forms、links、buttons 和 images 組成的樹狀結構。在 Web 中,前者通常指 JavaScript 中的數據結構,而后者指的是 DOM (Document Object Model)

我們經常稱這個過程為渲染(rendering),你可以想象這個過程是從數據模型到用戶界面的一個映射。當你把數據渲染成一個模板,你得到的是一個 DOM(或者說 HTML)。

[圖片上傳失敗...(image-37a043-1518421446682)]

這個過程本身已經足夠簡單了,數據模型到用戶界面之間的映射,并不總是那么的瑣碎。它基本只是一個接受輸入然后直接輸出的函數。

在我們需要考慮數據開始隨著時間而變化的時候,這件事就變得更有挑戰性了。當用戶進行操作或者其它某些操作導致數據產生變化的時候,用戶界面需要呈現出這些變化。而且,由于重新構建 DOM 樹的代價是極其昂貴的,我們要盡可能產生小的影響。

[圖片上傳失敗...(image-f1252f-1518421446682)]

因為狀態產生了變化,這比只是一次性渲染用戶界面變得更加難。這就到了以下解決方案開始表演的時候了。

服務器渲染 Server-Side Rendering

宇宙是永恒不變的,沒有任何變化

在 JavaScript 新紀元之前,你的 Web 應用的任何交互都會觸發一趟服務器的環繞旅行。每一個點擊和每一個表單提交都會卸載當前頁面,一個請求發送到服務器,服務器響應一個新的頁面,然后瀏覽器重新渲染。

[圖片上傳失敗...(image-b7a7c6-1518421446682)]

這種方式不需要前端管理任何的狀態(state)。就前端范疇而言,當一些事情發生了(后端返回的數據),整個過程就結束了。就算有狀態,那也只是后端的范疇。前端只是由 HTML 和 CSS 構成,也許有時候會有些 JavaScript 撒在表面調味。

從前端來說,這是一個很簡單的實現方式,但也是一個很慢的方式。每一個交互并不僅僅觸發UI的重渲染,還涉及服務器的數據查詢以及服務端渲染。

大多數人已經不再這樣做了,我們可以在服務器端初始化我們的應用,然后轉移到前端來做狀態的管理(這也是 isomorphic JavaScript 致力于的。)。已經有人在類似的更復雜的設計思想中取得成功。

JS第一代革命:手動重渲染

我不知道哪些需要渲染的,你來告訴我。

第一代革命的 JavaScript 框架,如:Backbone.js, Ext JS 以及 Dojo。第一次在瀏覽器端引入了數據模型(Data Model)的概念,代替了以前那些直接操作 DOM 的輕量級的腳本代碼。這意味著你終于可以在瀏覽器端管理狀態了。當數據模型的上下文改變時,你需要做一些工作,讓改變呈現在用戶界面中。

這些框架的體系能分離你的模型和界面代碼,但同時也留下了一大部分同步的工作給你。你可以監聽某類事件的發生,但是你有義務去計算如何重新渲染以及如何落實到用戶界面中。

[圖片上傳失敗...(image-f224ad-1518421446682)]

基于這種模型,作為開發者,你需要考慮大量的性能問題。由于你能控制什么時候和怎么處理更新,你可以從中做任意的做一些調整。這經常會面臨一些權衡:簡單的處理導致大面積的頁面更新,或者強性能的處理來更新一小塊頁面。

Ember.js: 數據綁定

由于我在控制你的模型和試圖,我會確切知道如何重新渲染。

當應用狀態改變的時候,手動處理渲染工作,無可避免的增加了復雜度。很多框架旨在解決這個問題,Ember.js 就是其中之一。

Ember,像 Backbone 一樣,當數據模型改變的時候會觸發某個事件。不同之處在于 Ember 同時提供了一些方法來接收這些事件。你可以把 UI 綁定到數據模型中,這意味著有一個監聽器綁定到了 UI 上。該監聽器當收到事件的時候,知道如何更新 UI。

[圖片上傳失敗...(image-b83a4f-1518421446682)]

這是一個高效率的機制。盡管設置全部的監聽器需要在初始化時多出一些工作,但是之后就能保證同步狀態時的最小影響。當狀態產生變化時, 只有真正需要更新的部分才會發生改變。

這種方式最大的犧牲是 Ember 需要時刻盯著數據模型。這意味著你需要通過 Ember 的 API 封裝你的數據,以及你要更新數據的時候是使用 foo.set('x',42) 而不是 foo.x = 42,以此類推。

在未來 ES6 的 Proxies 可能會對這種模式產生一定的幫助。它讓 Ember 可以通過裝飾 object 來綁定那些監聽器的代碼。這就不用像傳統方式那樣重寫 object 的 setter 方法了。

AngularJS:臟檢查

我不知道什么更新了,所以當更新的時候,我只能檢查所有的東西。

AngularJS 類似于 Ember,當狀態改變的時候,必須人工去處理。但不同的是,AngularJS 從不同的角度來解決問題。

當你在 Angular 模板中引用你的數據,例如這樣的語句 {{foo.x}} ,Angular 不僅僅只是渲染數據,而且會這個特定的數據創建一個觀察者。如此,只要你的應用中發生任何變化,Angular 都會檢查這個觀察者檢視著的數據是否發生了改變。如果發生了改變,就會重新渲染這個數據對應的用戶界面。這個過程稱作臟檢查(Dirty Checking)。

[圖片上傳失敗...(image-336488-1518421446682)]

這種監聽改變的風格最大的好處就是,你可以在你的數據模型中使用任何姿勢。Angular 對此沒有任何限制,它不關心這個。沒有基礎的對象需要擴展,也沒有 API 需要調用。

但壞處就是現在數據模型沒有任何內建的檢測手段告訴告訴框架哪些東西發生了改變,框架對是否或者哪里發生了改變沒有任何洞察力。這意味著數據模型需要通過外部來監聽改變,而 Angular 就是這樣子做的:所有觀察者在任何時間發生的任何改變,都需要被執行一次。點擊事件,HTTP 響應,timeout 方法的觸發,對于這些,觀察者都需要執行一遍。

經常去執行所有觀察者,這聽起來像是性能的噩夢,但是它令人驚訝的快。這主要是因為在檢查到任何改變之前,沒有 DOM 的操作過程,而原生的 JavaScript 引用對象的檢查平均消耗的性能是廉價的。但是當你要處理大量的 UI 或者經常性觸發重新渲染,那么額外的性能優化手段就變得很有必要了。

Ember 和 Angular 都即將得益于即將到來的標準:ECMAScript7 的 Object.observe 功能,很適合 Angular。它提供了原生的 API 給你用來監聽對象屬性的變化。盡管這樣,Angular 不需要支持所有的用例,因為 Angular 的觀察者相對于簡單的監聽對象屬性,可以做到的更好。

即將到來的 Angular 2 在檢測改變這件事上帶來了很多有趣的更新,最近 Victor Savkin 的一篇文章有介紹到。

關于這個主題,也可以看:Victor's ng-conf talk

React: 虛擬 DOM

我不知道到底哪些發生了變化,所以我只能重新渲染所有東西,然后看一下有哪些不同。

React 有很多有趣的特性,但是我們討論的最有趣的特性是虛擬 DOM。

像 Angular 一樣,React 不會對數據模型進行限制,而是讓你使用你認為合適的任何對象和數據結構。那么,它是如何在存在改變的情況下使 UI 保持最新呢?

React 所做的是有效的把我們帶回服務器渲染時代,當時我們還不關心狀態變化:每當某處發生改變的時候,它會從頭重新渲染整個 UI。這可以顯著的簡化 UI 的代碼。大部分情況,你不會關心如何在 React 中維護狀態。就像服務器渲染一樣,渲染一次就算了。當組件需要變更時,它只能再次重新渲染。組價的初始化渲染和更細它的數據之間,沒有任何區別。

如果故事就這么結束的話,它看起來的確非常低效。然而,React 在重新渲染方面,有點特殊。

當 React 進行重新渲染時,它首先會渲染到虛擬 DOM 中,這不是一個實際的 DOM 對象的圖。而是一個輕量級的,有純粹的 object 和 array 組成的純 JavaScript 的數據結構,它代表著一個真實的 DOM 對象的圖。

然后,一個獨立的進程會根據虛擬 DOM 的結構來創建那些在屏幕上顯示的真實的 DOM 元素。

[圖片上傳失敗...(image-597817-1518421446682)]

之后,當變化發生的時候,一個新的虛擬 DOM 會被從頭到尾創建出來。這個新的虛擬 DOM 將映射出數據模型的新的狀態。現在 React 在手上有兩個虛擬 DOM:一個新的,一個舊的。然后會對兩個虛擬 DOM 進行一個對比算法,得出它們之間的一組變化。有且只有這些更改會被應用到真實 DOM 中:此元素已添加,此屬性以改變,等等。

[圖片上傳失敗...(image-f8edb3-1518421446682)]

所以 React 起碼至少有一個好處,就是你不用追蹤變化了。你只需要每次重新渲染整個 UI ,然后無論改變了什么最終都會得到相應的結果。React 的虛擬 DOM 對比算法,能讓你做到這一點,并且最大限度的節省昂貴的 DOM 操作。

Om: 不可改變的數據結構

我確切的知道哪些沒有改變。

雖然 React 的虛擬 DOM 相當的塊,但是當你的 UI 非常龐大或者經常性渲染的時候(例如:每秒高達 60 次),它依然會面臨瓶頸。

問題在于,真的沒辦法每次都渲染出整個虛擬 DOM,除非你引入一些方法來控制數據模型的改變,就像 Ember 做的一樣。

一種控制變化的辦法是 不可改變的,持久化的數據結構。這些看起來似乎很適合使用在 React 的虛擬 DOM 中,正如 David Nolen 在 Om 庫中所做的 工作 那樣,一個構建于 React 和 ClojureScript 之上的庫。

有一點關于不可改變數據結構的是,顧名思義,你永遠不能改變它,只能產生新的版本。如果你想改變一個對象的屬性,你只能新建一個對象和屬性,因為你不能改變已經存在的那一個。由于持久化數據結構的工作方式,這比聽起來更加有效率。

這意味著在檢測變化方面,當 React 組件都只由不可變數據組成的時候,只有一個逃生窗口:當你重新渲染一個組件時,組件的狀態仍然指向上次渲染時的相同數據結構,你就可以跳過這次重新渲染。你可以使用該組件的先前的虛擬 DOM 以及源自該組件的整個組件樹。沒有必要進一步挖掘,因為在這個狀態中所有東西都不可能改變。

[圖片上傳失敗...(image-9dfd4a-1518421446682)]

就像 Ember 一樣,像 Om 的這種庫不允許在你的數據中使用舊的 JavaScript 對象圖。你必須在不可變數據結構中構建你的數據模型,從而才能在其中得到好處。我會贊同這樣的做法,因為這一次你這樣做并不是為了取悅框架本身。你這樣做只是因為這是一個又簡單又好的方式去管理你的應用狀態。使用不可變數據結構的主要好處,并不是提升渲染性能,而是簡化你的應用結構。

雖然 Om 和 ClojureScript 已經講 React 和不可變數據結構融合起來,但是他們并不是圈子里面的唯一組合。而僅僅使用 React 和 Facebook 的 Immutable-js 是完全可能的。這個庫的作者 Lee Byron 在最近的一次 React.js 為主題的會議中進行了一個 精彩的介紹。

同時我建議看一下 Rich Hickey's 的 Persistent Data Structures And Managed References, 去了解狀態管理的方法。

我自己現在一直在為不可變數據數據結構 寫詩,但我絕對沒有預見到它會進入前端 UI 框架行列。它看起來似乎不遺余力的發生著,而 Angular 的人 正在為支持這個而努力著。

總結

檢測變化時 UI 開發中的核心問題,而 JavaScript 框架們以各種方式解決這個問題。

EmberJS 能在它們發生變化的時候檢測到,因為它控制著你的數據模型 API,并且可以在你調用它的時候觸發事件。

Angular.js 是事后進行檢測, 它通過重新運行你已經在 UI 中注冊的所有數據綁定,來檢測它們的值是否已經發生變化。

React 的檢測方法是通過把整個 UI 重新渲染成一個虛擬 DOM,然后和舊的版本進行對比。無論改變了什么,都可以給真實 DOM 打上個補丁。

React 和 不可變數據結構的組合,對比純粹的 React 有所增強,通過快速的在組件樹中標記不可變的節點。因為組件內的變化是不被允許的。但是,這不是主要出于性能的原因,而是由于它對整個應用程序體系結構有積極的影響。

原文鏈接

Changes and Its detection of JavaScript Framework

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374