本文適合對MVVM有必定了解(若有主流框架ng,vue等使用經驗配合本文服用則效果更佳),雖然會用這類框架,可是對框架底層核心實現又不太清楚,或者能說出個因此然,可是讓他本身動手寫又沒有頭緒的碼友。若是還沒據說過MVVM,不妨先收藏着。。。
html
先給低配版的庫起一個響亮的名字,以便於開展教學,入鄉隨俗咱們就叫ta -- SegmentFault.js 吧 (如下簡稱sf.js)
vue
設置在DOM Element上的自定義屬性前綴統一以 sf- 開頭 (如 <input type="text" sf-value="xxx">)git
1. 沒有sf-repeat 2. 不支持select,checkbox,radio等控件的雙向綁定 3. 沒有sf-if 4. 不少都沒有
因爲是教學向,力圖用最簡短易讀的代碼來實現MVVM最主要最基本的功能,故砍掉了部分實現。angularjs
先看演示圖,圖中就是使用sf.js寫得DEMO
github
100多行的低配版不能要求太多,若是看不上低配版的庫,請關閉本教程。
web
首先明白一個概念,什麼是雙向綁定?在說雙向綁定以前,咱們先說說單向顯示。
單向顯示 說白了就是view動態地顯示變量。好比在ng或其它一些主流框架裏相似這種寫法面試
//scope.message= "segmentfault"; <h3 ng-bind="message"></h3> <!-- 運行時生成 --> <h3>segmentfault</h3>
爲何說是單向呢,由於都是 viewModel上某個變量(message) -> view (h3)的一個過程,viewModel上的變量被view所呈現。ajax
再來看看 逆向修改
前面說了單向是viewMode->view的過程,那逆向就是 viewModel <- view的過程,換句話說就是viewModel被view修改的過程。例如angular中segmentfault
<input type="text" ng-model="message">
一旦用戶在input控件中輸入值,便會實時地改變viewModel中message這個變量的值。這是一個view -> viewModel 的過程。app
所謂的雙向綁定就是一個 viewMode ->(顯示) view ->(修改)viewModel 的過程。
雙向綁定 = 單向顯示 + 逆向修改
注意: 單向顯示可能發生在全部的類型DOM節點上,而逆向修改只可能發生在INPUT,SELECT,TEXTAREA等交互型控件上。
若是整明白什麼是雙向綁定了,咱們就來談談設計思路,沒有整明白的同窗請再閱讀一遍.
先看看API的設計
<!-- view --> <div> <h3 sf-text="vm.message"></h3> </div> <script> // --- viewModel --- function ViewModel(){ this.message = "segmentfault"; } var vm = new ViewModel(); </script>
要實現這個功能,咱們的sf庫應該須要哪幾步操做呢?(先本身想一想,獨立思考下)
1. 註冊ViewModel,咱們的庫須要知道哪些object是viewModel 2. 掃描整個DOM Tree找到有哪些DOM節點上被配置了sf-xxxx這個attribute 3. 紀錄這些被單向綁定的DOM節點和viewModel之間的映射關係 4. 使用DOM API, element.innerText = vm.prop, element.value = vm.prop, element.xxxx = vm.prop 來顯示數據
Q:若是咱們要單向綁定不是innerText,value 而是做爲樣式的class,style呢?
A:沒錯,使用sf-class="vm.myClass" sf-style="vm.myStyle"就行了,其它原生屬性也以此類推
"sf-" + native attribute is good!
主流的一些mvvm框架上通常這麼設計API,仍是拿angular舉例子
<input type="text" ng-model="message">
這裏我我的並不認同這種xx-model的命名方式來做爲雙向綁定的一種標識,好比angular的ng-model,或Vue的v-model,撇開前綴不說,這個model很讓人困惑,咱們知道input控件是自己就有value這個native的屬性的,這個屬性就是表明着input輸入和輸出的值,若是要給一個input進行雙向綁定咱們應該很天然而然地使用一個 *-value就能夠了,徹底沒有必要弄出一個新的attribute叫作*-model的,從而增長學習成本。
因此,咱們就設計一個叫作sf-value的attribute來作API
<input type="text" sf-value="vm.message">
拍腦殼想一想,view要改變數據只可能發生在能夠和用戶交互的一些html控件上,好比input家族(text, radio, checkbox), select, textarea上。 像h1~hn家族,這輩子是沒有機會的。
要實現view改寫viewModel,咱們的庫應該須要哪幾步操做呢?(也先本身想一想,千萬不要丟掉獨立思考能力)
1.掃描整個DOM Tree,找到哪些INPUT,SELECT,TEXTAREA節點上被配置了sf-value這個attribute 2.紀錄這些被雙向綁定的DOM節點和viewModel之間的映射關係 3.sf.js庫自動給這個寫DOM加上onchange或者oninput的事件監聽 4.一旦監聽到change/input事件,當即獲取這個DOM的value值,把這個element.value賦給與之綁定的viewModel的變量上。
Q:那麼問題來了,vm.message被input修改了,誰去通知其它一樣綁定了vm.message的view呢?
A:請看下一段
髒檢查大法 這三個字想必你們已經如雷貫耳,我2年多前出去面試的時候被問及最多的就是angular的髒檢查,什麼是髒檢查?angular髒檢查的時機是什麼?
髒檢查的原理就是,拷貝一份copy_viewModel在內存中,一旦有用戶點擊,輸入操做,或ajax請求,setInterval,setTimeout等這些可能致使viewModel發生改變的行爲,框架都會把copy_viewModel和最新的viewModel進行深度比較,一旦發現有屬性(如vm.message)發生變化,則從新渲染與message綁定的DOM節點。
這也是爲何,一旦你沒有使用ng自帶的$http,$interval,$timeout,ng-click這些angular本身封裝的API去操做viewModel,angular都不會自動去同步view,由於已經超出他的管轄範圍了,你必須手動調用apply函數去強制執行一次髒檢查,以同步view。
setter大法
據說VUE是使用的這種同步機制,其核心原理就是使用Object.defineProperty(obj, prop, descriptor)(不瞭解defineProperty的請戳)這個API,在setter中加點料,一旦有任何地方執行 vm.message = "new value"語句,則setter都會被調用,由setter去觸發從新渲染view的邏輯。
相較這兩種同步機制,彷佛setter更加輕便,性能更好。因此本文使用了setter的方式來實現同步機制(關鍵是實現setter機制使用的代碼較少)。
給setter加點料
http://jsbin.com/gosigoh/edit...
因此概括來講一個MVVM庫主要由3塊組成
MVVM庫 = 單向顯示 + 逆向修改 + 同步機制
下圖爲SegmentFault.js的實現機制
其中Renderer負責單向顯示和逆向修改,Watcher負責監視viewModel爲同步機制的核心模塊,
Scanner負責sf.js初始化時掃描DOM Tree生成view和viewModel的映射關係。
SegmentFault模塊則負責維護view-viewModel Map,以及各個模塊間的調度
Q:瞭解了MVVM的實現機制,你可否本身動手也試着用百來行代碼實現一個MVVM庫呢?
好了!本教程第一部分設計篇就寫到這裏,具體coding請移步(下一篇 【教學向】150行代碼教你實現一個低配版的MVVM庫(2)- 代碼篇)
我會用Typescript給出一版實現。
2年前寫了我受夠了angular的笨重,學習曲線陡峭等缺點,本身一怒之下寫下一個輕量的MVVM庫,給她起名叫【Ukulele.js】(跟我一塊兒念『尤克里裏.傑愛死』,固然本文不是這個庫的安利文,請安心服用),一開始寫這個庫出於好玩,後來也加入了愈來愈多的功能,諸如web component的支持,我漸漸發現,其實要寫一個MVVM庫也並非很難,難的是你有沒有決心敲下第一行代碼。後來我把她和【精通angularjs】一塊兒寫在裏簡歷裏,而後就去找工做了。面試的時候被問及最多的問題就是:"說說MVVM的實現機制"。
我今天寫下此文,1是但願有機會看到這篇文章的碼友能真正掌握MVVM的核心機制,2是鼓勵下你們能靜下心來,本身動手寫寫庫,寫寫框架,有些你如今以爲蠻高大上的東西,你仔細一分析,動動腦,真的沒有那麼高大上,普通的碼農也能本身實現
【教學向】150行代碼教你實現一個低配版的MVVM庫(1)- 原理篇
【教學向】150行代碼教你實現一個低配版的MVVM庫(2)- 代碼篇
【教學向】再加150行代碼教你實現一個低配版的web component庫(1) —設計篇
【教學向】再加150行代碼教你實現一個低配版的web component庫(2) —原理篇