Vue 源码初探(四)依赖(对象)收集的过程

372 阅读2分钟

思维导图

Vue源码初探.png

前言

上一篇文章里面我们已经做完了单个根组件数据到页面的渲染过程。Vue 源码初探(三)单组件挂载(渲染)

现在我们来做一件事情,更改属性之后手动调用_update()函数让页面触发更新。我们发现页面也进行了更新。

<body>
  <div id="app">
    <li>{{name}}</li>
    <li>{{age}}</li>
  </div>
  <script src="./vue.js"></script>
  <!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script> -->
  <!-- <script src="./test.js"></script> -->
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        name: '小明',
        age: 20
      }
    })
  
    vm.name = '天明'

    vm._update(vm._render())
  </script>
</body>

image.png

正文

依赖收集与触发更新

function defineReactive(data, key, value){
  //如果当前对象的key 再是一个对象就进行深层监测
   
  //给每个属性都添加一个dep
  let dep = new Dep()

  observe(value)
  Object.defineProperty(data, key,{
    get() {
      if(Dep.target){  //dep.target 现在保存着watcher
        dep.depend()
      }
      return value
    },
    set(newValue) {
      //如果新值和老值相等 就不做处理
      if(value === newValue) return
      //如果设置的是一个对象的话,要再次对这个新的对象进行劫持。
      observe(newValue)
      value = newValue
      //告诉所有的watcher可以更新了
      dep.notify()
    }
  })
}

每个属性的dep

let id = 0;

class Dep{
  constructor() {  //要把watcher放到dep中
    this.subs = []
    this.id = id++
  }


  depend() {
    Dep.target.addDep(this)  //让watcher记住dep
  }

  addSub(watcher){ //让dep记住watcher
    this.subs.push(watcher)
  }

  notify(){
    this.subs.forEach((watcher)=>{
      watcher.update()
    })
  }
}

//做标识,用来存放watcher
Dep.target = null  //相当于创建了一个全局变量,静态属性

export default Dep

每个视图的watcher

import Dep from "./dep"

let id = 0
class Watcher {
  constructor(vm, fn, cb, options) {
    this.vm = vm;
    this.fn = fn;
    this.cb = cb;
    this.options = options;
    this.id = id++;
    this.depsId = new Set()
    this.deps = [] //存放dep

    this.getter = fn;   //fn 就是外面的渲染逻辑
    this.get();   //第一次做组件渲染
  }
  addDep(dep) {
    let did = dep.id  
    if(!this.depsId.has(did)){
      this.depsId.add(did)
      this.deps.push(dep)

      dep.addSub(this) //让dep记住watcher
    }
  }
  get() {
    //执行渲染逻辑 
    Dep.target = this;
    this.getter(); //执行渲染逻辑的时候会走render函数,会触发到数据响应式里面的get函数
    Dep.target = null;
  }

  update() {
    //会多次更新
    console.log('update')
    this.get()
  }

}

export default Watcher

dep和watcher的关系图

image.png

总结

  1. vue里面用到了观察者模式,默认组件渲染的时候会创建一个watcher,(并渲染视图)。
  2. 当渲染视图的时候,通过render函数的时候,就会走响应式里面的get方法,这时让这个属性的dep记录当前的这个watcher。
  3. 同时也让watcher记住这个dep(现在还没有用到)dep和watcher是多对多的关系,因为一个属性可能对应多个视图,一个视图对应多个数据。
  4. 如果数据发生变化,会通知对应属性的dep,依次通知存放的watcher去更新页面。

后续写当页面中多次修改同一个属性值的时候需要去重操作。nextTick()

系列文章链接(持续更新中...)

Vue 源码初探(一)响应式原理

Vue 源码初探(二)模板编译

Vue 源码初探(三)单组件挂载(渲染)

Vue 源码初探(四)依赖(对象)收集的过程

Vue 源码初探(五)对象异步更新nextTick()