1.前端框架的根本意義前端
1.1 前端框架的好處vue
最開始學習前端框架的時候(我第一個框架是 React)並不理解框架能帶來什麼,只是由於你們都在用框架,最實際的一個用途就是全部企業幾乎都在用框架,不用框架就 out 了.前端框架
隨着使用的深刻我逐漸理解到框架的好處:數據結構
1.2 前端框架的根本意義架構
簡單來講,前端框架的根本意義是解決了UI 與狀態同步問題。app
在 Vue 中咱們若是要在todos
中添加一條,只須要app4.todos.push({ text: '新項目' })
,這時因爲 Vue 內置的響應式系統會自動幫咱們進行 UI 與狀態的同步工做.框架
<div id="app-4"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div>
var app4 = new Vue({ el: '#app-4', data: { todos: [ { text: '學習 JavaScript' }, { text: '學習 Vue' }, { text: '整個牛項目' } ] } }) //在此我向你們推薦一個前端全棧開發交流圈:619586920 突破技術瓶頸,提高思惟能力
若是咱們用 JQuery 或者 JS 進行操做,免不了一大堆li.appendChild
、document.createElement
等 DOM 操做,咱們須要一長串 DOM 操做保證狀態與 UI 的同步,其中一個環節出錯就會致使 BUG,手動操做的缺點以下:異步
不論是 vue 的數據劫持、Angular 的髒檢測仍是 React 的組件級 reRender都是幫助咱們解決 ui 與狀態同步問題的利器。函數
這也解釋了Backbone做爲前端框架鼻祖在以後落寞的緣由,Backbone只是引入了 MVC 的思想,並無解決 View 與 Modal 同步的問題,相比於現代的三大框架直接操做 Modal 就能夠同步 UI 的特性, Backbone 仍然與 JQuery 綁定,在 View 裏操做 Dom來達到同步 UI 的目的,這顯然是不符合現代前端框架設計要求的。組件化
UI 在 MVVM 中指的是 View,狀態在 MVVM 中指的是 Modal,而保證 View 和 Modal 同步的是 View-Modal。
Vue 經過一個響應式系統保證了View 與 Modal的同步,因爲要兼容IE,Vue 選擇了 Object.defineProperty
做爲響應式系統的實現,可是若是不考慮 IE 用戶的話,Object.defineProperty
並非一個好的選擇target=https%3A%2F%2Fjuejin.im%2Fpost%2F5acd0c8a6fb9a028da7cdfaf)。
咱們將用 Proxy 實現一個響應式系統。
2.1 發佈訂閱中心
一個響應式系統離不開發布訂閱模式,由於咱們須要一個 Dep保存訂閱者,並在 Observer 發生變化時通知保存在 Dep 中的訂閱者,讓訂閱者得知變化並更新視圖,這樣才能保證視圖與狀態的同步。
/** * [subs description] 訂閱器,儲存訂閱者,通知訂閱者 * @type {Map} */ export default class Dep { constructor() { // 咱們用 hash 儲存訂閱者 this.subs = new Map(); } // 添加訂閱者 addSub(key, sub) { // 取出鍵爲 key 的訂閱者 const currentSub = this.subs.get(key); // 若是能取出說明有相同的 key 的訂閱者已經存在,直接添加 if (currentSub) { currentSub.add(sub); } else { // 用 Set 數據結構儲存,保證惟一值 this.subs.set(key, new Set([sub])); } } // 通知 notify(key) { // 觸發鍵爲 key 的訂閱者們 if (this.subs.get(key)) { this.subs.get(key).forEach(sub => { sub.update(); }); } } }
咱們在訂閱器 Dep
中實現了一個notify
方法來通知相應的訂閱這們,然而notify
方法到底何時被觸發呢?
固然是當狀態發生變化時,即 MVVM 中的 Modal 變化時觸發通知,然而Dep
顯然沒法得知 Modal 是否發生了變化,所以咱們須要建立一個監聽者Observer
來監聽 Modal, 當 Modal 發生變化的時候咱們就執行通知操做。
vue 基於Object.defineProperty
來實現了監聽者,咱們用 Proxy 來實現監聽者.
與Object.defineProperty
監聽屬性不一樣, Proxy 能夠監聽(實際是代理)整個對象,所以就不須要遍歷對象的屬性依次監聽了,可是若是對象的屬性依然是個對象,那麼 Proxy 也沒法監聽,因此咱們實現了一個observify
進行遞歸監聽便可。
/** * [Observer description] 監聽器,監聽對象,觸發後通知訂閱 * @param {[type]} obj [description] 須要被監聽的對象 */ const Observer = obj => { const dep = new Dep(); return new Proxy(obj, { get: function(target, key, receiver) { // 若是訂閱者存在,直接添加訂閱 if (Dep.target) { dep.addSub(key, Dep.target); } return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { // 若是對象值沒有變,那麼不觸發下面的操做直接返回 if (Reflect.get(receiver, key) === value) { return; } const res = Reflect.set(target, key, observify(value), receiver); // 當值被觸發更改的時候,觸發 Dep 的通知方法 dep.notify(key); return res; }, }); }; /** * 將對象轉爲監聽對象 * @param {*} obj 要監聽的對象 */ export default function observify(obj) { if (!isObject(obj)) { return obj; } // 深度監聽 Object.keys(obj).forEach(key => { obj[key] = observify(obj[key]); }); return Observer(obj); }
咱們目前已經解決了兩個問題,一個是如何得知 Modal 發生了改變(利用監聽者 Observer 監聽 Modal 對象),一個是如何收集訂閱者並通知其變化(利用訂閱器收集訂閱者,並用notify通知訂閱者)。
咱們目前還差一個訂閱者(Watcher)
// 訂閱者 export default class Watcher { constructor(vm, exp, cb) { this.vm = vm; // vm 是 vue 的實例 this.exp = exp; // 被訂閱的數據 this.cb = cb; // 觸發更新後的回調 this.value = this.get(); // 獲取老數據 } get() { const exp = this.exp; let value; Dep.target = this; if (typeof exp === 'function') { value = exp.call(this.vm); } else if (typeof exp === 'string') { value = this.vm[exp]; } Dep.target = null; return value; } // 將訂閱者放入待更新隊列等待批量更新 update() { pushQueue(this); } // 觸發真正的更新操做 run() { const val = this.get(); // 獲取新數據 this.cb.call(this.vm, val, this.value); this.value = val; } }
咱們在上一節中實現了訂閱者( Watcher),可是其中的update
方法是將訂閱者放入了一個待更新的隊列中,而不是直接觸發,緣由以下:
所以這個隊列須要作的是異步且去重,所以咱們用 Set
做爲數據結構儲存 Watcher 來去重,同時用Promise
模擬異步更新。
// 建立異步更新隊列 let queue = new Set() // 用Promise模擬nextTick function nextTick(cb) { Promise.resolve().then(cb) } // 執行刷新隊列 function flushQueue(args) { queue.forEach(watcher => { watcher.run() }) // 清空 queue = new Set() } // 添加到隊列 export default function pushQueue(watcher) { queue.add(watcher) // 下一個循環調用 nextTick(flushQueue) }
咱們梳理一下流程, 一個響應式系統是如何作到 UI(View)與狀態(Modal)同步的?
咱們首先須要監聽 Modal, 本文中咱們用 Proxy 來監聽了 Modal 對象,所以在 Modal 對象被修改的時候咱們的 Observer 就能夠得知。
咱們得知Modal發生變化後如何通知 View 呢?要知道,一個 Modal 的改變可能觸發多個 UI 的更新,好比一個用戶的用戶名改變了,它的我的信息組件、通知組件等等組件中的用戶名都須要改變,對於這種狀況咱們很容易想到利用發佈訂閱模式來解決,咱們須要一個訂閱器(Dep)來儲存訂閱者(Watcher),當監聽到 Modal 改變時,咱們只須要通知相關的訂閱者進行更新便可。
那麼訂閱者來自哪裏呢?其實每個組件實例對應着一個訂閱者(正由於一個組件實例對應一個訂閱者,才能利用 Dep 通知到相應組件,否則亂套了,通知訂閱者就至關於間接通知了組件)。
當訂閱者得知了具體變化後它會進行相應的更新,將更新體如今 UI(View)上,至此UI 與 Modal 的同步完成了。
響應式系統雖然是 Vue 的核心概念,可是一個響應式系統並不夠.
響應式系統雖然得知了數據值的變化,可是當值不能完整映射 UI 時,咱們依然須要進行組件級別的 reRender,這種狀況並不高效,所以 Vue 在2.0版本引入了虛擬 DOM, 虛擬 DOM進行進一步的 diff 操做能夠進行細粒度更高的操做,能夠保證 reReander 的下限(保證不那麼慢)。
除此以外爲了方便開發者,vue 內置了衆多的指令,所以咱們還須要一個 vue 模板解析器.