mvvm只关心数据的变化,无需直接操作dom节点,以下是一个简易的双向绑定原理的例子,可复制粘贴运行测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="test">{{name}}</div>
<script>
//观察函数,递归观察data的所有属性,数据变化时利用set和get订阅和发布消息
function observe(obj) {
// 判断类型
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
// 如果这个属性实例化了一个监听对象Watcher,将 Watcher 添加到订阅
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
// 执行 watcher 的 update 方法
dp.notify()
}
})
}
//我的理解相当于一个eventBus,可实例为一个订阅发布对象,用于订阅和发布消息
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
// sub 是 Watcher 实例
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null
function update(value) {
// document.querySelector('span').innerText = value
console.log('span', document.getElementById('test'))
document.getElementById('test').innerHTML = value
}
// 监听类,用于监听data数据
class Watcher {
constructor(obj, key, cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
Dep.target = this
this.cb = cb//更新dom的函数
this.obj = obj//监听的data数据
this.key = key//监听的data数据中的某一属性的key
this.value = obj[key]//监听的data数据中的某一属性的值
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 调用 update 方法更新 Dom
this.cb(this.value)
}
}
var data = {name: 'yck'}
observe(data)
// 测试实例化一个监听类,监听某一个属性变化,若属性变化则更新模板显示
new Watcher(data, 'name', update)
// 手动触发属性值变化
data.name = 'yyy'
</script>
<style>
</style>
</body>
</html>