响应式对象核心是利用了 Object.defineProperty 给对象的属性添加 getter 和 setter
Vue 会把 props、data 等变成响应式对象,在创建过程中,发现子属性也为对象则递归把该对象变成响应式
getter
依赖收集
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
}
当对数据进行访问的时候会触发 getter ,dep.depend() 实际上就是将 当前对象的 watch 收集起来添加到 this.subs 里面去。
依赖收集就是订阅数据变化的 watcher 的收集
依赖收集的目的是为了当这些响应式数据发送变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者去做相应的逻辑处理。
setter
派发更新
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
派发更新就是当数据发生改变后,通知所有订阅了这个数据变化的 watcher 执行 update
派发更新的过程中会把所有要执行 update 的 watcher 推入到队列中,在 nextTick 后执行 flush 。
nextTick
nextTick 是把要执行的任务推入到一个队列中,在下一个 tick 同步执行
数据改变后触发渲染 watcher 的 update ,但是 watchers 的 flush 是在 nextTick 后,所以重新渲染是异步的。
计算属性
Watch属性
- 计算属性的本质是 computed watcher
- 侦听属性的本质是 user watcher ,它还支持 deep、sync、immediate 等配置
计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
组件更新
组件更新的过程核心就是新旧 vnode diff,对新旧节点相同以及不同的情况分别做不同的处理。
- 新旧节点不同的更新流程是创建新节点 -> 更新占位符节点 -> 删除旧节点
- 新旧节点相同的更新流程是去获取它们的 children,根据不同情况做不同的更新逻辑。
响应式系统
设置响应式对象 - Observer
首先引入一个类 Observer ,这个类的目的是将数据变成响应式对象,利用 Object.defineProperty 对数据的 getter , setter 方法进行改写。在数据读取 getter 阶段进行依赖的收集,在数据的修改 setter 阶段,进行依赖的更新。因此,在数据初始化阶段,我们会利用 Observer 这个类将数据对象修改为响应式对象。
class MyVue {
initData(options) {
if(!options.data) return;
this.data = options.data;
// 将数据重置getter,setter方法
new Observer(options.data);
}
}
// Observer类的定义
class Observer {
constructor(data) {
// 实例化时执行walk方法对每个数据属性重写getter,setter方法
this.walk(data)
}
walk(obj) {
const keys = Object.keys(obj);
for(let i = 0;i< keys.length; i++) {
// Object.defineProperty的处理逻辑
defineReactive(obj, keys[i])
}
}
}
依赖本身 - Watcher
一个 Watcher 实例就是一个依赖,数据不管是在渲染模板时使用还是在用户计算时使用,都可以算做一个需要监听的依赖, watcher 中记录着这个依赖监听的状态,以及如何更新操作的方法。
在渲染数据到真实 DOM 时可以创建 watcher 。 $mount 会经历模板生成 render 函数和 render 函数渲染真实 DOM 的过程。
依赖管理 - Dep
watcher 如果理解为每个数据需要监听的依赖,那么 Dep 可以理解为对依赖的一种管理。数据可以在渲染中使用,也可以在计算属性中使用。相应的每个数据对应的 watcher 也有很多。而我们在更新数据时,如何通知到数据相关的每一个依赖,这就需要 Dep 进行通知管理了。并且浏览器同一时间只能更新一个 watcher ,所以也需要一个属性去记录当前更新的 watcher 。而 Dep 这个类只需要做两件事情,将依赖进行收集,派发依赖进行更新。
let uid = 0;
class Dep {
constructor() {
this.id = uid++;
this.subs = []
}
// 依赖收集
depend() {
if(Dep.target) {
// Dep.target是当前的watcher,将当前的依赖推到subs中
this.subs.push(Dep.target)
}
}
// 派发更新
notify() {
const subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
// 遍历dep中的依赖,对每个依赖执行更新操作
subs[i].update();
}
}
}
Dep.target = null;
依赖管理过程 - defineReactive
前面的 Observer 实例化最终会调用 defineReactive 重写 getter ,setter 方法。这个方法开始会实例化一个 Dep ,也就是创建一个数据的依赖管理。在重写的 getter 方法中会进行依赖的收集,也就是调用 dep.depend 的方法。在 setter 阶段,比较两个数不同后,会调用依赖的派发更新。即 dep.notify 。
const defineReactive = (obj, key) => {
const dep = new Dep();
const property = Object.getOwnPropertyDescriptor(obj);
let val = obj[key]
if(property && property.configurable === false) return;
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
// 做依赖的收集
if(Dep.target) {
dep.depend()
}
return val
},
set(nval) {
if(nval === val) return
// 派发更新
val = nval
dep.notify();
}
})
}
回过头来看 watcher ,实例化 watcher 时会将 Dep.target 设置为当前的 watcher ,执行完状态更新函数之后,再将 Dep.target 置空。这样在收集依赖时只要将 Dep.target 当前的 watcher push 到 Dep 的 subs 数组即可。而在派发更新阶段也只需要重新更新状态即可。
Vue2原理-Object.defineProperty
Vue3原理-Proxy、Reflect
参考文章: