當前位置:
首頁 > 知識 > 那些前端MVVM框架是如何誕生的

那些前端MVVM框架是如何誕生的

以下內容純屬扯淡,跟著文章思路慢慢看。。

刀耕火種時代

有以上html片段,想將其中個人信息替換為jabbla的,我們的做法:

存在的問題

仔細想一想,這種開發方式,在users.jabbla和對應的html結構中間,總感覺有一層膜,除了需要在模板里定義某個元素的id外,還要在js中經過getDom(獲取dom元素)和setDom(設置dom元素)操作。

有沒有一種方法,可以幫助我們省去getDom和setDom,直接將users.jabbla和html結構對應起來。

當然有,由於innerHTML這個屬性,我們可以利用模板系統。

引入模板

有了模板系統之後,我們可以寫下這樣的html片段:

js中渲染模板,替換userInfo內容:

使用這種開發方式,省去了上面getDom和setDom的過程,可以說是"一氣呵成"。

存在的問題

現在我們只實現了初始的內容渲染,如果需要在點擊某個按鈕切換用戶信息:

切換部分的邏輯:

在這裡,我們還是需要獲取nextUserBtn元素,有沒有不需要獲取元素,在寫userInfoTemplate的時候就指定好點擊事件的方法呢?當然有,可以升級模板系統。

增強後的模板系統

我們想要的效果:

js代碼:

如何實現

可以看到,我們現在可以將事件直接放在userInfoTemplate中,然後在js中直接寫替換邏輯,但是這個是如何實現的呢?

如果直接在html結構上綁定事件,事件處理函數無法獲取到js中的作用域。所以換個思路,直接在html結構上綁定這個方式行不通。

想要獲取函數的作用域,必須在dom元素上綁定。只要能將userInfoTemplate中的html結構解析成DOM樹,然後遍曆元素上的屬性獲取事件處理函數標識,再進行綁定就可以了。

於是在這一過程有很多種方案可以選擇,但是思路都是先從userInfoTemplate生成一個類似Dom結構的Tree,再通過遍歷Tree生成最終的DomTree。

在這個階段,現有輪子總體上可以分為以下3個派別:

1. 特定的template語法,使用Parser解析出來的AST生成目標DomTree。

2. 模板語法使用html規範,藉助innerHTML,讓瀏覽器自己生成一個帶有template字元串的fakeDomTree,通過fakeDomTree生成最終設置好的DomTree。

3. 直接將userInfoTemplate的內容寫在js中,通過預處理的方式將模板字元串轉換成聲明virtualDom結構的js代碼,最終使用完整的virtualDom生成DomTree。

各自特點

1. 生成template和DomTree中間結構的過程不依賴瀏覽器環境,支持豐富的模板語法,但是其中Parser是在瀏覽器環境中運行,會帶來一些性能問題,理論上首屏渲染速度會比其它兩種方式遜色一點。

2. 直接利用瀏覽器構建fakeDomTree,可以說性能方面要比帶Parser的方案好很多,理論上首屏渲染速度要比第1種好很多。但是這種方案強依賴於瀏覽器環境,而瀏覽器的不同實現是不穩定因素。

3. 通過對模板語法的預處理,直接生成virtualDom結構聲明的代碼,理論上這種方案是3個方案中首屏渲染速度最快的,它沒有從template到virtualDom這一過程。比較受爭議的一點是:模板與js必須寫在一起,有人說這種方案是圖靈完備的,模板也具有完整的編程能力。又有聲音說:html,css,js寫在一起的這種方式是有悖潮流的。見仁見智吧。

存在問題

使用以上討論的三種方式解決了事件綁定之後,還有一個問題需要我們亟待解決。

可以看到,上面我們在點擊「下一個用戶」按鈕之後,會再次調用templateEngine.render()這個方法。例子中還只是一個html片段,而現在我們寫的template是整個頁面的模板,如果再重新渲染整個頁面,重複template``到DomTree這一過程,一點非常小的改動都會導致整個頁面的重新渲染,這樣會使得頁面性能會非常差。

所以,我們需要局部更新功能。

引入局部更新

什麼是局部更新呢?

為了避免整個頁面重新渲染,使得在每次數據發生變化之後,只渲染那些需要更新的部分。

更新策略

主流框架分為兩個派別:

1. 將Dom元素與數據綁定在一起(創建觀察者),當數據發生變化之後,執行更新Dom元素的操作

2. 對比數據變化前後生成的兩個類Dom結構樹,將改變映射到真實的Dom樹

對比:

1. 會創建很多觀察者常駐內存,隨著頁面越來越複雜,性能可能是個問題

2. 每次改變都會重新生成新的Dom元素,所以數據與Dom元素無法形成綁定的關係。另外一點,diff演算法的好壞直接決定了局部更新的性能。

檢測變化

三種方式:

1. 臟檢查:遍歷觀察者,判斷改變前後值是否發生變化,也就是臟檢查,是主動的。

2. 懶檢測:劫持數據的改變行為,每當行為發生,就做一次變化檢測,是"懶"的,在需要的時候進行。

3. 不檢測:不關心某個數據是否已經發生變化,只關心前後兩個最後輸出的類Dom結構的變化。

方案組合

結合更新策略和檢測變化的幾種方案,得到局部更新的幾個方案

基於臟檢查

1. 臟檢查+數據綁定Dom元素:在某些特定時刻,自動執行臟檢查,更新臟數據對應的Dom元素。

2. 臟檢查+類Dom結構樹對比:臟檢查檢測的單位可以具體到每個屬性,這樣會讓類Dom結構樹對比的範圍更加精確,diff演算法的性能會更好。

這兩種方案都比較依賴臟檢查,而臟檢查最大的缺陷就是性能問題,它會遍歷所有的觀察者,不管這些觀察者對應的數據是否已經發生變化。所以說這兩種方案最大的缺陷就是臟檢查的性能問題。

基於懶檢測

1. 懶檢測+數據綁定Dom元素:每次屬性的改變行為發生,對比前後值,更新對應Dom元素

2. 懶檢測+類Dom結構樹對比:與臟檢查的方案一致,都會讓類Dom結構樹的對比範圍更加精確,會有比較好的diff演算法效率。

看上去這種基於懶檢測的方案,比臟檢查的方案好很多,其實未必,如果不進行特殊處理,頻繁發生數據改變行為,Dom元素會頻繁更新或者頻繁運行diff演算法。需要做的就是合併一定時期內的所有變化,統一進行Dom更新或者diff。

基於不檢測

1. 不檢測+數據綁定Dom元素:這種方案顯然行不通,不檢測數據變化我怎麼更新Dom。。

2. 不檢測+類Dom結構樹對比:手動觸發數據集合到類Dom結構樹的映射,對比前後兩棵樹,將改變映射到真實的Dom樹。

這種方案對比前兩種優勢是不會有很多觀察者常駐內存,不會頻繁觸發更新。而在diff演算法效率方面,前兩種方案會比較有優勢。

存在的問題

現在,貌似已經解決了大部分的問題,這麼多方案已經足夠解決局部更新這個問題了。

又有個問題來了,如果我想復用下面這個結構片段,頁面希望存在一個用戶信息列表,怎麼辦?

按照之前的思路,我們想在構建視圖的時候就需要聲明式地復用這個userInfoTemplate片段。

引入可復用概念

聲明式地復用,可以像下面這樣:

如何實現

在之前解決事件綁定的問題時候,已經分析出來了幾種解決方案,現在回顧一下:

1. 特定的template語法,使用Parser解析出來的AST生成目標DomTree。 2. 模板語法使用html規範,藉助innerHTML,讓瀏覽器自己生成一個帶有template字元串的fakeDomTree,通過fakeDomTree生成最終設置好的DomTree。 3. 直接將userInfoTemplate的內容寫在js中,通過預處理的方式將模板字元串轉換成聲明virtualDom結構的js代碼,最終使用完整的virtualDom生成DomTree。

基於前兩種方案

從template到最終DomTree,中間會生成一個用於生成DomTree的類Dom結構,不管它是AST或者fakeDomTree,本質都是一樣的,我們都需要walk(遍歷)這個中間產物(下面的文章稱之為"Tree")。

當編譯器遇到類似userInfoTemplate這樣的自定義標記時,就得將這個元素視為一個可復用單位(我們習慣稱之為"組件"),然後在當前作用域內尋找該組件的定義。

組件定義

什麼是組件定義呢?

首先得包括幾個基本元素,1. 模板,2. 狀態,3. 事件處理函數 函數定義,可描述組件的這幾個基本元素的可復用數據結構,目前來看只有"類"了,比如像下面這樣:

像下面這樣就是實例化一個組件:

作用域

有人可能就問了,為什麼還有個"作用域"呢?因為考慮到這裡的組件標記會被重複定義,如果所有的組件公用一個命名空間,隨著頁面規模越來越大,很容易導致命名衝突的情況。

在這裡暫且劃分為兩類作用域,全局作用域和組件作用域。在全局作用域註冊的組件在任何作用域內都能尋找到該組件的定義,而在組件作用域內註冊的組件只能在該作用域內可以尋找到相關的組件定義。

比如可以像下面這樣註冊組件:

基於第3種方案

這種方案使得我們可以在js中寫模板,而且這種模板具有完整的js編程能力,所以,除了上面說的"類",我們還能使用"函數"達到復用目的。

這種方案組件中的基本元素和上面的差不多,只不過少了"模板",因為模板在預處理階段已經被轉化成了聲明virtualDom的js代碼。

而且這種方案中沒有"作用域"概念,作用域的出現只是為組件提供了一個命名空間,方便解析模板時找到相應的組件定義,現在"模板"已經寫在了js中,就無需再額外定義組件名稱尋找相關定義了。

總結

在引入組件化之後,其實還有很多情況需要考慮,比如生命周期,組件通信等等,不過這些都不在這篇文章的考慮範圍。

以上已經總結了前端MVVM框架中比較基礎也是最重要的東西,其它功能其實都是圍繞這些核心脈絡來拓展的。

從模板方案-->局部更新-->組件化,經歷過這幾個階段的洗禮其實最後會呈現出各種方案組合,於是就看到了現在層出不窮的框架。

原文:https://zhuanlan.zhihu.com/p/36453279 作者:Gloria


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 JavaScript 的精彩文章:

我是如何在前端領域賺到年薪30W的?
作為前端開發工程師,你進階的瓶頸集中分析突破了嗎?

TAG:JavaScript |