MVVM实现原理图
vue采用数据劫持配合订阅者和发布者模式的方式 ,
通过 Object.defineProperty 的setter 和 getter 对数据进行劫持 ,
在数据变化时, 发布消息给依赖器 Dep(订阅者), 去通知观察者Watcher 做出对应的回调函数 , 进行视图更新
MVVM 作为绑定入口 , 整合Observer , Compile 和Watcher 三者 , Observer来监听model数据变化 ,
Compile来解析编译模板指令 , 最终利用Watcher 搭建起 Compile , Observer , Watcher 之间的通信桥梁 ,
达到数据 变化=> 视图更新 ; 视图交互变化 => 数据model变更的双向绑定效果
OBserver.js
class Watcher { //观察者 作用 去观察新值与旧值是否有变化 ,如果有变化则更新 constructor(vm, expr, cd) { //需要vm , expr 来获取旧值和新值是否有更新 , cd为回调 ,进行数据更新 // 先将形参保存 this.vm = vm; this.expr = expr; this.cd = cd; //先保存旧值 this.oldVal = this.getOldVal() } getOldVal() { //获取旧值 Dep.target = this; //new MVue()时建立watcher , 将watcher挂载到dep上 const oldval = compileUtil.getVal(this.expr, this.vm); Dep.target = null; //获取到旧值后销毁 , 不然每次改变旧值添加新的观察者 , 不易维护 return oldval } updata() { //作用 ,判断新值与旧值是否有变化 , 有变化则回调回去更新视图 const newval = compileUtil.getVal(this.expr, this.vm); if (newval !== this.oldval) { //旧值与新值不相等 this.cd(newval) } } }
class Dep { //作用 , 1.通知watcher更新 , 2.收集watcher constructor() { //收集依赖 , 不需要传参 this.subs = []; //用于存储观察者 } //收集观察者 addSub(watcher) { this.subs.push(watcher); } //通知观察者去更新 notify() { console.log('观察者', this.subs) //遍历数组 , 找到对应的观察者进行更新 this.subs.forEach(w => w.updata()) //观察者需要有更新视图的方法updata() } } class Observer { constructor(data) { this.observer(data) } observer(data) { //data为对象 , 此处只针对对象处理 if (data && typeof data === 'object') { //针对对象进行遍历 Object.keys(data).forEach(key => { this.defineRective(data, key, data[key]) }) } } defineRective(obj, key, value) { //递归遍历 this.observer(value) //劫持数据的时候创建收集依赖器 const dep = new Dep(); //劫持 Object.defineProperty(obj, key, { enumerable: true, //是否可遍历 configurable: false, //是否可更改编写 get() { //获取值时走这里 //订阅数据变化时 , 往Dep中添加观察者 Dep.target && dep.addSub(Dep.target) //watcher从哪来? //Dep =>>订阅器 //订阅消息=>绑定监听 发布消息 =>触发事件 return value }, set: (newVal) => { //设置值走这里 this.observer(newVal) if (newVal != value) { //如果旧值不等于新值 , 则将新值赋给旧值 value = newVal; } //更改数据后 通知观察者更新 dep.notify() } }) } }
1. 初始化数据时 , 劫持所有属性 走Observer中 Object.defineProperty 的get方法 通知订阅者Dep去收集每个属性上绑定的观察者Watcher , 通过dep.addSub 存放 subs 中
2.更改数据时 , 触发Observer 中Object.defineProperty 的set方法 , 此时 通知订阅者Dep , 数据有变化 , 订阅者通过 dep.notify() , 对存在subs 数组中属性绑定的观察者Watcher进行遍历 并触发Watcher.updata进行视图更新
需要注意:
1. 何时在所有属性上绑定观察者Wacther ?
当初始化页面 , 解析指令渲染页面前 , 对不同指令模板的所有属性分别添加Watcher
并在 Observer 中 Object.defineProperty 的get方法中往收集依赖器中存放Wacther
2.何时往Dep中添加观察者 , 并能获取到数据变化的属性上绑定的Wacther?
通过 Dep.target = this 来获取 Wacther 并在数据变化后对之前的Wacther进行销毁 Dep.target = null
避免不断添加Wacther , 不易维护
结合 Vue源码解析--实现一个指令解析器 Compile 基础上在解析指令渲染视图时添加观察者 并实现Proxy代理 和 input 视图驱动数据
MVue.js
const compileUtil = { getVal(expr, vm) { return expr.split('.').reduce((data, currentVal) => { return data[currentVal] }, vm.$data) }, setVal(expr, vm, inputVal) { //对于嵌套数据用不了 return expr.split('.').reduce((data, currentVal) => { data[currentVal] = inputVal }, vm.$data) }, getContentVal(expr, vm) { return expr.replace(/{{(.+?)}}/g, (...args) => { return this.getVal(args[1], vm); }) }, 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); }, updater: { textUpdater(node, value) { node.textContent = value; }, htmlUpdater(node, value) { node.innerHTML = value; }, modelUpdater(node, value) { node.value = value; } } } class Complie { constructor(el, vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el) this.vm = vm; //1.获取文档碎片对象 ,放入内存中编译,减少回流和重绘 const fragment = this.node2Fragment(this.el); // 2.header编译模板 this.compile(fragment) //3.追加字节点到根元素 this.el.appendChild(fragment) } compile(fragment) { //1.获取每一个子节点 const childNodes = fragment.childNodes; [...childNodes].forEach(child => { if (this.isElementNode(child)) { //是元素节点 // console.log(child) //编译元素节点 this.compileElement(child) } else { //是文本节点 // 编译文本节点 this.compileText(child) } if (child.childNodes && child.childNodes.length > 0) { this.compile(child) } }) } compileElement(node) { const attributes = node.attributes; [...attributes].forEach(attr => { const { name, value } = attr; if (this.isDirective(name)) { //是一个指令 v-model v-text v-on:click const [, direction] = name.split('-'); //text on:click const [dirName, eventName] = direction.split(':'); //分割on:click text html on click //dirName== html || text || no //compileUtil[dirName]需要在compileUtil中分别找到解析html text no 的方法 // 传入 node value 需要知道哪个节点的哪个值 //传入vm 需要在拿到 data中value 对应的值 //eventName 如果有事件 传入事件 // console.log(node,value,this.vm,eventName) compileUtil[dirName](node, value, this.vm, eventName) //策略模式 数据驱动视图 //删除有指令的标签上的属性 node.removeAttribute('v-' + direction) } else if (this.isEventName(name)) { //解析@符操作 let [, eventName] = name.split('@'); compileUtil['on'](node, value, this.vm, eventName) //策略模式 数据驱动视图 } }) } compileText(node) { const content = node.textContent; if (/{{(.+?)}}/.test(content)) { compileUtil['text'](node, content, this.vm) //策略模式 数据驱动视图 } } node2Fragment(node) { //创建文档碎片 const f = document.createDocumentFragment(); let firstChild; while (firstChild = node.firstChild) { f.appendChild(firstChild) } return f; } isElementNode(el) { return el.nodeType === 1 } isDirective(attrName) { //判断是否已v-开头 return attrName.startsWith('v-') } isEventName(attrName) { return attrName.startsWith('@') } } class MVue { constructor(options) { this.$data = options.data; this.$el = options.el; this.$options = options; if (this.$el) { //实现观察者 new Observer(this.$data) //实现指令解析器 new Complie(this.$el, this); //Proxy代理 this.proxyData(this.$data) } } proxyData(data) { for (const key in data) { Object.defineProperty(this, key, { get() { return data[key]; }, set() { data[key] = newVal; } }) } } }