MVVM的理解
MVVM : 即 M (model-数据模型), V(view视图), VM(view-model同步M与V的对象),本质上,数据模型与视图之间是不能直接更新的,都需要通过VM来通知双方;比如,视图更新,由VM通知数据模型, 数据模型变化时,VM检测到,再通知视图层更新。这个过程是全自动的,使用者无需关心这其中的操作。
MVVM的原理
MVVM原理有多种,这里使用的是Object.defindeProperty(), 通过其对数据源进行数据绑定,并将设置了数据绑定的数据 通过'dom绑定' 的方式呈现在视图层。视图层的数据发生变化时,使用事件绑定来监听到变化,并通知dom。
实现步骤:
· 初始化数据
· 为数据进行数据绑定
· 获取dom(input)元素并且添加事件绑定
· 将数据显示到dom上
MVVM的实现
dom准备
<body> 标签内:
这里有几个输入数据的input,后面的dom事件监听也会绑定到上面
这里有几个p标签,用来回显数据。
在p内又嵌套了一个同样的标签,是为了模拟多种情况下获取dom元素的过程(递归)
<div id="app">
<div>
<input type="text" v-model="name" placeholder="姓名" />
<input type="text" v-model="age" placeholder="年龄" />
<input type="text" v-model="email" placeholder="邮箱" />
<input type="text" v-model="tel" placeholder="电话号码" />
</div>
<div>
<div>
<p>姓名:<span>{{ name }}</span></p>
<p>年龄:<span>{{ age }}</span></p>
<p>邮箱:<span>{{ email }}</span></p>
<p>
<p>电话:<span>{{ tel }}</span></p>
</p>
</div>
</div>
<button id="btn">改变名字</button>
</div>
这里实例化自定义的MVVM类,并且传入一些初始化数据,当然,此时dom上并没有效果。
<script src="mvvm2.js"></script>
<script>
const app = new MVVM('#app', {
name: 'GUJI',
age: '11',
email: '',
tel: ''
})
</script>
开始 JS
我在其中关键地方都加了注释,并且代码思路都是由上而下的,阅读起来非常轻松。
class MVVM {
constructor(el, data) {
this._data = data
this.el = document.querySelector(el)
// 存储了绑定数据的dom
this.domPool = {}
this.init()
}
init() {
this.initData()
this.initDom()
}
initDom() {
this.initInput(this.el)
this.bindDom(this.el)
}
// 初始化数据
initData() {
this.data = {}
const _this = this
for (const key in this._data) {
Object.defineProperty(this.data, key, {
get() {
return _this._data[key]
},
set(newValue) {
// 修改dom中的值
_this.domPool[key].innerHTML = newValue
_this._data[key] = newValue
},
})
}
}
// 得到input中的数据
initInput(el) {
const _allInputs = el.querySelectorAll('input')
// 循环这些input
_allInputs.forEach((input) => {
const _vModel = input.getAttribute('v-model')
if (_vModel) {
//绑定数据
input.addEventListener(
'keyup',
this.handleInput.bind(this, _vModel, input),
false
)
}
})
}
// 设置数据
handleInput(key, input) {
const _value = input.value
this.data[key] = _value
}
// 将数据绑定到dom上
bindDom(el) {
const childNodes = el.childNodes
childNodes.forEach((item) => {
// 总是得到空的,原因:没有解析到最后一层,所以要在循环后面进行递归调用
const _value = item.nodeValue
if (item.nodeType === 3 && _value.trim().length > 0) {
// 匹配到v-model的键
let isVaile = /\{\{(.+?)\}\}/.test(_value)
if (isVaile) {
// 去除大括号 以及多余空格
// 到这里就得到了这个dom的v-model中的值了
const key = _value.match(/\{\{(.+?)\}\}/)[1].trim()
// 因为item是文本节点,所以插入值的地方应该是它的父节点
// 先将dom订阅到domPool中
this.domPool[key] = item.parentNode
// 绑定数据
item.parentNode.innerText = this.data[key] || ''
}
}
// 如果这个不是最后一层(有子节点)的话,就继续解析
item.childNodes && this.bindDom(item)
})
}
// 当需要在js中手动添加数据时,可以调用这个方法
setData(key, data) {
this.data[key] = data
}
}
使用Object.defindeProperty()缺点:
使用Object.defindeProperty()的缺点是,当你添加或者删除该属性时,Object.defindeProperty()无法检测到,这也是vue2.0的缺点,当然它里面也提供了相应的解决方案。
可是这不能更方便开发者使用,所以vue3.0后就开始使用proxy进行数据代理。使用proxy来实现mvvm功能更加简单,有兴趣可以试一试