MVVM原理,你看了也會vue MVVM
請問vue中雙向資料繫結是如何實現的?MVVM原理是什麼?
vue中的雙向資料繫結是採用資料劫持結合釋出者-訂閱者模式的方式,透過Object.defineProperty()來劫持各個屬性的setter,getter;在資料變動時釋出訊息給訂閱者,觸發相應的回撥更新函式。透過Observer來監聽model資料變化,透過Compile來解析編譯模板指令;當資料發生變化時,Observer釋出訊息給Watcher(訂閱者),訂閱者透過呼叫更新函式來更新檢視。Watcher搭起了Observer和Compile之間的通訊橋樑,從而達到資料變化 -> 檢視更新;檢視互動變化(input) -> 資料model變更的雙向繫結效果。
實現一個Compile(編譯器)
用於解析指令,並初始化檢視。並在此時建立訂閱者,繫結更新函式,在資料變化時更新檢視或資料。
class Compile{ constructor(el, vm) { // 判斷el是否是一個元素節點物件 this.el = this.isElementNode(el) ? el : document.querySelector(el) this.vm = vm // 1 獲取文件碎片物件 放入記憶體中 減少頁面的迴流和重繪 const fragement = this.node2Fragment(this.el) // 2 編譯模板 this.compile(fragement) // 3 追加子元素到根元素上 this.el.appendChild(fragement) } compile(fragement) { // 1 獲取所有子節點 let childNodes = fragement.childNodes childNodes = [...childNodes] childNodes.forEach(child => { if (this.isElementNode(child)) { // 是元素節點 // 編譯元素節點 // console.log('元素節點', child) this.compileElement(child) } else { // console.log('文字節點', child) // 編譯文字節點 this.compileText(child) } if (child.childNodes && child.childNodes.length) { // 遞迴遍歷編譯所有子節點 this.compile(child) } }) } // 編譯解析元素節點 compileElement(node){ // 元素節點 v-html v-model v-text等指令或者事件繫結 let attributes = node.attributes attributes = [...attributes] // 拿到所有的屬性 解析出指令 attributes.forEach(attr => { const {name, value} = attr // 例如:v-text msg // console.log(attr, name, value) if (this.isDirective(name)) { // 判斷是否是v-開始 表示是一個指令 v-text v-html v-model v-on:click const [, dirctive] = name.split('-') // text html model on:click const [dirName, eventName] = dirctive.split(':') // dirName: text html model on eventName: click // 更新資料 資料驅動檢視 compileUtil[dirName](node, value, this.vm, eventName) // 刪除帶有指令標籤的屬性 node.removeAttribute('v-'+dirctive) } else if (this.isEventName(name)) { // @click="handleClick" let [,eventName] = name.split('@') compileUtil['on'](node, value, this.vm, eventName) // 刪除帶有@的屬性 node.removeAttribute('@'+eventName) } else if (this.isBindName(name)) { let [, attrName] = name.split(':') compileUtil['bind'](node, value, this.vm, attrName) // 刪除帶有:的屬性 node.removeAttribute(':'+attrName) } }) } // 編譯解析文字節點 compileText(node) { // {{}} 對應類似v-text const content = node.textContent if (/\{\{(.+?)\}\}/.test(content)) { // 正則匹配含有雙大括號的文字 並且 // console.log(content) compileUtil['text'](node, content, this.vm) } }
實現一個Update(更新方法)
透過解析指令以及文字,在資料變化時,透過操作dom節點,更新檢視;修改data,更新資料。
// 更新函式 updater: { textUpdater(node, value) { node.textContent = value }, htmlUpdater(node, value) { node.innerHTML = value }, modelUpdater(node, value) { node.value = value }, bindUpdater(node, attrName, value) { node.setAttribute(attrName, value) } } }
實現一個Watcher(訂閱者)
資料發生變化時,呼叫回撥函式,更新檢視或資料。
class watcher{ constructor(vm, expr, cb) { this.vm = vm this.expr = expr this.cb = cb // 先儲存舊值 用於判斷新值傳入時 是否有變化 this.oldVal = this.getOldVal() } getOldVal() { Dep.target = this const oldVal = compileUtil.getVal(this.expr, this.vm) // 在呼叫getVal時會觸發observer中defineReactive中 object.defineProperty 中的get函式 // 在get函式中拿到該watcher並新增到dep中 Dep.target = null return oldVal } update() { const newVal = compileUtil.getVal(this.expr, this.vm) if (this.oldVal !== newVal) { this.oldVal = newVal this.cb(newVal) } } }
編譯模板指令時,初始化一個watcher例項並繫結更新函式
text(node, expr, vm) { let value if (expr.indexOf('{{') !== -1) { // 處理 存在雙大括號的文字 {{personalbar.name}} {{msg}} value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => { // replace 替換回撥函式引數分別有:0 匹配到的字串 1在使用組匹配 組匹配到的值 匹配值在原字串中的索引 原字串 // 繫結觀察者 將來資料發生變化 觸發這裡的回撥 進行更新 new watcher(vm, args[1], (newVal) => { // console.log('newVal', newVal, this.getContentVal(expr, vm)) // 在此有個疑問 newVal和getContentVal重新解析原表示式獲取的值是一樣的 不知作者為啥要重新解析一遍? this.updater.textUpdater(node, this.getContentVal(expr, vm)) }) return this.getVal(args[1], vm) }) } else { // 處理v-text expr: msg vm: 整個例項 new watcher(vm, expr, (newVal) => { this.updater.textUpdater(node, newVal) }) value = this.getVal(expr, vm) } this.updater.textUpdater(node, value) }, html(node, expr, vm) { const value = this.getVal(expr, vm) new watcher(vm, expr, (newVal) => { this.updater.htmlUpdater(node, newVal) }) this.updater.htmlUpdater(node, value) }, model(node, expr, vm) { const value = this.getVal(expr, vm) // 建立監聽者 並透過watcher中的update來繫結回撥這個更新函式 資料 =》 檢視 new watcher(vm, expr, (newVal) => { this.updater.modelUpdater(node, newVal) }) // 檢視 =》 資料 =》 檢視 node.addEventListener('input', (e) => { // 設定值 this.setVal(expr, vm, e.target.value) }) this.updater.modelUpdater(node, value) }, on(node, expr, vm, eventName) { // 找到對應的函式方法 繫結監聽函式 let fn = vm.$options.methods && vm.$options.methods[expr] // 修改函式this指向為當前vue例項 node.addEventListener(eventName, fn.bind(vm), false) }, bind(node, expr, vm, attrName) { const value = this.getVal(expr, vm) this.updater.bindUpdater(node, attrName, value) },
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984138/viewspace-2729394/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- vue之mvvm原理解析VueMVVM
- vue--實現MVVM原理VueMVVM
- 剖析Vue原理&實現雙向繫結MVVMVueMVVM
- 潛入理解MVVM&VueMVVMVue
- Vue.js中的MVVMVue.jsMVVM
- 簡陋版 MVVM 原理實現MVVM
- MVVMMVVM
- 知否?知否?Vue之MVVMVueMVVM
- 理解vue與MVVM三要素VueMVVM
- Proxy實現vue MVVM實踐VueMVVM
- 實現Vue-MVVM-step1VueMVVM
- VUE 中 MVVM – step7 – EventVueMVVM
- Vue、MVVM、MVC、雙向繫結VueMVVMMVC
- 我也來實現一把MVVMMVVM
- MVVM 開發總結 —— Vue 元件(你所需要知道的)MVVMVue元件
- MVVM開發總結——vue元件(你所需要知道的)MVVMVue元件
- 模擬 Vue 手寫一個 MVVMVueMVVM
- Vue 與 MVVM 之間那些事兒VueMVVM
- 淺探VUE的MVVM模式實現VueMVVM模式
- MVVM模式MVVM模式
- 實現 VUE 中 MVVM - step2 - DepVueMVVM
- 基於Vue的簡易MVVM實現VueMVVM
- 實現 VUE 中 MVVM - step11 - ExtendVueMVVM
- VUE - MVVM - part13 - inject & 總結VueMVVM
- 實現 VUE 中 MVVM - step5 - ObserveVueMVVM
- 實現 VUE 中 MVVM - step6 - ArrayVueMVVM
- Vue.js 是如何實現 MVVM 的?Vue.jsMVVM
- Vue.js 和 MVVM 的小細節Vue.jsMVVM
- 是的,你沒看錯!這才是MVVM原理實現的正規軍MVVM
- MVVM原始碼 - 如何實現一個MVVM框架MVVM原始碼框架
- MVC,MVP,MVVMMVCMVPMVVM
- MVC——MVP——MVVMMVCMVPMVVM
- MVC、MVP、MVVMMVCMVPMVVM
- ios MVVM使用iOSMVVM
- 理解MVVM模式MVVM模式
- MVVM With ReactiveCocoaMVVMReact
- 手寫MVVMMVVM
- Android開發者必備的技能你會嗎?MVVM 最新學習心得!AndroidMVVM