原理
- Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的 setter、getter【Vue3中使用Proxy】,在数据变动时发布消息给订阅者,触发相应的监听回调
实现步骤
- 将需要observe的数据对象都加上 setter 和 getter, 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
- compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
- ① 在自身实例化时往属性订阅器(dep)里面添加自己
- ② 自身必须有一个 update()方法
- ③ 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退
- MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input)-> 数据 model 变更的双向绑定效果
- 从图可看出,双向数据绑定可总结为三个部分
- Observer 用来监听拦截data的属性为监察者
- Dep用来添加订阅者,为订阅器
- Watcher 就是订阅者
- 最后:监察者通过 Dep 向 Watcher发布更新消息
模拟实现
function observer(obj,vm) {
//obj app.data
//vm app
Object.entries(obj).map(([key,val]) => {
//key num val 1
//以app.data里的属性作为原始数据 给app对象本身挂载上这些属性(修饰后的)
//app.data.num => num(set,get)修饰 => app.num
defineReactive(vm,key,val)
})
}
//监听数据改变 defineProperty proxy
function defineReactive(obj, key, val) {
let dep = new Dep()
Object.defineProperty(obj,key,{
get() {
//obj的key属性的值被获取的时候,触发
console.log(`读取数据:${val}`)
if(Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set(newVal) {
//obj的key属性的值被修改的时候,触发
if(val === newVal) {
return
}
val = newVal
console.log(`更新数据:${val}`)
//发布通知 通知watcher update
dep.notify()
}
})
}
//创建一个新的空白的文档片段,并将处理后的dom加入该文档片段
function nodeToFragment(node,vm) {
let flag = document.createDocumentFragment()
let child
// 循环取出node中的第一个子节点,放入compile处理,后移动到fragement
while (child = node.firstChild) {
compile(child,vm)
flag.append(child)
}
return flag
}
/**
* 1. 解析原始dom,获取内容的差值标识 {{model}}
* 2. 提取的差值标识 数据与model层进行绑定
*/
// 解析原始view,提取其中的model差值
function compile(node,vm) {
let reg = /\{\{(.*)\}\}/
if(node.nodeType === 1) {
// node 为Element的节点
let attr = node.attributes
for(let i = 0,len = attr.length; i<len; i++) {
if(attr[i].nodeName === 'v-model') {
let name = attr[i].nodeValue
node.addEventListener('input',(e) => {
vm[name] = e.target.value
})
node.value = vm[name] //model层数据对象
node.removeAttribute('v-model')
new Watcher(vm,node,name)
}
}
}
if(node.nodeType === 3) {
// node为text类型节点
if(reg.test(node.nodeValue)) {
let name = reg.exec(node.nodeValue)
node.nodeValue = vm[name[1]] //model层数据对象
new Watcher(vm,node,name[1])
}
}
}
// 发布订阅设计模式
class Dep {
constructor() {
//观察者/订阅者列表
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.map(sub => {
//通知所有订阅者
sub.update()
})
}
}
//订阅者
class Watcher {
constructor(vm,node,name) {
this.name = name
Dep.target = this
this.node = node
this.vm = vm
this.init()
}
init() {
this.update()
Dep.target = null
}
update() {
//更新view展示 获取数据,修改dom
this.get()
this.node.value = this.value
this.node.nodeValue = this.value
}
get() {
this.value = this.vm[this.name]
}
}
class Vue {
constructor({el,data}) {
this.data = data
// this.num = this.data.num
// defineReactive(this,'num')
observer(this.data,this)
let ele = document.querySelector(el)
let dom = nodeToFragment(ele,this)
ele.appendChild(dom)
}
}
const app = new Vue({
el: '#app',
data: {
num: 1
}
})
-------------------------------------------------------------------------------2024.5.22每日一题