c# mvvm模式获取当前窗口_对Vue中的MVVM原理解析和实现

首先你对Vue需要有一定的了解,知道MVVM。这样才能更有助于你顺利的完成下面原理的阅读学习和编写

下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家可以学习到:

1.Vue数据双向绑定核心代码模块以及实现原理

2.订阅者-发布者模式是如何做到让数据驱动视图、视图驱动数据再驱动视图

3.如何对元素节点上的指令进行解析并且关联订阅者实现视图更新

1、思路整理

实现的流程图:

c3e922935e439fb82d8e792464023c1f.png

我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点:

1、实现一个数据监听Observer,对数据对象的所有属性进行监听,数据发生变化可以获取到最新值通知订阅者。

2、实现一个解析器Compile解析页面节点指令,初始化视图。

3、实现一个观察者Watcher,订阅数据变化同时绑定相关更新函数。并且将自己放入观察者集合Dep中。Dep是Observer和Watcher的桥梁,数据改变通知到Dep,然后Dep通知相应的Watcher去更新视图。

2、实现

以下采用ES6的写法,比较简洁,所以大概在300多行代码实现了一个简单的MVVM框架

1、实现html页面

按Vue的写法在页面定义好一些数据跟指令,引入了两个JS文件。先实例化一个MVue的对象,传入我们的el,data,methods这些参数。待会再看Mvue.js文件是什么?

html

5bd92ce1478cf292123c30b99f403aa1.gif
 1  2   
3

{{person.name}} --- {{person.age}}

4

{{person.fav}}

5

{{person.a.b}}

6 7 1 8 2 9 310 11

{{msg}}

12 16 按钮on17 按钮@18
19 20 21 45 46
5bd92ce1478cf292123c30b99f403aa1.gif

2、实现解析器和观察者

MVue.js

5bd92ce1478cf292123c30b99f403aa1.gif
  1 // 先创建一个MVue类,它是一个入口  2 Class MVue {  3     construction(options) {  4         this.$el = options.el  5         this.$data = options.data  6         this.$options = options  7     }  8     if(this.$el) {  9         // 1.实现一个数据的观察者     --先看解析器,再看Obeserver 10         new Observer(this.$data) 11         // 2.实现一个指令解析器 12         new Compile(this.$el,this) 13     } 14 } 15 ​ 16 // 定义一个Compile类解析元素节点和指令 17 class Compile { 18     constructor(el,vm) { 19         // 判断el是否是元素节点对象,不是就通过DOM获取 20         this.el = this.isElementNode(el) ? el : document.querySelector(el) 21         this.vm = vm 22         // 1.获取文档碎片对象,放入内存中可以减少页面的回流和重绘 23         const fragment = this.node2Fragment(this.el) 24          25         // 2.编辑模板 26         this.compile(fragment) 27          28         // 3.追加子元素到根元素(还原页面) 29         this.el.appendChild(fragment) 30     } 31      32     // 将元素插入到文档碎片中 33     node2Fragment(el) { 34         const f = document.createDocumnetFragment(); 35         let firstChild 36         while(firstChild = el.firstChild) { 37             // appendChild  38             // 将已经存在的节点再次插入,那么原来位置的节点自动删除,并在新的位置重新插入。 39             f.appendChild(firstChild) 40         } 41         // 此处执行完,页面已经没有元素节点了 42         return f 43     } 44      45     // 解析模板 46     compile(frafment) { 47         // 1.获取子节点 48         conts childNodes = fragment.childNodes; 49         [...childNodes].forEach(child => { 50             if(this.isElementNode(child)) { 51                 // 是元素节点 52                 // 编译元素节点 53                 this.compileElement(child) 54             } else { 55                 // 文本节点 56                 // 编译文本节点 57                 this.compileText(child) 58             } 59              60             // 嵌套子节点进行遍历解析 61             if(child.childNodes && child.childNodes.length) { 62                 this.compule(child) 63             } 64         }) 65     } 66      67     // 判断是元素节点还是属性节点 68     isElementNode(node) { 69         // nodeType属性返回 以数字值返回指定节点的节点类型。1-元素节点 2-属性节点 70         return node.nodeType === 1 71     } 72      73     // 编译元素节点 74     compileElement(node) { 75         // 获得元素属性集合 76         const attributes = node.attributes 77         [...attributes].forEach(attr => { 78             const {name, value} = attr 79             if(this.isDirective(name)) { // 判断属性是不是以v-开头的指令 80                 // 解析指令(v-mode v-text v-on:click 等...) 81                 const [, dirctive] = name.split('-') 82                 const [dirName, eventName] = dirctive.split(':') 83                 // 初始化视图 将数据渲染到视图上 84                 compileUtil[dirName](node, value, this.vm, eventName) 85                  86                 // 删除有指令的标签上的属性 87                 node.removeAttribute('v-' + dirctive) 88             } else if (this.isEventName(name)) { //判断属性是不是以@开头的指令 89                 // 解析指令 90                 let [, eventName] = name.split('@') 91                 compileUtil['on'](node,val,this.vm, eventName) 92                  93                 // 删除有指令的标签上的属性 94                 node.removeAttribute('@' + eventName) 95             } else if(this.isBindName(name)) { //判断属性是不是以:开头的指令 96                 // 解析指令 97                 let [, attrName] = name.split(':') 98                 compileUtil['bind'](node,val,this.vm, attrName) 99                 100                 // 删除有指令的标签上的属性101                 node.removeAttribute(':' + attrName)102             }103         }) 104     }105     106     // 编译文本节点107     compileText(node) {108         const content = node.textContent109         if(/{{(.+?)}}/.test(content)) {110             compileUtil['text'](node, content, this.vm)111         }112     }113     114     // 判断属性是不是指令115     isDirective(attrName) {116         return attrName.startsWith('v-')117     }118     // 判断属性是不是以@开头的事件指令119     isEventName(attrName) {120         return attrName.startsWith('@')121     }122     // 判断属性是不是以:开头的事件指令123     isBindName(attrName) {124         return attrName.startsWith(':')125     }126 }127 ​128 ​129 // 定义一个对象,针对不同指令执行不同操作130 const compileUtil = {131     // 解析参数(包含嵌套参数解析),获取其对应的值132     getVal(expre, vm) {133         return expre.split('.').reduce((data, currentVal) => {134             return data[currentVal]135         }, vm.$data)136     },137     // 获取当前节点内参数对应的值138     getgetContentVal(expre,vm) {139         return expre.replace(/{{(.+?)}}/g, (...arges) => {140             return this.getVal(arges[1], vm)141         })142     },143     // 设置新值144     setVal(expre, vm, inputVal) {145         return expre.split('.').reduce((data, currentVal) => {146             return data[currentVal] = inputVal147         }, vm.$data)148     },149     150     // 指令解析:v-test151     test(node, expre, vm) {152         let value;153         if(expre.indexOf('{{') !== -1) {154             // 正则匹配{{}}里的内容155             value = expre.replace(/{{(.+?)}}/g, (...arges) => {156                 157                 // new watcher这里相关的先可以不看,等后面讲解写到观察者再回头看。这里是绑定观察者实现     的效果是通过改变数据会触发视图,即数据=》视图。158                 // 没有new watcher 不影响视图初始化(页面参数的替换渲染)。159                 // 订阅数据变化,绑定更新函数。160                 new watcher(vm, arges[1], () => {161                     // 确保 {{person.name}}----{{person.fav}} 不会因为一个参数变化都被成新值 162                     this.updater.textUpdater(node, this.getgetContentVal(expre,vm))163                 })164                 165                 return this.getVal(arges[1],vm)166             })167         } else {168             // 同上,先不看169             // 数据=》视图170             new watcher(vm, expre, (newVal) => {171             // 找不到{}说明是test指令,所以当前节点只有一个参数变化,直接用回调函数传入的新值172         this.updater.textUpdater(node, newVal)173           })174             175             value = this.getVal(expre,vm)176         }177         178         // 将数据替换,更新到视图上179         this.updater.textUpdater(node,value)180     },181     //指令解析: v-html182     html(node, expre, vm) {183         const value = this.getVal(expre, vm)184         185         // 同上,先不看186         // 绑定观察者 数据=》视图187         new watcher(vm, expre (newVal) => {188             this.updater.htmlUpdater(node, newVal)189         })190         191         // 将数据替换,更新到视图上192         this.updater.htmlUpdater(node, newVal)193     },194     // 指令解析:v-mode195     model(node,expre, vm) {196         const value = this.getVal(expre, vm)197         198         // 同上,先不看199         // 绑定观察者 数据=》视图200         new watcher(vm, expre, (newVal) => {201             this.updater.modelUpdater(node, newVal)202         })203         204         // input框  视图=》数据=》视图205         node.addEventListener('input', (e) => {206             //设置新值 - 将input值赋值到v-model绑定的参数上207             this.setVal(expre, vm, e.traget.value)208         })209         // 将数据替换,更新到视图上210         this.updater.modelUpdater(node, value)211     },212     // 指令解析: v-on213     on(node, expre, vm, eventName) {214         // 或者指令绑定的事件函数215         let fn = vm.$option.methods && vm.$options.methods[expre]216         // 监听函数并调用217         node.addEventListener(eventName,fn.bind(vm),false)218     },219     // 指令解析: v-bind220     bind(node, expre, vm, attrName) {221         const value = this.getVal(expre,vm)222         this.updater.bindUpdate(node, attrName, value)223     }224     225 // updater对象,管理不同指令对应的更新方法226 updater: {227         // v-text指令对应更新方法228         textUpdater(node, value) {229             node.textContent = value230         },231         // v-html指令对应更新方法232         htmlUpdater(node, value) {233             node.innerHTML = value234         },235         // v-model指令对应更新方法236         modelUpdater(node,value) {237             node.value = value238         },239         // v-bind指令对应更新方法240         bindUpdate(node, attrName, value) {241             node[attrName] = value242         }243     },244 }

3、实现数据劫持监听

我们有了数据监听,还需要一个观察者可以触发更新视图。因为需要数据改变才能触发更新,所有还需要一个桥梁Dep收集所有观察者(观察者集合),连接Observer和Watcher。数据改变通知Dep,Dep通知相应的观察者进行视图更新。

Observer.js

 1 // 定义一个观察者 2 class watcher { 3     constructor(vm, expre, cb) { 4         this.vm = vm 5         this.expre = expre 6         this.cb =cb 7         // 把旧值保存起来 8         this.oldVal = this.getOldVal() 9     }10     // 获取旧值11     getOldVal() {12         // 将watcher放到targe值中13         Dep.target = this14         // 获取旧值15         const oldVal = compileUtil.getVal(this.expre, this.vm)16         // 将target值清空17         Dep.target = null18         return oldVal19     }20     // 更新函数21     update() {22         const newVal =  compileUtil.getVal(this.expre, this.vm)23         if(newVal !== this.oldVal) {24             this.cb(newVal)25         }26     }27 }28 ​29 ​30 // 定义一个观察者集合31 class Dep {32     constructor() {33         this.subs = []34     }35     // 收集观察者36     addSub(watcher) {37         this.subs.push(watcher)38     }39     //通知观察者去更新40     notify() {41         this.subs.forEach(w => w.update())42     }43 }44 ​45 ​46 ​47 // 定义一个Observer类通过gettr,setter实现数据的监听绑定48 class Observer {49     constructor(data) {50         this.observer(data)51     }52     53     // 定义函数解析data,实现数据劫持54     observer (data) {55         if(data && typeof data === 'object') {56             // 是对象遍历对象写入getter,setter方法57             Reflect.ownKeys(data).forEach(key => {58                 this.defineReactive(data, key, data[key]);59             })60         }61     }62     63     // 数据劫持方法64     defineReactive(obj,key, value) {65         // 递归遍历66         this.observer(data)67         // 实例化一个dep对象68         const dep = new Dep()69         // 通过ES5的API实现数据劫持70         Object.defineProperty(obj, key, {71             enumerable: true,72             configurable: false,73             get() {74                 // 当读当前值的时候,会触发。75                 // 订阅数据变化时,往Dep中添加观察者76                 Dep.target && dep.addSub(Dep.target)77                 return value78             },79             set: (newValue) => {80                 // 对新数据进行劫持监听81                 this.observer(newValue)82                 if(newValue !== value) {83                     value = newValue84                 }85                 // 告诉dep通知变化86                 dep.notify()87             }88         })89     }90     91 }
​​​

3、总结

其实复杂的地方有三点:

1、指令解析的各种操作有点复杂饶人,其中包含DOM的基本操作和一些ES中的API使用。但是你静下心去读去想,肯定是能理顺的。

2、数据劫持中Dep的理解,一是收集观察者的集合,二是连接Observer和watcher的桥梁。

3、观察者是什么时候进行绑定的?又是如何工作实现了数据驱动视图,视图驱动数据驱动视图的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值