最近看了珠峯的架構課——實現一個MVVM。html
首先,咱們來了解一下什麼是MVVM。vue
MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行爲抽象化,讓咱們將視圖 UI 和業務邏輯分開。固然這些事 ViewModel 已經幫咱們作了,它能夠取出 Model 的數據同時幫忙處理 View 中因爲須要展現內容而涉及的業務邏輯。node
先貼一下代碼,而後再作分析。npm
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>myMVVM</title> </head> <body> <div id="root"> <input type="text" v-model='person.name' > <h1>{{message}}</h1> <ul> <li>1</li> <li>2</li> </ul> {{person.age}} <br /> <button v-on:click="change">點我</button> <br /> {{getNewName}} <div v-html="message"></div> </div> <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> --> <script src="Vue.js"></script> <script> let vm = new Vue({ el: '#root', data: { message: 'she is a bad girl', person: { age: 20, name: 'zhangsan' } }, computed: { getNewName() { return this.person.name + 'hahaha' } }, methods: { change() { console.log("11111") } } }) </script> </body> </html>
MVVM的簡易實現(還有不少功能沒有涉及到)架構
/** * 訂閱發佈 調度中心 */ class Dep { constructor() { this.subs = [] // 存放全部的watcher } // 添加watcher, 訂閱 addSub(watcher) { this.subs.push(watcher) } // 發佈 notify() { this.subs.forEach(watcher => watcher.update()) } } /** * 觀察者模式 觀察者,被觀察者 */ class Watcher { constructor(vm, expr, cb) { this.vm = vm this.expr = expr this.cb = cb // 默認先存放舊值 this.oldValue = this.get(vm, expr) } // 獲取舊的值 get() { Dep.target = this let value = CompileUtil.getVal(this.vm, this.expr) Dep.target = null return value } // 數據更新 update() { let newVal = CompileUtil.getVal(this.vm, this.expr) if(newVal != this.oldValue) { this.cb(newVal) } } } /** * 實現數據劫持 */ class Observer { constructor(data) { this.observe(data) } observe(data) { if(data && typeof data == 'object') { for(let key in data) { this.defineReactive(data, key, data[key]) } } } defineReactive(obj, key, value) { this.observe(value) let dep = new Dep() // 給每個屬性都加上一個具備發佈訂閱的功能 Object.defineProperty(obj, key, { get() { Dep.target && dep.addSub(Dep.target) return value }, set: (newValue) => { if(newValue != value) { this.observe(newValue) value = newValue dep.notify() } } }) } } /** * 編譯模板 */ class Compiler { constructor(el, vm) { this.vm = vm // 判斷el是不是個元素,若是不是就獲取它 this.el = this.isElementNode(el) ? el : document.querySelector(el) // console.log(this.el) // 把當前節點中的元素放到內存中 let fragment = this.node2fragment(this.el) // 把節點中的內容進行替換 // 編譯模板 用數據來編譯 this.compile(fragment) // 把內容再塞回頁面中 this.el.appendChild(fragment) } // 是不是指令 v-開頭的 isDirective(attrName) { // startsWith('v-') 或 split('-') return attrName.indexOf('v-') !== -1 } // 編譯元素 compileElement(node) { let attributes = node.attributes // [...attributes] 或 Array.from 等價 Array.prototype.slice.call Array.from(attributes).forEach(attr => { let { name, value: expr } = attr if(this.isDirective(name)) { // let [, directive] = name.split('-') // console.log(name,node, expr, directive) if(directive.indexOf(':' !== -1)) { let [directiveName, eventName] = directive.split(':') CompileUtil[directiveName](node, expr, this.vm, eventName) } else { CompileUtil[directive](node, expr, this.vm) } } }) } // 編譯文本 找{{}} compileText(node) { let content = node.textContent if(/\{\{(.+?)\}\}/.test(content)) { // console.log(content) // 找到全部文本 CompileUtil['text'](node, content, this.vm) } } // 編譯內存中的dom節點 核心的編譯方法 compile(node) { let childNodes = node.childNodes Array.from(childNodes).forEach(child => { if(this.isElementNode(child)) { this.compileElement(child) // 若是是元素的話,須要除去本身,再去遍歷子節點 this.compile(child) } else { this.compileText(child) } }) } // 把節點移動到內存中 appendChild方法 node2fragment(node) { let fragment = document.createDocumentFragment() let firstChild while(firstChild = node.firstChild) { fragment.appendChild(firstChild) } return fragment } // 判斷是否爲元素節點 isElementNode(node) { return node.nodeType === 1 } } /** * 編譯工具函數 */ CompileUtil = { // 根據表達式取對應的數據 getVal(vm, expr) { return expr.split('.').reduce((data, current) => { return data[current] }, vm.$data) }, getContentVal(vm, expr) { return expr.replace(/\{\{(.+?)\}\}/g, (...args) => { return this.getVal(vm, args[1]) }) }, setVal(vm, expr, value) { expr.split('.').reduce((data, current, index, arr) => { if(index === arr.length - 1) { return data[current] = value } return data[current] }, vm.$data) }, // 解析v-model指令 model(node, expr, vm) { // node.value let fn = this.updater['modelUpdater'] new Watcher(vm, expr, (newVal) => { // 給輸入框加一個觀察者,稍後數據更新,觸發此方法,用新值給輸入框賦值 fn(node, newVal) }) node.addEventListener('input', e => { let value = e.target.value this.setVal(vm, expr, value) }) let value = this.getVal(vm, expr) fn(node, value) }, on(node, expr, vm, eventName) { node.addEventListener(eventName, e => { vm[expr].call(vm, e) }) }, text(node, expr, vm) { let fn = this.updater['textUpdater'] let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => { // 給表達式每一個變量都加上觀察者 new Watcher(vm, args[1], () => { fn(node, this.getContentVal(vm, expr)) // 返回一個全的字符串 }) return this.getVal(vm, args[1]) }) fn(node, content) }, html(node, expr, vm) { // node.innerHTML let fn = this.updater['htmlUpdater'] new Watcher(vm, expr, (newVal) => { // 給輸入框加一個觀察者,稍後數據更新,觸發此方法,用新值給輸入框賦值 fn(node, newVal) }) let value = this.getVal(vm, expr) fn(node, value) }, updater: { modelUpdater(node, value) { node.value = value }, textUpdater(node, value) { node.textContent = value }, htmlUpdater(node, value) { node.innerHTML = value } } } /** * Vue構造函數 */ class Vue { constructor(options) { this.$el = options.el this.$data = options.data let computed = options.computed let methods = options.methods if(this.$el) { // 作數據劫持 new Observer(this.$data) // console.log(this.$data) for(let key in computed) { // 依賴關係 Object.defineProperty(this.$data, key, { get: () => { return computed[key].call(this) } }) } for(let key in methods) { Object.defineProperty(this, key, { get() { return methods[key] } }) } // vm上的取值操做都代理上vm.$data上 this.proxyVm(this.$data) // 編譯模板 new Compiler(this.$el, this) } } // 代理 proxyVm(data) { for(let key in data) { Object.defineProperty(this, key, { get() { return data[key] }, set(newVal) { data[key] = newVal } }) } } }
未完待續......app