小程序究竟有何用处_详解Vue中的MVVM原理和完成方

发布时间:2021-01-05 13:02 作者:jianzhan

摘要: 详细说明Vue中的MVVM基本原理和完成方式 本文关键详细介绍了Vue中的MVVM基本原理和完成方式,原文中解读十分细腻,协助大伙儿更强的了解和学习培训,很感兴趣的朋友就行了解下

详解Vue中的MVVM原理和实现方法       这篇文章主要介绍了Vue中的MVVM原理和实现方法,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下

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

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

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

一、思路整理

实现的流程图:

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

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

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

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

二、实现

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

1、实现html页面

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

html

 body 
 div id="app" 
 h2 {{person.name}} --- {{person.age}} /h2 
 h3 {{person.fav}} /h3 
 h3 {{person.a.b}} /h3 
 li 1 /li 
 li 2 /li 
 li 3 /li 
 /ul 
 h3 {{msg}} /h3 
 div v-text="msg" /div 
 div v-text="person.fav" /div 
 div v-html="htmlStr" /div 
 input type="text" v-model="msg" 
 button v-on:click="click111" 按钮on /button 
 button @click="click111" 按钮@ /button 
 /div 
 script src="./MVue.js" /script 
 script src="./Observer.js" /script 
 script 
 let vm = new MVue({
 el: '#app',
 data: {
 person: {
 name: '星哥',
 age: 18,
 fav: '姑娘',
 a: {
 b: '787878'
 msg: '学习MVVM实现原理',
 htmlStr: ' h4 大家学的怎么样 /h4 ',
 methods: {
 click111() {
 console.log(this)
 this.person.name = '学习MVVM'
 // this.$data.person.name = '学习MVVM'
 /script 
 /body 

2、实现解析器和观察者

MVue.js

// 先创建一个MVue类,它是一个入口
Class MVue {
 construction(options) {
 this.$el = options.el
 this.$data = options.data
 this.$options = options
 if(this.$el) {
 // 1.实现一个数据的观察者 --先看解析器,再看Obeserver
 new Observer(this.$data)
 // 2.实现一个指令解析器
 new Compile(this.$el,this)
// 定义一个Compile类解析元素节点和指令
class Compile {
 constructor(el,vm) {
 // 判断el是否是元素节点对象,不是就通过DOM获取
 this.el = this.isElementNode(el) el : document.querySelector(el)
 this.vm = vm
 // 1.获取文档碎片对象,放入内存中可以减少页面的回流和重绘
 const fragment = this.node2Fragment(this.el)
 // 2.编辑模板
 pile(fragment)
 // 3.追加子元素到根元素(还原页面)
 this.el.appendChild(fragment)
 // 将元素插入到文档碎片中
 node2Fragment(el) {
 const f = Fragment();
 let firstChild
 while(firstChild = el.firstChild) {
 // appendChild
 // 将已经存在的节点再次插入,那么原来位置的节点自动删除,并在新的位置重新插入。
 f.appendChild(firstChild)
 // 此处执行完,页面已经没有元素节点了
 return f
 // 解析模板
 compile(frafment) {
 // 1.获取子节点
 conts childNodes = fragment.childNodes;
 [...childNodes].forEach(child = {
 if(this.isElementNode(child)) {
 // 是元素节点
 // 编译元素节点
 pileElement(child)
 } else {
 // 文本节点
 // 编译文本节点
 pileText(child)
 // 嵌套子节点进行遍历解析
 if(child.childNodes child.childNodes.length) {
 pule(child)
 // 判断是元素节点还是属性节点
 isElementNode(node) {
 // nodeType属性返回 以数字值返回指定节点的节点类型。1-元素节点 2-属性节点
 return node.nodeType === 1
 // 编译元素节点
 compileElement(node) {
 // 获得元素属性集合
 const attributes = node.attributes
 [...attributes].forEach(attr = {
 const {name, value} = attr
 if(this.isDirective(name)) { // 判断属性是不是以v-开头的指令
 // 解析指令(v-mode v-text v-on:click 等...)
 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,val,this.vm, eventName)
 // 删除有指令的标签上的属性
 node.removeAttribute('@' + eventName)
 } else if(this.isBindName(name)) { //判断属性是不是以:开头的指令
 // 解析指令
 let [, attrName] = name.split(':')
 compileUtil['bind'](node,val,this.vm, attrName)
 // 删除有指令的标签上的属性
 node.removeAttribute(':' + attrName)
 // 编译文本节点
 compileText(node) {
 const content = node.textContent
 if(/\{\{(.+ )\}\}/.test(content)) {
 compileUtil['text'](node, content, this.vm)
 // 判断属性是不是指令
 isDirective(attrName) {
 return attrName.startsWith('v-')
 // 
 isEventName(attrName) {
 return attrName.startsWith('@')
 // 判断属性是不是以:开头的事件指令
 isBindName(attrName) {
 return attrName.startsWith(':')

return expre.replace(/\{\{(.+ )\}\}/g, (...arges) = { return this.getVal(arges[1], vm) // 设置新值 setVal(expre, vm, inputVal) { return expre.split('.').reduce((data, currentVal) = { return data[currentVal] = inputVal }, vm.$data) // 指令解析:v-test test(node, expre, vm) { let value; if(expre.indexOf('{{') !== -1) { // 正则匹配{{}}里的内容 value = expre.replace(/\{\{(.+ )\}\}/g, (...arges) = { // new watcher这里相关的先可以不看,等后面讲解写到观察者再回头看。这里是绑定观察者实现 的效果是通过改变数据会触发视图,即数据=》视图。 // 没有new watcher 不影响视图初始化(页面参数的替换渲染)。 // 订阅数据变化,绑定更新函数。 new watcher(vm, arges[1], () = { // 确保 {{person.name}}----{{person.fav}} 不会因为一个参数变化都被成新值 this.updater.textUpdater(node, this.getgetContentVal(expre,vm)) return this.getVal(arges[1],vm) } else { // 同上,先不看 // 数据=》视图 new watcher(vm, expre, (newVal) = { // 找不到{}说明是test指令,所以当前节点只有一个参数变化,直接用回调函数传入的新值 this.updater.textUpdater(node, newVal) value = this.getVal(expre,vm) // 将数据替换,更新到视图上 this.updater.textUpdater(node,value) //指令解析: v-html html(node, expre, vm) { const value = this.getVal(expre, vm) // 同上,先不看 // 绑定观察者 数据=》视图 new watcher(vm, expre (newVal) = { Updater(node, newVal) // 将数据替换,更新到视图上 Updater(node, newVal) // 指令解析:v-mode model(node,expre, vm) { const value = this.getVal(expre, vm) // 同上,先不看 // 绑定观察者 数据=》视图 new watcher(vm, expre, (newVal) = { this.updater.modelUpdater(node, newVal) // input框 视图=》数据=》视图 node.addEventListener('input', (e) = { //设置新值 - 将input值赋值到v-model绑定的参数上 this.setVal(expre, vm, e.traget.value) // 将数据替换,更新到视图上 this.updater.modelUpdater(node, value) // 指令解析: v-on on(node, expre, vm, eventName) { // 或者指令绑定的事件函数 let fn = vm.$option.methods vm.$options.methods[expre] // 监听函数并调用 node.addEventListener(eventName,fn.bind(vm),false) // 指令解析: v-bind bind(node, expre, vm, attrName) { const value = this.getVal(expre,vm) this.updater.bindUpdate(node, attrName, value) // updater对象,管理不同指令对应的更新方法 updater: { // v-text指令对应更新方法 textUpdater(node, value) { node.textContent = value // v-html指令对应更新方法 htmlUpdater(node, value) { node.innerHTML = value // v-model指令对应更新方法 modelUpdater(node,value) { node.value = value // v-bind指令对应更新方法 bindUpdate(node, attrName, value) { node[attrName] = value }

3、实现数据劫持监听

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

Observer.js

// 定义一个观察者
class watcher {
 constructor(vm, expre, cb) {
 this.vm = vm
 this.expre = expre
 this.cb =cb
 // 把旧值保存起来
 this.oldVal = this.getOldVal()
 // 获取旧值
 getOldVal() {
 // 将watcher放到targe值中
 Dep.target = this
 // 获取旧值
 const oldVal = compileUtil.getVal(this.expre, this.vm)
 // 将target值清空
 Dep.target = null
 return oldVal
 // 更新函数
 update() {
 const newVal = compileUtil.getVal(this.expre, this.vm)
 if(newVal !== this.oldVal) {
 this.cb(newVal)

其实复杂的地方有三点:

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

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

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

在gitHub上有上述,欢迎clone打桩尝试,还请不要吝啬一个小星星哟!

以上就是详解Vue中的MVVM原理和实现方法的详细内容,更多关于Vue中的MVVM的资料请关注凡科其它

  • 互联网定制开发广范围的

    谢邀。 到今日才行我在事精益早已五年了,還是个新兵,缺憾的就是我还并不是精益生产管理主管。这一岗位做久了也会想未来的事儿:晋升加薪…担任CEO这类的,可是相反一想,到底

  • 云建站(自助建站,智能

    呐,我以前的上级代理商企业收购了我以前以前的上级代理商企业,我以前以前的上级代理商企业又收购了再之前的上级代理商企业。这一再之前的上级代理商企业就是一个做智能化化

  • 对中文支持较好的自助建

    Squarespace 和 Strikingly 的字体样式目录上都沒有汉语字体样式可选择,针对对汉语排版设计有要求的平面图设计方案师怎样不在设计方案 coding 的状况下构建出排版设计出色的网站?strin

  • 面对保险公司的电销轰炸

    电话轿车商业保险,逐一电話营销推广公司问,之后问有什么优惠。选个大些的公司保险投保,理赔会方便快捷点,价格一定要最划得来的。倘若不肯被搔扰,我都在告之你一招,毫无

  • 想做一个网站,用网页自

    很多人都想拥有一个所属于本身的网站,却又苦于模糊不清白程序撰写,模糊不清白设计方案计划方案,无法下手。那么,在没有技术性性的情况下,可以建一个本身的网站吗?回应是