内容简介:MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。要实现一个mvvm的库,我们首先要理解清楚其实现的整体思路。先看看下图的流程:
一. 什么是mvvm
MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。
要实现一个mvvm的库,我们首先要理解清楚其实现的整体思路。先看看下图的流程:
1.实现compile,进行模板的编译,包括编译元素(指令)、编译文本等,达到初始化视图的目的,并且还需要绑定好更新函数;
2.实现Observe,监听所有的数据,并对变化数据发布通知;
3.实现watcher,作为一个中枢,接收到observe发来的通知,并执行compile中相应的更新方法。
4.结合上述方法,向外暴露mvvm方法。
二. 实现方法
首先编辑一个html文件,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MVVM原理及其实现</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message">
<div>{{message}}</div>
<ul><li></li></ul>
</div>
<script src="watcher.js"></script>
<script src="observe.js"></script>
<script src="compile.js"></script>
<script src="mvvm.js"></script>
<script>
let vm = new MVVM({
el: '#app',
data: {
message: 'hello world',
a: {
b: 'bbb'
}
}
})
</script>
</body>
</html>
1.实现一个mvvm类(入口)
新建一个mvvm.js,将参数通过options传入mvvm中,并取出el和data绑定到mvvm的私有变量$el和$data中。
// mvvm.js
class MVVM {
constructor(options) {
this.$el = options.el
this.$data = options.data
}
}
2.实现compile(编译模板)
新建一个compile.js文件,在mvvm.js中调用compile。compile.js接收mvvm中传过来的el和vm实例。
// mvvm.js
class MVVM {
constructor(options) {
this.$el = options.el
this.$data = options.data
// 如果有要编译的模板 =>编译
if(this.$el) {
// 将文本+元素模板进行编译
new Compile(this.$el, this)
}
}
}
(1)初始化传值
// compile.js
export default class Compile {
constructor(el, vm) {
// 判断是否是元素节点,是=》取该元素 否=》取文本
this.el = this.isElementNode(el) ? el:document.querySelector(el)
this.vm = vm
},
// 判断是否是元素节点
isElementNode(node) {
return node.nodeType === 1
}
}
(2)先把真实DOM移入到内存中 fragment,因为fragment在内存中,操作比较快
// compile.js
class Compile {
constructor(el, vm) {
// 判断是否是元素节点,是=》取该元素 否=》取文本
this.el = this.isElementNode(el) ? el:document.querySelector(el)
this.vm = vm
// 如果这个元素能获取到 我们才开始编译
if(this.el) {
// 1. 先把真实DOM移入到内存中 fragment
let fragment = this.node2fragment(this.el)
}
},
// 判断是否是元素节点
isElementNode(node) {
return node.nodeType === 1
}
// 将el中的内容全部放到内存中
node2fragment(el) {
let fragment = document.createDocumentFragment()
let firstChild
// 遍历取出firstChild,直到firstChild为空
while (firstChild = el.firstChild) {
fragment.appendChild(firstChild)
}
return fragment // 内存中的节点
}
}
(3)编译 =》 在fragment中提取想要的元素节点 v-model 和文本节点
// compile.js
class Compile {
constructor(el, vm) {
// 判断是否是元素节点,是=》取该元素 否=》取文本
this.el = this.isElementNode(el) ? el:document.querySelector(el)
this.vm = vm
// 如果这个元素能获取到 我们才开始编译
if(this.el) {
// 1. 先把真实DOM移入到内存中 fragment
let fragment = this.node2fragment(this.el)
// 2. 编译 =》 在fragment中提取想要的元素节点 v-model 和文本节点
this.compile(fragment)
// 3. 把编译好的fragment在放回到页面中
this.el.appendChild(fragment)
}
}
// 判断是否是元素节点
isElementNode(node) {
return node.nodeType === 1
}
// 是不是指令
isDirective(name) {
return name.includes('v-')
}
// 将el中的内容全部放到内存中
node2fragment(el) {
let fragment = document.createDocumentFragment()
let firstChild
// 遍历取出firstChild,直到firstChild为空
while (firstChild = el.firstChild) {
fragment.appendChild(firstChild)
}
return fragment // 内存中的节点
}
//编译 =》 提取想要的元素节点 v-model 和文本节点
compile(fragment) {
// 需要递归
let childNodes = fragment.childNodes
Array.from(childNodes).forEach(node => {
// 是元素节点 直接调用文本编译方法 还需要深入递归检查
if(this.isElementNode(node)) {
this.compileElement(node)
// 递归深入查找子节点
this.compile(node)
// 是文本节点 直接调用文本编译方法
} else {
this.compileText(node)
}
})
}
// 编译元素方法
compileElement(node) {
let attrs = node.attributes
Array.from(attrs).forEach(attr => {
let attrName = attr.name
// 判断属性名是否包含 v-指令
if(this.isDirective(attrName)) {
// 取到v-指令属性中的值(这个就是对应data中的key)
let expr = attr.value
// 获取指令类型
let [,type] = attrName.split('-')
// node vm.$data expr
compileUtil[type](node, this.vm, expr)
}
})
}
// 这里需要编译文本
compileText(node) {
//取文本节点中的文本
let expr = node.textContent
let reg = /\{\{([^}]+)\}\}/g
if(reg.test(expr)) {
// node this.vm.$data text
compileUtil['text'](node, this.vm, expr)
}
}
}
// 解析不同指令或者文本编译集合
const compileUtil = {
text(node, vm, expr) { // 文本
let updater = this.updater['textUpdate']
updater && updater(node, getTextValue(vm, expr))
},
model(node, vm, expr){ // 输入框
let updater = this.updater['modelUpdate']
updater && updater(node, getValue(vm, expr))
},
// 更新函数
updater: {
// 文本赋值
textUpdate(node, value) {
node.textContent = value
},
// 输入框value赋值
modelUpdate(node, value) {
node.value = value
}
}
}
// 辅助 工具 函数
// 绑定key上对应的值,从vm.$data中取到
const getValue = (vm, expr) => {
expr = expr.split('.') // [message, a, b, c]
return expr.reduce((prev, next) => {
return prev[next]
}, vm.$data)
}
// 获取文本编译后的对应的数据
const getTextValue = (vm, expr) => {
return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
return getValue(vm, arguments[1])
})
}
(3) 将编译后的fragment放回到dom中
let fragment = this.node2fragment(this.el) this.compile(fragment) // 3. 把编译好的fragment在放回到页面中 this.el.appendChild(fragment)
进行到这一步,页面上初始化应该渲染完成了。如下图:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Docker实现原理之 - OverlayFS实现原理
- 微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理
- 带你了解vue计算属性的实现原理以及vuex的实现原理
- Docker原理之 - CGroup实现原理
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTTP Essentials
Stephen A. Thomas、Stephen Thomas / Wiley / 2001-03-08 / USD 34.99
The first complete reference guide to the essential Web protocol As applications and services converge and Web technologies not only assume HTTP but require developers to manipulate it, it is be......一起来看看 《HTTP Essentials》 这本书的介绍吧!