Vue2响应式原理

288 阅读2分钟

data属性的响应式

遍历data对象,首先给data的每个属性new一个Dep;再通过 Object.defineProperty() 方法给data的每一个属性添加 get 和 set 方法

get: 依赖/观察者(Watcher) 收集 ; 使用data属性,会执行get()方法

set: 通知 依赖/观察者(Watcher) 执行; 修改data属性的值,会执行set()方法

data属性的依赖收集器(Dep)

data的每一个属性对应一个Dep

一个Dep是一个Watcher数组

有三种类型的Watcher

  • render watcher 每个属性只对应一个
  • computed watcher 每个属性可对应多个
  • watch watcher 每个属性可对应多个

每个watcher中有一个回调函数

当属性值更改的时候,会通知Dep,Dep会通知这些Wacther执行回调函数。

<body>
    <div id="app">
        {{ name }}
        {{info}}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <script>
       const app = new Vue({
            el: '#app',
            data() {
                return {
                    name: '林三心'
                }
            },
            computed: {
                info () {
                    return this.name
                }
            },
            watch: {
                name(newVal) {
                    console.log(newVal)
                }
            },
            methods:{
                init: function(){
                    this.name = '123'
                }
            },
            mounted(){
                console.log(this)
                this.init()
            }
        })
       app.mount()
    </script>
</body>

上面例子中的name对应的Dep如下:

Dep下有3个Watcher;

每个Watcher里面还有Dep

image.png

image.png

Watcher为何要反过来收集Dep?

dep是name的管家,他的职责是:name更新时,dep会带着主人的命令去通知subs里的Watcher去做该做的事,那么,dep收集Watcher很合理。那为什么Watcher也需要反过来收集dep呢?这是因为computed属性里面的变量没有自己的dep。

如何html里不依赖name这个变量,那么无论name再怎么变,他都不会主动去刷新视图,因为html没引用他(说专业点就是:name的dep里没有渲染Watcher),注意,这里说的是不会主动,但并不代表他不会被动去更新。什么情况下他会被动去更新呢?那就是computed有依赖他的属性变量。

computed属性里面的变量info依赖于name, 但是info是没有自己的dep的(因为他是computed属性变量),而name是有dep的。如何html里面只有info的引用没有name的引用,那么name一改变,按理说虽然info跟着变了,但是html不会重新渲染,因为name虽然有dep,有更新视图的能力,但是奈何人家html不引用他啊!info想要自己去更新视图,但是他却没有这个能力啊,毕竟他没有dep这个管家!这个时候name的computed Watcher里面的dep就派上用场了,可以借助这些dep去更新视图,达到更新html里的info的效果。

如何将render Watcher添加到data属性的Dep中

  1. mountComponent时, 首先new 一个render Watcher;
  2. 将该Watcher实例赋值给 Dep.target
  3. 执行该Watcher的回调函数,即执行 vm._update(vm._render(), hydrating);
  4. 执行render函数
  5. 执行render函数时,遇到data的属性,则调用data属性的 get 方法
  6. 在get方法中,会判断Dep.target(即render Watcher 实例)是否存在
  7. 存在,则将该Watcher实例添加到当前属性对应的Dep中。添加时会去重。