編者按:本文作者 Berwin,W3C效能工作組成員,360導航高階前端工程師。Vue.js早期使用者,《深入淺出Vue.js》(正在出版)作者。
最近看到一篇國外的文章,說現代JS框架存在的根本原因是保持UI與狀態同步、這其實與我這篇文章的思想是一致的,同時也認證了我對現代前端框架的認知是正確的。
現在前端界有三大框架橫行,Vue,React,Angular,幾乎是所有身為一名前端工程師所必備的一項技能。
但是我不知道有多少人仔細思考過為什麼會這樣?
現在的一些應屆生和剛入行的人們,在剛一踏入前端這個行業起就會面臨著是學習Vue還是學習React又或者是學習Angular等這樣的選擇問題。
事實上在早幾年是沒有這個問題的,我們不需要選擇,那時候我們寫前端就是jQuery一把梭,就是幹,幹就完了。
那為什麼現在人們需要選擇各種框架了呢?
其實之所以現在我們需要選擇框架,本質上是因為我們面臨的需求變了。大家肯定都明白如果我們只寫一個純展示資訊的頁面,沒有任何互動功能的頁面,其實即便是現在,我們也是不需要選擇框架的,我們只需要寫幾行CSS和HTML就可以完成任務。
所以是因為我們面臨的需求變得複雜了,我們的應用經常需要在執行時做一些互動。
這裡面有三個很重要的字我標了粗體,叫做執行時(Runtime)。現代的前端開發,我們開發的應用經常需要在執行時來做一些互動,這些互動在早期只是個幻燈片或者Tab切換下拉選單等一些簡單的互動,這些互動用jQuery實現完全沒什麼問題。但現代的前端我們的目標是用Web去PK原生應用,去和Native進行PK。
那這個時候我們會發現用jQuery來開發應用,我們的程式碼變得很難以維護,那為什麼使用現代框架比如Vue,React等就變得容易維護了呢?
這裡面請容我講一個故事,一個小插曲,前幾天我在一個微信群裡面有人討論,Vue和jQuery的區別是什麼,有人非常強烈的說什麼差別是Vue有元件,有什麼這個那個的一些特性。
當時我在微信群裡說了我的觀點,我說Vue和jQuery之間的區別只有一點,宣告式與命令式。
我們可以想一下,我們用jQuery去操作DOM的目的是什麼?是為了區域性更新檢視,換句話說是為了區域性重新渲染。
jQuery是命令式的操作DOM,命令式的區域性更新檢視,而現代主流框架Vue,React,Angular等都是宣告式的,宣告式的區域性更新檢視。
為什麼宣告式的操作DOM就可以讓應用變得好維護了呢?
弄明白這個問題首先我們先簡單介紹下什麼是命令式,什麼是宣告式。
命令式
命令式,像jQuery,我們都是想幹什麼然後就去幹就完了,例如下面的程式碼:
$('.box')
.append('<p>Test</p>')
.xxx()
.yyy()
.jjj()
...
複製程式碼
命令式就是想幹什麼就直接去呼叫方法直接幹就完了,簡單直接粗暴。
試想一個很簡單的場景,比如一個toggle效果,點選一個按鈕,切換顏色。
用命令式寫,我們肯定是這樣寫,如果當前是什麼顏色就讓它變成另外一個顏色。
如果你仔細思考,其實這裡面可以細分成兩個行為,一個是對狀態判斷,另一個是操作DOM。
那什麼是宣告式??
宣告式
宣告式是通過描述狀態與檢視之間的對映關係,然後通過這樣的一個對映關係來操作DOM,或者說具體點是用這樣的對映關係來生成一個DOM節點插入到頁面去。比如Vue中的模板。模板的作用就用是來描述狀態與DOM的對映關係。
同樣的場景,我們用Vue中的模板來實現,當我們用模板描述了對映關係之後,我們在點選按鈕時,我們只需要對顏色這個變數進行修改就可以完成需求。
看到區別了麼?
仔細思考下,用Vue來實現同樣的需求,如果細分來看,我們在邏輯上只有一個行為,只有狀態。而jQuery是兩個行為,狀態+DOM操作。
所以宣告式為什麼可以簡化維護應用程式碼的複雜度?
因為它讓我們可以把關注點只放在狀態的維護上。這樣一來當應用複雜後,其實我們的思維,我們管理程式碼的方式只在狀態上,所有的DOM操作都不用關心了,可以說大大降低程式碼維護的複雜度。
我們不再需要關注怎麼操作DOM,因為框架會幫我們自動去做,我們只關注狀態就好了。
但是如果應用特別特別複雜,我們會發現即便是我們只關注狀態的維護,依然很難,即便只維護狀態也很難,所以才出現了Vuex,Redux等技術解決方案。
什麼是渲染?
經過前面的介紹,你會發現其實現代主流框架要解決的最本質的問題依然是渲染,只是不同框架之間的解決方案有差異,那麼什麼是渲染?
現在開發前端,我們的應用在執行時需要不斷的進行各種互動,現代主流框架讓我們把關注點放在了狀態的維護上,也就是說應用在執行時,應用內部的狀態會不斷的發生變化。
而將狀態生成DOM插入到頁面展示在使用者介面上,這一套流程叫做渲染。
現代前端框架對渲染的處理
當應用在執行時,內部狀態會不斷的發生變化,這時使用者頁面的某個區域性區域需要不停的重新渲染。
如何重新渲染?
最簡單粗暴的解決方式,也是我平時在沒有使用任何框架的專案裡寫的一些簡單的功能時最常用的方式是用狀態生成一份新的DOM,然後用innerHTML把舊DOM替換了。
我寫的小功能塊用這種方式沒問題,因為功能涉及到的DOM標籤少,狀態變的時候,幾乎就是我這個功能塊的所有標籤都需要變,所以即便是用innerHTML也不會有太大的效能浪費,是在可接受範圍內的。
但是框架不行,框架如果用innerHTML這樣去替換,那就不是區域性重新渲染了,而是整個頁面整體重新整理,這性質就變了,那麼框架如何做到區域性重新渲染?
解決這個問題,需要一些技術方案來解決,可以是VirtualDOM,但並不一定必須是VirtualDOM,也可以是Angular中的髒檢測的流程,也可以是細粒度的繫結,像Vue1.0就是使用細粒度的繫結來實現的。
什麼是細粒度繫結?
細粒度的繫結意思是說,當某個狀態,與之繫結的是頁面中的某個具體的標籤。就是說,如果模板中有十個標籤使用了某個變數,那麼與這個變數所繫結的就是10個具體的標籤。
相對比較React和Angular粒度都比較粗,他們的變化偵測其實不知道具體哪個狀態變數,所以需要一個暴力的比對,比對後才知道需要對檢視中的哪個部分進行更新。
而Vue這種細粒度的繫結其實在狀態發生變化的那一個瞬間,立刻就知道哪個狀態變了,而且還知道有哪些具體的標籤使用了這個狀態,那麼事情就變的簡單的多了,直接把與這個狀態所繫結的這些具體的標籤進行更新就能達到區域性更新的目的。
但是這樣做其實也有一定的代價,因為粒度太細,會有一定的依賴追蹤的開銷。所以Vue2.0開始採取了一個折中的方案,就是把繫結調整為中等粒度。
一個狀態對應某個元件,而不再是具體標籤,這樣做有一個好處是可以大大降低依賴的數量,畢竟元件的數量與DOM中的具體標籤比,數量要少的多。但是這樣就需要多一個操作,當狀態發生變化只通知到元件,那麼元件內部如何知道具體更新哪個DOM標籤??
答案是VirtualDOM。
也就是說,當粒度調整為中等之後,需要多一個操作就是在元件內部使用VirtualDOM去重新渲染。
Vue很聰明地通過變化偵測+VirtualDOM這兩種技術方案,提升了框架執行的效能問題。
所以說,Vue2.0引入VirtualDOM並不是因為VirtualDOM有多好,而是恰好VirtualDOM結合變化偵測可以將繫結調整成中等粒度來解決依賴追蹤的開銷問題。
關於變化偵測我專門寫過文章來介紹Vue是如何實現變化偵測的。傳送門。
所以變化偵測的方式,在一定程度上就已經決定了框架如何進行渲染。
關於VirtualDOM的實現原理我寫過一個PPT,有興趣的可以看看,傳送門。
還有一個是模板編譯,其實前面對於模板編譯這個問題並沒有說太多,模板的作用是描述狀態與DOM之間的對映關係,通過模板可以編譯出一個渲染函式,執行這個渲染函式可以得到VirtualDOM中所提供的VNode,事實上你看過我前面介紹VirtualDOM原理的PPT你就會知道VirtualDOM對節點進行diff其實是對VNode進行diff。關於模板編譯的實現原理我專門寫過一篇文章介紹過,傳送門。
最後
最後我想說的話是,現在的前端我個人感覺有點浮躁,很多人都在追新,每天關注一些今天出了一個新特性,明天出了一個新框架什麼的,對於這些我是贊成的,但是我更希望在追新的同時,要看到它的本質。
所有技術解決方案的終極目標都是在解決問題,都是先有問題,然後在有解決方案,解決方案可能並不完美,可能解決方案有很多種,那麼他們之間都有哪些優缺點?解決問題的同時各自都做了哪些權衡和取捨?
我們要透過現象看本質才不至於被表面所迷惑。
關於奇舞週刊
《奇舞週刊》是360公司專業前端團隊「奇舞團」運營的前端技術社群。關注公眾號後,直接傳送連結到後臺即可給我們投稿。