Vue 2.x MVVM实现原理
html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .ulclass { border: 1px solid #ccc; } #ulclass { color: red; } </style> </head> <body> <div id="app"> <input type="text" v-model="name"> <p :id="cls"> {{age}}</p> <div>姓名:{{name}} 年龄:{{age}} 我是{{user.gionlee.name}}</div> <ul v-bind:class="cls"> <li v-text="user.gionlee.name">1</li> <li v-html="user.gionlee.html">2</li> <li v-text="name">3</li> </ul> <button v-on:click="test">v-on点击</button> <button @click="test">@点击</button> </div> </body> <script src="./observer.js"></script> <script src="./mvvm.js"></script> <script> let vm = new MvvM({ el:'#app', data :{ cls:'ulclass', text:'111', name:'admin', age:18, user:{ gionlee:{ name:"123", html:"<p>my name is gionlee!</p>" } } }, methods: { test () { console.log(this,'事件绑定成功!') } } }) </script> </html>
js:
mvvm.js
const compileUtil = { // 深度获取对象的值如: user.name => 'gionlee' getValue (expr,vm) { return expr.split('.').reduce( (data,val) => { return data[val] },vm.$data) }, setValue (expr,vm,value) { return expr.split('.').reduce( (data,val) => { if(typeof data[val] != 'object') { return data[val] = value } return data[val] },vm.$data) }, // 获取文本对象 批量处理 单个标签内使用多个变量如: <div>姓名:{{name}}年龄:{{age}}</div> getContext(expr,vm) { const value = expr.replace(/\{\{(.+?)\}\}/g, (...arg) => { return this.getValue(arg[1],vm) }) return value }, // 渲染 v-text 、 {{name}} text(node,expr,vm) { let value; if(expr.indexOf('{{') !== -1) { value = expr.replace(/\{\{(.+?)\}\}/g, (...arg) => { new Watcher (vm,arg[1], (newValue) => { this.updater.textUpdater(node,this.getContext(expr,vm)) }) return this.getValue(arg[1],vm) }) } else { value = this.getValue(expr,vm) new Watcher (vm,expr, (newValue) => { this.updater.textUpdater(node,newValue) }) } this.updater.textUpdater(node,value) }, // 渲染 v-html html(node,expr,vm) { const value = this.getValue(expr,vm) new Watcher (vm,expr, (newValue) => { this.updater.htmlUpdater(node,newValue) }) this.updater.htmlUpdater(node,value) }, // 渲染 v-model model(node,expr,vm) { const value = this.getValue(expr,vm) // console.log(value) new Watcher (vm,expr, (newValue) => { this.updater.modelUpdater(node,newValue) }) // 输入时对v-model绑定的值进行更新 node.addEventListener('input',(e)=> { this.setValue(expr,vm,e.target.value) }) this.updater.modelUpdater(node,value) }, // 绑定事件 on(node,expr,vm,event){ let fn = vm.$options.methods && vm.$options.methods[expr] node.addEventListener(event,fn.bind(vm),false) }, // 渲染 v-bind bind(node,expr,vm,attrName) { node.setAttribute(attrName,this.getValue(expr,vm)) }, // 更新函数 updater: { textUpdater(node,value) { node.textContent = value }, htmlUpdater(node,value) { node.innerHTML = value }, // 更新 v-model 绑定的变量 modelUpdater(node,value) { node.value = value } } } class MvvM { constructor(options) { this.$el = options.el this.$data = options.data this.$options = options if (this.$el) { new Observer(this.$data) new Compile(this.$el, this) this.proxyData(this.$data) } } // 代理this proxyData(data) { for(const key in data) { Object.defineProperty(this,key,{ get() { return data[key] }, set(newValue) { data[key] = newValue } }) } } } class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm // 获取文档碎片对象 放入内存 减少页面的回流和重绘 const fragment = this.nodeToFragment(this.el) // 编译模板 this.compile(fragment) // 追加子元素到根元素 this.el.appendChild(fragment) } isElementNode(node) { return node.nodeType === 1; } compile(fragment) { // 获取子节点 const childNodes = fragment.childNodes childNodes.forEach(element => { if (this.isElementNode(element)) { // 编译元素节点 this.compileElement(element) } else { // 编译文本节点 this.compileText(element) } // 递归遍历节点 if (element.childNodes && element.childNodes.length) { this.compile(element) } }); } compileElement(node) { const attributes = node.attributes if (attributes && attributes.length) { [...attributes].map((attr,index) => { const {name,value} = attr if(this.isDirective(name)) { const [,directive] = name.split('-') const [dirName,eventName] = directive.split(':') // 更新数据 compileUtil[dirName](node,value,this.vm,eventName) // 删除指令属性 node.removeAttribute('v-' + directive) } else if (this.isEventName(name)) { // 判断事件绑定 使用@时按照v-on处理 let [,eventName] = name.split('@') compileUtil['on'](node,value,this.vm,eventName) } else if (this.isAttrName(name)) { // 使用 :绑定属性时 按照v-bind处理 let [,attrName] = name.split(':') node.removeAttribute(':' + attrName) compileUtil['bind'](node,value,this.vm,attrName) } }) } } compileText(node) { const content = node.textContent if(/\{\{(.+?)\}\}/.test(content)) { compileUtil['text'](node,content,this.vm) } } isAttrName(attrName) { return attrName.startsWith(':') } isEventName (eventName) { return eventName.startsWith('@') } isDirective (attrName) { return attrName.startsWith('v-') } nodeToFragment(el) { // 创建文档碎片 const fragment = document.createDocumentFragment() let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild) } return fragment } }
observer.js
// 观察者 监听数据变化 class Watcher { constructor (vm,expr,cb) { this.vm = vm this.expr = expr this.cb = cb this.oldValue = this.getOldValue() } getOldValue () { Dep.target = this const oldValue = compileUtil.getValue(this.expr,this.vm) Dep.target = null return oldValue } update () { console.log(this.expr) // 有变化更新视图 const newValue = compileUtil.getValue(this.expr,this.vm) if(newValue !== this.oldValue) { this.cb(newValue) } } } // 收集依赖 class Dep { constructor() { this.subs = [] } // 收集观察者 addSub(watcher) { this.subs.push(watcher) } // 通知观察者更新 notify() { this.subs.forEach( watcher => { watcher.update() }) } } class Observer { constructor(data) { this.observe(data) } observe(data) { // 判断是否为对象 if(data && typeof data === 'object') { Object.keys(data).forEach( (key) => { this.defineReactive(data,key,data[key]) }) } } defineReactive(obj,key,value) { // 递归遍历 this.observe(value) const dep = new Dep() // 劫持并监听所有的属性 Object.defineProperty(obj,key,{ configurable:false, enumerable:true, get () { // 订阅的数据发生变化时,向Dep中添加观察者 Dep.target && dep.addSub(Dep.target) return value }, set :(newValue) => { this.observe(newValue) if(newValue !== value) { value = newValue } // 通知Dep变化 dep.notify() } }) } }