image.png

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数据变更主要经历以下几个过程:

image.png

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回调函数的执行
  1. watch(
  2. () => data.msg, // 渲染函数
  3. (old, newVal) => { // 当数据源发生改变时的回调
  4. console.log('old: ', old)
  5. console.log('newVal: ', newVal)
  6. }
  7. );
  8. # watch
  9. import Watcher from './watcher'
  10. export default function watch(getter, callback) {
  11. new Watcher(getter, { watch: true, callback })
  12. }
  13. # watch
  14. update() {
  15. if (this.computed) {
  16. this.get();
  17. this.dep.notify();
  18. }
  19. else if (this.watch) {
  20. const oldValue = this.value;
  21. this.get();
  22. this.callback(oldValue, this.value);
  23. } else {
  24. this.get();
  25. }
  26. }

computed

computed计算值为什么还可以依赖另外一个computed计算值?

  1. const computedNum = computed(() => data.number + 3);
  2. // step2: 代表update的渲染函数,交给watcher管理,渲染函数依赖了data,所以是data的订阅者
  3. // watcher负责将该订阅者 添加到 deps 信息调度中心, 所以deps在每次变化 都能通知到 该渲染函数
  4. new Watcher(() => {
  5. console.log(computedNum);
  6. });

computed: 接受渲染属性:新属性是在data基础上改造的,需要传入computed函数包裹一下
computed包裹了改造函数,实现的效果就是:依赖的属性data改变了,computed也会改变

watcher传入 读取了computed属性的渲染函数
现在 改变computedNum 每次都会触发watcher里的渲染函数

思考一下,你会怎么设计?
在computed里 肯定 和dep进行了某种关联,才可以让data的属性改变了 computed也接收到通知

image.png

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

首次渲染

computed.png

问题:
1、本来很疑问 为什么计算watcher要收集页面watcher作为订阅者的
==》 setter 触发链

更新

image.png
更新路径:
data.number = 5 -> computedWatcher -> 渲染watcher -> 更新视图
computedWatcher.update: this.get -> this.dep.nodify() // 调用渲染watcher更新
image.png

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的依赖收集

    1. var vm = new Vue({
    2. el: '#demo',
    3. data: {
    4. firstName: 'Foo',
    5. lastName: 'Bar'
    6. },
    7. computed: {
    8. fullName: function () {
    9. return this.firstName + ' ' + this.lastName
    10. }
    11. }
    12. })
    13. 复制代码

    由于 this.firstNamethis.lastName (上面是Vue官方示例)都是响应式变量,因此会触发它们的 getter,根据我们之前的分析,它们会把自身持有的 dep 添加到当前正在计算的 watcher 中,这个时候Dep.target就是这个 computed watcher,具体步骤如下:

  • data 属性初始化 getter setter

  • computed 计算属性初始化,提供的函数将用作属性 vm.fullName 的 getter
  • 当首次获取 fullName 计算属性的值时,Dep 开始依赖收集
  • 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定firstNamelastNamefullName 的依赖,并建立依赖关系
  • firstNamelastName 发生变化时,根据依赖关系,触发 fullName 的重新计算
  • 如果计算值没有发生变化,不会触发视图更新

通过以上的分析,我们知道计算属性本质上就是一个 computed watcher,也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程,其实这是最新的计算属性的实现,之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化才会触发渲染 watcher 重新渲染,本质上是一种优化。