Vue 最独特的特性之一,是其非侵入性的响应式系统。比如我们修改了数据,那么依赖这些数据的视图都会进行更新,大大提高了我们的"搬砖"效率,回想一下初学 JS 的时候海量的 Dom操作.......,Vue 通过数据驱动视图,极大的将我们从繁琐的DOM操作中解放出来。
如下图,我们改变了 msg 的值,视图也响应式的进行了更新
我们先看 vue 官网的图,其实不太清晰,我初看的时候也是一脸懵逼的.:
再看下面这张图,响应式原理涵盖在里面了(图片来源于网络):
梳理一下流程:
对于 Observer、Dep 和 Watcher 这三大金刚 ,我初学的时候也是傻傻的分不清楚很懵,我的理解是:
Dep(dependence) 即依赖收集器,收集 Watcher 即观察者。
Watcher 即观察者,观察数据,数据变化时更新对应的视图(dom)。
Observer 即劫持者,通过 Object.defineProperty() 给数据设置 get 和 set 方法:
通过上面分析,可知每一个数据有一个依赖收集器 Dep,Dep 里面存放用到该数据的 Watcher,如下图所示(图片来源于网络):
1. Dep
我们先实现 Dep,Dep 我们可以用数组来模拟,它应该有两个方法:
2. Watcher
Watcher 实现如下,其中 cb 是更新视图的方法,关键点在于 oldVal,它有两个用处:
Dep.target = this
的用处是相当于设置了一个全局变量 让 Dep 能收集到 watcher 自己 ,后面 Dep.target = null
用处是销毁全局变量
3. Observer
Observer 实现如下,通过 Object.defineProperty 拦截数据的读写操作:
4. Compile
到这里我们已经实现了 Observer、Dep 和 Watcher,实现了数据的响应式追踪,可是还有一个点没打通,那就是 依赖收集 ,那么依赖什么时候收集呢?换言之我们怎么知道哪些数据依赖了哪些视图呢?
在 Vue 解析模板的时候,实际上我们已经知道了哪些 Dom 依赖了哪些数据,所以是在 compile 的时候完成了模板解析并完成了依赖收集。
Compile 实现如下,省略大部分 dom 操作相关代码,可以用 DocumentFragment 文档碎片提升性能,逻辑比较简单,我们在 dom 解析数据的时候生成了对应的 watcher,并完成了依赖收集:
# 编译类,输出真实Dom
class Compile {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
# 获取文档对象
const fragment = this.nodeFragment(this.el);
# 编译
this.compile(fragment);
# 挂载回app
this.el.appendChild(fragment);
}
# 是否元素节点
isElementNode(node) {
return node.nodeType === 1;
}
# 获取文档碎片
nodeFragment(el) {
# do something
}
compile(fragment) {
const childNodes = fragment.childNodes;
[...childNodes].forEach(node => {
if (this.isElementNode(node)) {
# 元素节点
# do something
} else {
# 文本节点
# do something
}
})
}
}
# 根据不同指令 执行不同的编译操作
const compileUtils = {
# v-text
text(node, expr, vm) {
const value = vm.$data[expr];
# 创建观察者 完成依赖收集
new Watcher(vm, expr, newVal => {
node.textContent = value;
});
node.textContent = value;
},
};
至此一个响应式的系统就已经完了
上面我们实现了响应式的系统,但只是单向的,即数据驱动视图,什么是双向数据绑定呢?如下图:
我们常见的 v-model, 就是双向数据绑定,其实它是一个语法糖:
<input v-model="msg" />
等价于 =>
<input :value="msg" @input="msg = $event.target.value" />
双向数据绑定即:
比如最简单的 input,我们只需要监听 input 事件,文本发生变化时更新数据,触发数据的 set 方法,通知所有的 watcher 更新视图
我们在模板编译的时候, 给 dom 元素绑定相应的事件 ,如 input 标签绑定 input 事件并指定更新数据的回调函数:
const compileUtils = {
# v-model
model(node, expr, vm) {
const value = vm.$data[expr];
# 创建观察者 完成依赖收集
new Watcher(vm, expr, newVal => {
node.value = value;
});
node.addEventListener('input', (e) => {
# 更新数据,触发数据的 set 方法
vm.$data[expr] = newVal;
});
node.value = value;
},
};
至此大功告成
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。