zoukankan      html  css  js  c++  java
  • 说说 Vue 依赖收集

    前置说明

    vue 版本 2.6.2,测试用的代码

    <!DOCTYPE html>
    <html>
    <head>
      <title>vue test</title>
    </head>
    <body>
    <div id="app">
      {{message}}
    
      <button-counter :title="tt"></button-counter>
    </div>
    
      <!-- Vue.js v2.6.11 -->
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      <script>
        Vue.component('button-counter', {
          props: ['title'],
          data: function () {
            return {
              count: 0
            }
          },
          template: '<button v-on:click="count++">{{title}}: You clicked me {{ count }} times.</button>'
        });
        var app = new Vue({
          el: '#app',
          data: {
            message: 'TEST',
            tt: 'on'
          },
          mounted() {
            window.addEventListener('test', (e) => {
              this.message = e.detail;
            }, false);
          },
        })
    
        console.log(app);
        // var event = new CustomEvent('test', { 'detail': 5 }); window.dispatchEvent(event);
      </script>
    </body>
    </html>
    

    简要概括

    在拦截器(Object.defineProperty)里,在它的闭包中会有一个观察者(Dep)对象,这个对象用来存放被观察者(watcher)的实例。

    并且拦截器注册 get 方法,该方法用来进行「依赖收集」。其实「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。

    get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Wathcer 对象进行视图更新。

    function defineReactive$$1(obj, key, val, customSetter, shallow) {
      var dep = new Dep();
    
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          var value = getter ? getter.call(obj) : val;
          if (Dep.target) {
            dep.depend();
            //... 省略部分代码
          }
          return value;
        },
        set: function reactiveSetter(newVal) {
          var value = getter ? getter.call(obj) : val;
          /* eslint-disable no-self-compare */
          if (newVal === value || (newVal !== newVal && value !== value)) {
            return;
          }
          //... 省略部分代码
          val = newVal;
          dep.notify();
        }
      });
    }
    

    分析

    在初始化过程中(beforeCreate 和 created 之间) Object.defineProperty 劫持了数据

    劫持的过程中定义了观察者 dep,其结构非常简单:

    var Dep = function Dep () {
      this.id = uid++;
      this.subs = [];
    };
    

    然后在挂载过程中(beforeMount 和 mounted 之间) ,拦截器(Object.defineProperty) 触发了 get ,get 函数里 dep.depend(); 就做了观察者 dep 关联被观察者 watcher 的动作。

    Dep.prototype.depend = function depend () {
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    };
    

    watcher 的结构如下

    var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      this.vm = vm;
      // ... 省略部分
      this.cb = cb;
      this.id = ++uid$2; // uid for batching
      // ... 省略部分
      this.expression = expOrFn.toString();
    };
    

    所以最后结构观察者和被观察者就是这样的结构,完成了依赖收集。最终就是我们熟知的触发流程,点击上图代码的按钮时,拦截器的 set 触发了 dep.notify() 通知了所有被观察者 Wacher,而一番排队操作后需而触发 watcher 里的表达式,就去重新渲染这个组件。

    Dep {
      id: 9,
      subs: [
        0: Watcher {
          ...
          expression: "function () { vm._update(vm._render(), hydrating); }"
          ...
        }
      ]
    }
    

    以上有个关键的一步,为什么 Dep.target 为什么会指向这个 Watcher 对象?

    Dep.target 为什么会指向这个 Watcher 对象?

    在 callHook(vm, 'beforeMount') 后,进入 mount 阶段,此时初始化 Watcher

    
    function noop (a, b, c) {}
    
    var updateComponent;
    // 省略if (config.performance && mark)判断
    updateComponent = function() {
      vm._update(vm._render(), hydrating);
    };
    
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);
    

    我们知道 computed 属性会被标记为 lazy 直到取值时才触发 this.cb,那么一般情况下就调用 this.get。

    var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      this.vm = vm;
      // ... 省略部分
      vm._watchers.push(this);
      if (options) {
        this.lazy = !!options.lazy;
        // ... 省略部分
      }
      this.cb = cb;
      this.id = ++uid$2; // uid for batching
      // ... 省略部分
      this.expression = expOrFn.toString();
      this.value = this.lazy
          ? undefined
          : this.get();
    };
    

    而就是在 Watcher.prototype.get,注意 pushTarget,此时就和 Dep 发布者产生了联系,Dep 的 target 被设置为了这个 wacher,并且在每次监测对象被 get 时,就会往自身的 Dep 里推入这个 wacher。

    Watcher.prototype.get = function get() {
      pushTarget(this);
      var value;
      var vm = this.vm;
      //...
      value = this.getter.call(vm, vm);
      //...
      popTarget();
      this.cleanupDeps();
      
      return value;
    };
    
    function pushTarget (target) {
      targetStack.push(target);
      Dep.target = target;
    }
    function popTarget () {
      targetStack.pop();
      Dep.target = targetStack[targetStack.length - 1];
    }
    
    

    到此就完成了依赖收集。

    那么再来阐述下被观察者怎么开始更新视图的

    多数情况下,被观察者 Watcher 的结构里都有表达式 expression 属性,它的内容是 vm._update(vm._render(), hydrating),渲染的过程就是触发了此函数。

    那么首先需要调用 vm._render() 方法,此方法要返回一个 VNode

    Vue.prototype._render = function () {
      // ...
      var vm = this;
      var ref = vm.$options;
      var render = ref.render;
      vnode = render.call(vm._renderProxy, vm.$createElement);
      // ...
      return vnode
    }
    
    // 而render方法其实就是用于输出一个虚拟节点
    (function anonymous(
    ) {
    with(this){return _c('div',{attrs:{"id":"app"}},[(message + 1 > 1)?_c('div',[_v(_s(message + 1))]):_e(),_v(" "),_c('button',{on:{"click":function($event){message += 1}}},[_v("阿道夫")])])}
    })
    

    然后结果交给 vm._update

    Vue.prototype._update = function(vnode, hydrating) {
      var vm = this;
      var prevEl = vm.$el;
      var prevVnode = vm._vnode;
      // ...
      vm._vnode = vnode;
      
      // ...
      vm.$el = vm.__patch__(prevVnode, vnode);
      
      
      // ...
    };
    
    

    结论是 mount 阶段 初始化 Watcher,然后在 wathcer初始化后调用 get,get里 pushTarget(this),并且执行自身的getter也就是表达式,表达式的内容就是 vm._update(vm._render(), hydrating) 故而就开始执行 render函数,render 函数就是就是输出虚拟节点的。

  • 相关阅读:
    一、入门实例
    十四、事物配置
    JAVA-调用http链接
    ORACLE 年龄计算到月份
    ORACLE和MYSQL 去重查询
    JAVA--将图片转为BASE64编码并返回thymeleaf页面
    JAVA--将图片保存至项目路径
    java--获取类加载路径和项目根路径
    excel执行INSERT和UPDATE操作语句
    c# 未能加载Oracle.DataAccess, Version=2.112.1.0, Culture=neutral, PublicKeyToken=89b483f429c47342
  • 原文地址:https://www.cnblogs.com/everlose/p/12564451.html
Copyright © 2011-2022 走看看