原文出處:極限前端javascript
當下前端開發框架設計顯然已經在mvvm方式上又發展了一步,virtual dom
提出不久,使用前端代碼來調用native的思路就開始被實踐。相信你們也知道是什麼東西。到了今天,咱們不得不認可,mnv*
框架開發時代已經到來。html
mnv是什麼,具體能夠這麼理解,model-Native-View-*
,然後面的則能夠認爲是 virtual dom
或 mvvm
中的 ViewModel
,或者咱們也能夠本身使用controller來實現的調用方式。想一想這樣定義是很是合適的。相比以前的不一樣,就是用 nativeView
代替了 htmlView
。那麼咱們再看看下從dom api
到 mnv*
,咱們爲何會看到這樣的變化。前端
在此以前不得不提下以前的dom交互框架,就是直接選擇找到特定的dom進行操做,思路十分直接也很實用,經過dom交互框架,相比JavaScript原生API,咱們能夠比較高效的處理dom的改變和事件綁定了,這種高效的方式給咱們到來了效率上的提升,可是頁面複雜了就很差處理了。java
隨着ajax技術的盛行,SPA應用開始被普遍運用。SPA的引入將整個應用的內容都在一個頁面中進行異步交互。這樣,原有的dom交互方式開發就顯得很差管理,例如某SPA頁面上交互和異步加載的內容不少,咱們的作法是每一次請求渲染後作事件綁定,異步請求後再作另外一部分事件綁定,後面以此類推。當全部異步頁面所有調用完成,頁面上的綁定將變得十分混亂,各類元素綁定,渲染後的視圖內容邏輯不清,又不得不聲明各類變量保存每次異步加載時返回的數據,由於頁面交互須要對這些數據作操做,最後寫完,項目代碼就成了一鍋粥。react
爲了更方便的統一管理頁面上的事件、數據和視圖內容,就有了早期mvc的框架設計。mvc能夠認爲是一種設計模式,其基本思路是將dom交互的過程分爲調用事件、獲取數據、管理視圖。即將以前全部的事件、數據、視圖分別統一管理。用model來存放數據請求和數據操做,視圖來存放對視圖的操做和改變,controller用來管理各類事件綁定。ajax
例如,SPA中的每一個異步頁面能夠當作是一個組件,以前的作法是每一個組件獨立完成本身的數據請求操做、渲染和數據綁定,可是組件多了,每一個組件本身去作就比較混亂,邏輯比較混亂。到了mvc裏面,全部的組件數據請求、渲染、數據綁定都到一個統一的model、view、controller註冊管理。後面的操做咱們就不在管你有多少個組件了,你要調用必需要經過統一的model、view、controller來調。通俗來講就像是組件交出了本身控制權到一個統一的地方註冊調用,這樣就方便了不少,相信你們都已經瞭解過,這裏就省篇幅不舉例了。算法
mvp能夠跟mvp對照起來看,並且咱們也不多專門去關注它。和mvc同樣,mvc的M就是 Model, V就是View,而P,則表明Presenter,它與Controller有點類似。不一樣的是,在mvc中V會直接展示M,而在mvp中V會把全部的任務都委託給P。V和P會互相持有reference,所以能夠互相調用。編程
例如咱們能夠在MVC代碼上作一點改變,寫成這樣:react-native
<div controller="Controller.vp" id="text">html</div>
var Controller = new Controller(); Controller['vp']= new VP({ $el: $('text'), click: fn(e){ console.log(self.$el.html()); }, mouseenter: function(e){ console.debug(self.$el.html()); }, mouseleave: function(e){ console.info(self.$el.html()); } });
幾個好處,這樣將view和Controller的引用關聯了起來,而MVC通常是經過事件監聽或觀察者的異步方式來實現的,咱們能夠在任意地方定義註冊監聽事件都不會有問題,這樣監聽的事件和觸發這個事件的html元素脫離了引用,當應用複雜起來後要維護dom的交互邏輯就比較麻煩了。而mvp提供了一個簡單的引用,將元素對應的操做於對應的presenter關聯起來。咱們要查詢元素對應的controller時只要經過Controller.vp就能夠直接調用了,其實這個時候就和mvvm的定義方式有點相似了,不是嗎?設計模式
mvvm概念能夠認爲是一個自動化的presenter,也這個時候進一步弱化了C層,任何操做都經過viewModel來驅動。Controller最終在頁面上的行爲經過directives的形式體現,經過對directives的識別來註冊事件,這樣管理起來就更清晰了。看一個mvvm框架定義的例子。
<form action="" id="form"> <label for="text" q-html="label"></label> <input type="text" q-value="value" q-model="value" q-mydo="number | getValue"> <button q-on="click: submit"></button> </form>
let viewModel = new VM({ $el: '#form', data:{ label: '用戶名', value: '輸入初始值', number: 0 }, method:{ submit(){ // doSubmit } }, directive:{ mydo(value){ console.log(value); } }, filter:{ getValue(){ reutrn value ++; } } })
和MVP的定義比較,有點相似,mvvm設計一個很大的好處是將mvc中controller中的controller註冊到相對應的元素中,讓咱們後期維護時很快定位,免去了查看controller中event列表的工做,並且初始化後自動作數據綁定,能將頁面中全部同類操做複用,大大節省了咱們本身寫代碼作綁定的時間。這段代碼中初始化時自動幫咱們就作了數值填充、數據雙向綁定、事件綁定的事情。那麼框架怎樣幫我作的呢。咱們來看下new VM作了哪些事情:這裏傳入了元素、數據、方法列表、自定義directive列表,首先程序找到這個元素,開始對這個元素的屬性節點進行遍歷,一旦遍歷到屬性名稱含有q-開頭的屬性是,認爲是mvvm框架自定義的屬性,而後會對屬性的指進行特殊處理;例如遍歷到q-html="label"
時,將data中的label值賦給這個元素的innerHTML;若是遍歷到q-on="click: submit"
時,將這這個元素上綁定click事件,事件回調函數爲submit;也能夠自定義q-mydo
的指令,遍歷到該節點屬性是,調用directive中的mydo方法,輸入參數爲data中的getValue方法返回的值,getValue輸入參數爲number值,這裏的getValue被稱爲過濾器。
這裏要知道的是q-
開頭的屬性指令是框架約定的,不一樣的框架約定的不同,例如ng-
、v-
、ms-
,這些你們也都見過或用過。這裏viewModel建立進行綁定的原理就這麼簡單,按照這個思路去擴充,就能夠本身寫一個mvvm框架。固然完整的框架涉及東西多的多,含有豐富的directive、filter、表達式、vm完善的api和甚至一些兼容性處理等。
總結來講從mvc到mvp,而後到mvvm,前端設計模式仍然是向着易實現、易維護、易擴展的基本方向發展的。但目前前端各種框架也已經成熟並開始版本迭代。可是,這尚未結束,咱們依然沒有脫離dom編程的基本套路,一次次框架的改進只是提升了咱們的開發效率,可是dom元素的效率仍沒有獲得本質的提高。
爲了改進dom交互的效率,或者說是儘可能減小dom交互的次數,virtual dom的概念當下十分盛行,目前圈內各類大小團隊紛紛投入項目使用。由於viewModel的改變最終仍是要實時操做dom來刷新view層,而dom對象的操做相對於JavaScript對象的操做仍然是要慢些。緣由很簡單,dom節點對象的內置屬性不少,就建立一個dom對象而言,dom的建立須要處理各類內置屬性的初始化,而若是使用JavaScript對象來描述就簡單了。
使用virtual dom,頁面的渲染過程再也不是數據直接經過前端模板渲染到頁面,也不是初始化viewModel進行頁面模板填充和事件綁定,而是經過dom衍生描述語法(這爲何稱爲DOM衍生描述語法,一般咱們經過html來描述,可是目前一些框架是經過非標準的html的方式描述的,定義的一套迎合本身框架的方式,其實使用html也是能夠的)解析生成virtual dom,頁面交互變成了是修改virtual dom,而後將virtual dom的改變反映到htmlView層上。
例如如下結構:
<ul id="ui-list"> <li class="ui-list=item">1</li> <li class="ui-list-item">2</li> <li class="ui-list-item">3</li> </ul>
可使用以下javascript來表示
var element = { tagName: 'ul', props: { id: 'ui-list' }, children: [ {tagName: 'li', props: {class: 'ui-list-item'}, children: ["1"]}, {tagName: 'li', props: {class: 'ui-list-item'}, children: ["2"]}, {tagName: 'li', props: {class: 'ui-list-item'}, children: ["3"]}, ] }
若是javascript對象children屬性第三個元素要被移除,同時,添加一個class爲ui-list-item2的li節點,則首先須要對javascript對象進行修改記錄全部的操做,最後將修改的vitual dom變化反映到頁面上:
<ul id="ui-list"> <li class="ui-list=item">1</li> <li class="ui-list-item">2</li> <li class="ui-list-item2"></li> </ul>
這裏的javascript對象就至關於virtual dom,用戶的某個交互操做可能致使dom的多個地方,若是沒有vitual dom,那可能就要進行屢次dom操做,virtual dom則能夠將多個用戶交互操做反映在virtual dom上,最後作的virtual dom DIFF算法而後再dispatch到頁面view層上。相對於mvvm,在頁面初始化渲染階段,也避免了掃面節點,解析directives,要知道這些操做都是dom操做,使用virtual dom顯然能將頁面渲染速度提升很多。
若是說vitual dom減小了dom的交互次數,那麼mnv*想要作的一件事情就是徹底拋棄使用dom,那樣就只能在view層作改進了,使用nativeView來代替目前html的view,而交互邏輯依然可使用viewModel、virtual Dom或者controller來實現,具體就看實現的方式了。
要作到NativeView的操做,這裏與以前不一樣之處就是調用時經過衍生HTML語法經過解釋器執行nativeView的渲染,這是就須要在native和衍生HTML語法之間添加一層解釋器來解析現有的view描述語法了。好比咱們看一個渲染Native的例子:
// iOS import React, { Component, } from 'react'; import { TabBarIOS, NavigatorIOS } from 'react-native'; class App extends Component { render() { return ( <TabBarIOS> <TabBarIOS.Item title="React Native" selected={true}> <NavigatorIOS initialRoute={{ title: 'React Native' }} /> </TabBarIOS.Item> </TabBarIOS> ); } }
這裏和vitual dom框架相似的地方都是都使用衍生的html描述語法來表示view層,而不一樣的是mnv模式是調用的nativeView來實現的衍生html的view展現。其實這裏和上節中的實現惟一不一樣的地方是這裏的view是native view。固然這只是一種實現,目前mnv的實現方案已經不止一種了,有人已經實踐了經過mvvm的編程方式來將viewModel渲染轉化爲native view的方案。
總結下來,前端框架一次次進化,先從效率的方向上提高,而後再性能上完善,這裏只是想提出mnv*的一個概念來描述前端native開發的這個階段。目前mnv的開發模式開始進入視線,也在快速地造成和創建生態。但儘管如此,咱們若是須要選擇的技術棧方案,固然仍是以最適合咱們的做爲最高原則。切忌過分設計。