1、
innerhtml = data.xxx
dom的渲染是 data的订阅者, dom的渲染由compile解析出渲染函数
watcher统一管理调用,该属性的渲染函数 watcher.update()
dep通知watcher去调用,dep.notify()
2、watch函数
watch(
() => data.msg, // 渲染函数
(old, newVal) => { // 当数据源发生改变时的回调
console.log(‘old: ‘, old)
console.log(‘newVal: ‘, newVal)
}
);
等同于 渲染函数,不同的是,渲染函数是watchcallback,
同样借用data.gettter的那套
1、watch借用data的getter使得watch本身变为data的订阅者
===> watch的key会在Watcher里进行值的读取,也就是立马执行get获取value
3、computed函数
const computedNum = computed(() => data.number + 3);
当data变化的时候,computed也会变化
即computed是依赖data变化的 渲染函数
computed 还可以 依赖另一个computed,即computed属性本身 也可以有watcher管理
computed计算属性监听data数据变更主要经历以下几个过程:
watch
watch的实现原理
watch的分类:
- deep watch(深层次监听)
- user watch(用户监听)
- computed watcher(计算属性)
- sync watcher(同步监听)
watch实现过程:
- watch的初始化在data初始化之后(此时的data已经通过
Object.defineProperty
的设置成响应式)
走data.xxx那套触发data的getter从而依赖收集
- watch的key会在Watcher里进行值的读取,也就是立马执行get获取value(从而实现data对应的key执行getter实现对于watch的依赖收集),此时如果有
immediate
属性那么立马执行watch对应的回调函数 - 当data对应的key发生变化时,触发user watch实现watch回调函数的执行
watch(
() => data.msg, // 渲染函数
(old, newVal) => { // 当数据源发生改变时的回调
console.log('old: ', old)
console.log('newVal: ', newVal)
}
);
# watch
import Watcher from './watcher'
export default function watch(getter, callback) {
new Watcher(getter, { watch: true, callback })
}
# watch
update() {
if (this.computed) {
this.get();
this.dep.notify();
}
else if (this.watch) {
const oldValue = this.value;
this.get();
this.callback(oldValue, this.value);
} else {
this.get();
}
}
computed
computed计算值为什么还可以依赖另外一个computed计算值?
const computedNum = computed(() => data.number + 3);
// step2: 代表update的渲染函数,交给watcher管理,渲染函数依赖了data,所以是data的订阅者
// watcher负责将该订阅者 添加到 deps 信息调度中心, 所以deps在每次变化 都能通知到 该渲染函数
new Watcher(() => {
console.log(computedNum);
});
computed: 接受渲染属性:新属性是在data基础上改造的,需要传入computed函数包裹一下
computed包裹了改造函数,实现的效果就是:依赖的属性data改变了,computed也会改变
watcher传入 读取了computed属性的渲染函数
现在 改变computedNum 每次都会触发watcher里的渲染函数
思考一下,你会怎么设计?
在computed里 肯定 和dep进行了某种关联,才可以让data的属性改变了 computed也接收到通知
1、computed属性里有自己的一个对应的watcher实例
watcher本质:存储了一个需要在特定时机触发的函数
2、computedwatcher与data的dep的联系:
当wathcer的渲染函数读取到computeddata时,会将Dep.taget = computedwatcher
3、computedwatcher:
寄宿于普通watcher保持普调watcher的大致功能,但在普通watcher的基础上:
1、拥有自己的dep:所以可以收集其他的watcher作为自己的依赖
2、惰性求值,constructor初始化时不先运行getter
=> 先把正在运行的渲染函数的watcher收集到computedWatcher内部的dep里
所以运行流程:
========
突破点: 把代码回归到最原始的写法,打断点看懂的
有两个地方new watcher了,区分下
直接new watcher渲染函数是 输出数据到dom: 页面watcher
computed数据里监听器使用的 工具watcher,负责接收data的变化: 计算watcher
computed.drawio
首次渲染
问题:
1、本来很疑问 为什么计算watcher要收集页面watcher作为订阅者的
==》 setter 触发链
更新
更新路径:data.number = 5
-> computedWatcher
-> 渲染watcher
-> 更新视图
computedWatcher.update: this.get -> this.dep.nodify() // 调用渲染watcher更新
computed运行原理
- computed的属性是动态挂载到vm实例上的,和普通的响应式数据在data里声明不同
- 设置computed的getter,如果执行了computed对应的函数,由于函数会读取data属性值,因此又会触发data属性值的getter函数,在这个执行过程中就可以处理computed相对于data的依赖收集关系了
- 首次计算computed的值时,会执行vm.computed属性对应的getter函数(用户指定的computed函数,如果没有设置getter,那么将当前指定的函数赋值computed属性的getter),进行上述的依赖收集
如果computed的属性值又依赖了其他computed计算属性值,那么会将当前target暂存到栈中,先进行其他computed计算属性值的依赖收集,等其他计算属性依赖收集完成后,在从栈中pop出来,继续进行当前computed的依赖收集
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
复制代码
由于
this.firstName
和this.lastName
(上面是Vue官方示例)都是响应式变量,因此会触发它们的 getter,根据我们之前的分析,它们会把自身持有的 dep 添加到当前正在计算的 watcher 中,这个时候Dep.target
就是这个 computed watcher,具体步骤如下:data 属性初始化 getter setter
- computed 计算属性初始化,提供的函数将用作属性
vm.fullName
的 getter - 当首次获取
fullName
计算属性的值时,Dep 开始依赖收集 - 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定
firstName
和lastName
为fullName
的依赖,并建立依赖关系 - 当
firstName
或lastName
发生变化时,根据依赖关系,触发fullName
的重新计算 - 如果计算值没有发生变化,不会触发视图更新
通过以上的分析,我们知道计算属性本质上就是一个 computed watcher,也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程,其实这是最新的计算属性的实现,之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化才会触发渲染 watcher 重新渲染,本质上是一种优化。