美文网首页
vue响应式原理

vue响应式原理

作者: 晗笑书生 | 来源:发表于2020-04-22 22:15 被阅读0次
    image.png

    注意右侧的调用队列 进行初始化的时候 this._init() 初始化的时候调用 initState() initData() initData中会调用根节点的observe 将数据变成响应式对象


    image.png
    // initData()中调用根节点的 observe 
    // observe data
      observe(data, true /* asRootData */);
      observe只处理 对象 
     */
    function observe (value, asRootData) {
    // 不是对象 或 是 VNode就返回 这是递归的出口
      if (!isObject(value) || value instanceof VNode) {
        return
      }
      var ob;
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // 这是一个observe的自己
        ob = value.__ob__;
      } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
      ) {
    //  创建Observer的对象
        ob = new Observer(value);
      }
      if (asRootData && ob) {
        ob.vmCount++;
      }
      return ob
    }
    
    // Observer 对象
    var Observer = function Observer (value) {
      this.value = value;
    // watch的管理者 收集依赖 和派发更新中使用
      this.dep = new Dep();
      this.vmCount = 0;
    // 定义不可枚举的值 作为标识
      def(value, '__ob__', this);
      if (Array.isArray(value)) {
        if (hasProto) {
          protoAugment(value, arrayMethods);
        } else {
          copyAugment(value, arrayMethods, arrayKeys);
        }
        this.observeArray(value);
      } else {
      
        this.walk(value);
      }
    };
    // 如果是对象 每个对象中调用 defineReactive
    Observer.prototype.walk = function walk (obj) {
      var keys = Object.keys(obj);
      for (var i = 0; i < keys.length; i++) {
        defineReactive$$1(obj, keys[i]);
      }
    };
    // 如果是数组 递归调用observe每一个对象 所以都会走到 defineReactive
    Observer.prototype.observeArray = function observeArray (items) {
      for (var i = 0, l = items.length; i < l; i++) {
        observe(items[i]);
      }
    };
    

    下面来看看defineReactive的代码

    function defineReactive$$1 (
      obj,
      key,
      val,
      customSetter,
      shallow
    ) {
    // Dep是一个watch收集管理器
      var dep = new Dep();
    
      var property = Object.getOwnPropertyDescriptor(obj, key);
    // 如果是不可枚举的 就直接return 所以不可变更对象可以使用Object.frezone
      if (property && property.configurable === false) {
        return
      }
     // ...  
      var childOb = !shallow && observe(val);
    // 利用es5的defineProperty  劫持getter/setter  getter的时候 通过dep.depend()建立依赖收集 setter的时候dep.notify() 派发更新
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          var value = getter ? getter.call(obj) : val;
          if (Dep.target) {
            dep.depend();
            if (childOb) {
              childOb.dep.depend();
              if (Array.isArray(value)) {
                dependArray(value);
              }
            }
          }
          return value
        },
        set: function reactiveSetter (newVal) {
          var value = getter ? getter.call(obj) : val;
    
          if (getter && !setter) { return }
          if (setter) {
            setter.call(obj, newVal);
          } else {
            val = newVal;
          }
          childOb = !shallow && observe(newVal);
          dep.notify();
        }
      });
    }
    // 派发更新 执行堆栈dep.notify() watch.update() queueWatcher(this) flushSchedulerQueue() 执行watcher的run方法 如下源码所示
    dep.notify
    Dep.prototype.notify = function notify () {
      // stabilize the subscriber list first
      var subs = this.subs.slice();
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        // subs aren't sorted in scheduler if not running async
        // we need to sort them now to make sure they fire in correct
        // order
        subs.sort(function (a, b) { return a.id - b.id; });
      }
      for (var i = 0, l = subs.length; i < l; i++) {
    // subs是一个watch的数组
        subs[i].update();
      }
    };
    
    
    /**
     * Flush both queues and run the watchers.
     */
    function flushSchedulerQueue () {
      currentFlushTimestamp = getNow();
      flushing = true;
      var watcher, id;
    
      // Sort queue before flush.
      // This ensures that:
      // 1. Components are updated from parent to child. (because parent is always
      //    created before the child)
      // 2. A component's user watchers are run before its render watcher (because
      //    user watchers are created before the render watcher)
      // 3. If a component is destroyed during a parent component's watcher run,
      //    its watchers can be skipped.
      queue.sort(function (a, b) { return a.id - b.id; });
    
      // do not cache length because more watchers might be pushed
      // as we run existing watchers
      for (index = 0; index < queue.length; index++) {
        watcher = queue[index];
        if (watcher.before) {
          watcher.before();
        }
        id = watcher.id;
        has[id] = null;
        watcher.run();
        // in dev build, check and stop circular updates.
        if (process.env.NODE_ENV !== 'production' && has[id] != null) {
          circular[id] = (circular[id] || 0) + 1;
          if (circular[id] > MAX_UPDATE_COUNT) {
            warn(
              'You may have an infinite update loop ' + (
                watcher.user
                  ? ("in watcher with expression \"" + (watcher.expression) + "\"")
                  : "in a component render function."
              ),
              watcher.vm
            );
            break
          }
        }
      }
    
    image.png

    Watcher.prototype.run = function run () {
    if (this.active) {
    var value = this.get();
    if (
    value !== this.value ||
    // Deep watchers and watchers on Object/Arrays should fire even
    // when the value is the same, because the value may
    // have mutated.
    isObject(value) ||
    this.deep
    ) {
    // set new value
    var oldValue = this.value;
    this.value = value;
    if (this.user) {
    try {
    // 执行用户定义的watch的回调 参数 newValue, oldValue
    this.cb.call(this.vm, value, oldValue);
    } catch (e) {
    handleError(e, this.vm, ("callback for watcher "" + (this.expression) + """));
    }
    } else {
    this.cb.call(this.vm, value, oldValue);
    }
    }
    }
    };

    // mountComponent vue数据更新驱动视图的原理
    // 定义渲染watcher  watch的get函数 对应的updateComponent  会改变视图的变化 既是渲染watch初始化的时候被订阅了对应的响应式数据 数据发生了变化会触发这个视图更新 
    // 这里会执行 watch的run 会执行 this.get 求职 会执行 updateComponent函数 这样会触发视图的更新
    简单来说 响应数据发生变化 会触发 defineReactive 的setter中this.get 然后会触发 渲染watch的updateComponent 从而达到视图的变化
        updateComponent = function () {
          vm._update(vm._render(), hydrating);
        };
      }
    
      // we set this to vm._watcher inside the watcher's constructor
      // since the watcher's initial patch may call $forceUpdate (e.g. inside child
      // component's mounted hook), which relies on vm._watcher being already defined
      new Watcher(vm, updateComponent, noop, {
        before: function before () {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, 'beforeUpdate');
          }
        }
      }, true /* isRenderWatcher */);
      hydrating = false;
    

    在vue中, 数据模型下的所有属性,会被Vue使用Object.defineProperty(Vue3使用Proxy)进行数据劫持代码里。响应式的机制是观察者模式, 数据是被观察的一方, 一旦发生变化, 通知所有的观察者, 这样观察者可以做出响应, 当观察者为视图的时候,会更新视图。

    Vue的响应式系统的三个重要概念, Dep, Observer, Watcher.

    Dep 调度中心-订阅器

    Dep是负责调度和订阅watcher的 dep在getter触发了dep.depend 收集依赖 在setter的时候 触发了dep.notify 派发更新

    Observer 发布者

    Observer是发布者, 主要作用是vm初始化的时候, 调用defineReactive函数, 使用Object.defineProperty方法对对象的每一个子属性进行数据劫持、监听, 就是为每个属性添加上gettersetter, 使属性变成响应式。

    Watcher-观察者

    Watcher 扮演的角色是订阅者/观察者,他的主要作用是为观察属性提供回调函数以及收集依赖,当被观察的值发生变化时,会接收到来自调度中心Dep的通知,从而触发回调函数。

    而Watcher又分为三类,normal-watchercomputed-watcherrender-watcher

    normal-watcher:在组件钩子函数watch中定义,即监听的属性改变了,都会触发定义好的回调函数。
    computed-watcher:在组件钩子函数computed中定义的,每一个computed属性,最后都会生成一个对应的Watcher对象,但是这类Watcher有个特点:当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备lazy(懒计算)特性。
    render-watcher:每一个组件都会有一个render-watcher, 当data/computed中的属性改变的时候,会调用该Watcher来更新组件的视图。
    这三种Watcher也有固定的执行顺序,分别是:computed-render -> normal-watcher -> render-watcher。这样就能尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher 排在 computed-render 前面,就会导致页面更新的时候 computed 值为旧数据。

    小结

    image.png

    Observer 负责将数据进行拦截,Watcher 负责订阅,观察数据变化, Dep 负责接收订阅并通知 Observer 和接收发布并通知所有 Watcher。

    整理的文章 可以参考我的语雀

    相关文章

      网友评论

          本文标题:vue响应式原理

          本文链接:https://www.haomeiwen.com/subject/uitvihtx.html