Vue响应式系统的核心-----变化侦测(Object)

什么是变化侦测

变化侦测的作用是侦测数据的变化,当数据变化时,会通知视图进行相应的更新

在运行时应用内部的状态会不断发生变化,此时需要不停地重新渲染。这时如何确定状态中发生了什么变化?变化侦测主要用来解决这个问题。

Vue的变化侦测属。当状态发生变化时,Vue立刻知道了,在一定程度上知道哪些状态变了。

有一个状态绑定多个依赖,每个依赖表示一个具体的DOM节点,当状态变化了,向这个状态的所有依赖发送通知,让他们进行DOM更新操作。每个状态绑定的依赖越多,依赖追踪在内存上的开销就会越大。

从vue.js 2.0开始引入了虚拟DOM,一个状态所绑定的依赖不再是具体的DOM节点,而是一个组件。当状态变化后,会通知到组件,组件内部在使用虚拟DOM进行比对。这样可以大大降低依赖数量,从而降低依赖追踪所消耗的内存。

Object的变化侦测

  • Data通过Observer转换成了getter/setter的形式来追踪变化

  • 当外界通过watcher读取数据时,会触发getter从而将watcher添加到依赖中

  • 当数据发生变化时,会触发setter,从而向Dep中的依赖发送通知。

  • watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。

如何追踪变化

在vue中如何侦测一个对象的变化?使用Object.defineProperty和ES6的Proxy

 function defineReactive(data,key,val){
        Object.defineProperty(data,key,{
            enumerable: true,
            configuration: true,
            get: function(){
                return val
            },
            set: function(newVal){
                if(val === newVal){
                    return
                }
                val = newVal
            }
        })
    }

每当从data的key中读取数据时,get函数被触发;每当往data的key中设置数据时,set函数被触发。

如何收集依赖

观察数据目的是当数据的属性发生变化时,可以通知那些曾经使用该数据的地方。

  <template>
         <h1>{{name}}</h1>
    </template>

该模板使用了数据name,所以当它发生变化时,要向使用了它的地方发送通知。

收集依赖:把用到数据name的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍。

依赖收集在哪里

在getter中收集依赖,在setter中触发依赖

dep类专门管理依赖,使用这个类可以收集依赖、删除依赖或向依赖发送通知等。

    export default class Dep {
      constructor () {
        this.subs = []
      }
    
      addSub (sub) {
        this.subs.push(sub)
      }
      // 删除一个依赖
      removeSub (sub) {
        remove(this.subs, sub)
      }
      // 添加一个依赖
      depend () {
        if (window.target) {
          this.addSub(window.target)
        }
      }
      // 通知所有依赖更新
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    function defineReactive (obj,key,val) {
      const dep = new Dep()  //实例化一个依赖管理器,生成一个依赖管理数组dep
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
          dep.depend()    // 在getter中收集依赖
          return val;
        },
        set(newVal){
          if(val === newVal){
              return
          }
          val = newVal;
          dep.notify()   // 在setter中通知依赖更新
        }
      })
    }

依赖是谁?

当属性发生变化后,需要通知谁

其实在Vue中还实现了一个叫做Watcher的类,而Watcher类的实例就是我们上面所说的那个"谁"。换句话说就是:谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher实例。在之后数据变化时,我们不直接去通知依赖更新,而是通知依赖对应的Watch实例,由Watcher实例去通知真正的视图。

    export default class Watcher {
      constructor (vm,expOrFn,cb) {
        this.vm = vm;
        this.cb = cb;
        this.getter = parsePath(expOrFn)
        this.value = this.get()
      }
      get () {
        window.target = this;
        const vm = this.vm
        let value = this.getter.call(vm, vm)
        window.target = undefined;
        return value
      }
      update () {
        const oldValue = this.value
        this.value = this.get()
        this.cb.call(this.vm, this.value, oldValue)
      }
    }
    
    
    /**
     * Parse simple path.
     * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
     * 例如:
     * data = {a:{b:{c:2}}}
     * parsePath('a.b.c')(data)  // 2
     */
    const bailRE = /[^\w.$]/
    export function parsePath (path) {
      if (bailRE.test(path)) {
        return
      }
      const segments = path.split('.')
      return function (obj) {
        for (let i = 0; i < segments.length; i++) {
          if (!obj) return
          obj = obj[segments[i]]
        }
        return obj
      }
    }
    

递归侦测所有key

前面的代码只能侦测数据中的某一个属性,我们希望把数据中的所有属性都侦测到,所以需要封装一个Observer类。这个类的作用将一个数据内的所有属性都转换成getter/setter的形式,然后再追踪它们的变化。

 function Observer (value) {
        this.value = value;
        if (!Array.isArray(value)) {
          this.walk(value);
        }
      };
      Observer.prototype.walk = function walk (obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
          defineReactive$$1(obj, keys[i],obj[keys[i]]);
        }
      };

    function defineReactive (obj,key,val) {
       //新增,递归子属性
        if(typeof val === 'object'){
            new Observer(val);
        }
      const dep = new Dep()  //实例化一个依赖管理器,生成一个依赖管理数组dep
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
          dep.depend()    // 在getter中收集依赖
          return val;
        },
        set(newVal){
          if(val === newVal){
              return
          }
          val = newVal;
          dep.notify()   // 在setter中通知依赖更新
        }
      })
    }

不足之处

虽然我们通过Object.defineProperty方法实现了对object数据的可观测,但是这个方法仅仅只能观测到object数据的取值及设置值,当我们向object数据里添加一对新的key/value或删除一对已有的key/value时,它是无法观测到的,导致当我们对object数据添加或删除值时,无法通知依赖,无法驱动视图进行响应式更新。

当然,Vue也注意到了这一点,为了解决这一问题,Vue增加了两个全局API:Vue.set和Vue.delete。

posted @ 2021-11-06 17:35  某某雅  阅读(191)  评论(0)    收藏  举报