vue是采用数据劫持配合发布者-订阅者模式的方式,通过Obejct.definerProperty()来劫持各个属性的setter和getter,在数据变动时,发布消息给依赖收集器(Dep),去通知观察者(Watcher),做出对应的回调函数,去更新视图。
MVVM作为绑定入口,整合Observer和Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析模板指令,最终利用Watcher搭起Observer,Compile之间的通信桥梁,达到数据变化 ==>视图更新,视图交互变化 ==>数据model变更的双向绑定效果。
实现MVVM原理(参考与b站up视频教学)
Vue.js
/** * options: 传进来的参数 */ class MVue { 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) } } proxyData(data) { for (const key in data) { Object.defineProperty(this, key, { get() { return data[key] }, set(newVal) { data[key] = newVal } }) } } }
Watcher.js
class Watcher{ constructor(vm, expr, cb) { this.vm = vm; this.expr = expr; this.cb = cb; this.oldVal = this.getOldVal(); } getOldVal() { // 把watcher挂在到dep上 Dep.target = this; const oldVal = compileUtil.getVal(this.expr, this.vm); Dep.target = null; return oldVal } update() { const newVal = compileUtil.getVal(this.expr, this.vm); if (newVal !== this.oldVal) { this.cb(newVal) } } }
Observer.js
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, { enumerable: true, configurable: false, get() { //订阅数据变化,当dep中添加观察者 Dep.target && dep.addSub(Dep.target) return value; }, set: (newVal) => { if (newVal !== value) { value = newVal } //数据改变通知视图改变 dep.notify() } }) } }
Dep.js
class Dep{ constructor() { this.subs =[] } //收集观察者 addSub(watcher) { this.subs.push(watcher) } //通知观察者更新 notify() { this.subs.forEach(w =>w.update()) } }
Compile.js
const compileUtil = { getVal(expr, vm) { return expr.split(".").reduce((data, currentVal) => { // console.log(data[currentVal]) return data[currentVal] }, vm.$data) }, getContentVal(expr,vm) { return expr.replace(/\{\{(.+?)\}\}/g, (...args) => { return this.getVal(args[1],vm) }) }, setVal(expr, vm, val) { return expr.split(".").reduce((data, currentVal) => { data[currentVal] = val; },vm.$data) }, text(node, expr, vm) { let value; if (expr.indexOf("{{") !== -1) { value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => { new Watcher(vm, args[1], (newVal) => { this.updater.textUpdater(node,this.getContentVal(expr,vm)) }) return this.getVal(args[1], vm) }) } else { 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); //绑定更新函数, 数据=>视图 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]; node.addEventListener(eventName, fn.bind(vm), false) }, bind(node, expr, vm,eventName) { const value = this.getVal(expr, vm); new Watcher(vm, expr, (newVal) => { this.updater.bindUpdater(node,newVal,eventName) }) this.updater.bindUpdater(node,value,eventName) }, /** * node: 节点 * value: $data所对应的值 * eventName:属性名 */ updater: { textUpdater(node, value) { node.textContent = value; }, htmlUpdater(node, value) { node.innerHTML = value }, modelUpdater(node, value) { node.value = value }, bindUpdater(node,value,eventName) { node.setAttribute(eventName, value) } } } class Compile { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el) this.vm = vm; //获取文档碎片对象 const fragment = this.node2Fragment(this.el); // console.log(fragment) //编译模板 this.compile(fragment) //添加到跟元素 this.el.appendChild(fragment) } /** * * @param {*} fragment 进行模板的编译 */ compile(fragment) { const childNodes = fragment.childNodes; [...childNodes].forEach(child => { if (this.isElementNode(child)) { //元素节点,编译元素节点 this.compileElement(child) } else { //文本节点,编译文本节点 this.compileText(child) } if (child.childNodes && child.childNodes.length) { this.compile(child) } }) } /** * * @param {*} node 编译元素节点 */ compileElement(node) { const attributes = node.attributes; [...attributes].forEach(attr => { const { name, value } = attr; if (this.isDirective(name)) { const [, dirctive] = name.split("-"); const [dirName, eventName] = dirctive.split(":"); //更新数据 数据驱动视图 compileUtil[dirName](node, value, this.vm, eventName) //删除有指令的标签属性 node.removeAttribute(‘v-‘ + dirctive) } else if (this.isEventName(name)) { let [, eventName] = name.split("@"); compileUtil[‘on‘](node, value, this.vm, eventName) } else if (this.isBindName(name)) { let [, eventName] = name.split(":"); compileUtil[‘bind‘](node, value, this.vm, eventName) //删除动态绑定属性, 如 :html=‘index‘ node.removeAttribute(‘:‘+eventName) } }) } isBindName(attrName) { return attrName.startsWith(":") } /** * * @param {*} attrName 处理属性@ */ isEventName(attrName) { return attrName.startsWith("@") } /** * * @param {*} attrName 处理属性名v- */ isDirective(attrName) { return attrName.startsWith("v-") } /** * * @param {*} node 编译文本节点 */ compileText(node) { const content = node.textContent; if (/\{\{(.+?)\}\}/.test(content)) { compileUtil[‘text‘](node, content, this.vm); } } /** * * @param {*} el 获取当前元素下所有节点 */ node2Fragment(el) { const f = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { f.appendChild(firstChild) } return f; } /** * * @param {*} node 判断是否时元素节点 */ isElementNode(node) { return node.nodeType === 1 } }
index.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> </head> <body> <div id="app"> <h1>123</h1> <h3 v-text="msg">456</h3> <h4 v-text="class.age"></h4> <p v-html="msg" :html="index"></p> <div>{{msg}}</div> <input type="text" v-model="msg"> <div v-bind:data="msg">我时谁</div> <span :value="val">vue模拟MVVM属性绑定</span> <button @click="btn" :value="index">点击</button> </div> <script src="./mVue.js"></script> <script src="./Compile.js"></script> <script src="./Watcher.js"></script> <script src="./Dep.js"></script> <script src="./Observer.js"></script> <script> var vm = new MVue({ el: "#app", data: { msg: ‘a‘, class: { age: 12 }, name:"哈哈", index:123, val:"终于完成了" }, methods: { btn() { console.log(123) } } }) </script> </body> </html>
原文:https://www.cnblogs.com/wangyisu/p/12850502.html