zoukankan      html  css  js  c++  java
  • vue依赖收集代码分析

    结构

    Vue$3
        __data:
            __ob__: Observer
                dep: Dep
                    id: 2
                    subs: []
    

    subs里放置Watcher,当改变data数据时,触发对应的Dep通知其subs里的watcher进行更新。

    代码

    首先在 observer 的过程中会注册 get 方法,该方法用来进行「依赖收集」。在它的闭包中会有一个 Dep 对象,这个对象用来存放 Watcher 对象的实例。其实「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Wathcer 对象进行视图更新。

    function defineReactive (obj, key, val) {
        /* 一个Dep类对象 */
        const dep = new Dep();
        
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter () {
                /* 将Dep.target(即当前的Watcher对象存入dep的subs中) */
                dep.addSub(Dep.target);
                return val;         
            },
            set: function reactiveSetter (newVal) {
                if (newVal === val) return;
                /* 在set的时候触发dep的notify来通知所有的Wathcer对象更新视图 */
                dep.notify();
            }
        });
    }
    
    class Vue {
        constructor(options) {
            this._data = options.data;
            observer(this._data);
            /* 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象 */
            new Watcher();
            /* 在这里模拟render的过程,为了触发test属性的get函数 */
            console.log('render~', this._data.test);
        }
    }
    

    Dep 和 Watcher 定义如下

    class Dep {
        constructor () {
            /* 用来存放Wathcer对象的数组 */
            this.subs = [];
        }
    
        /* 在subs中添加一个Watcher对象 */
        addSub (sub) {
            this.subs.push(sub);
        }
    
        /* 通知所有Wathcer对象更新视图 */
        notify () {
            this.subs.forEach((sub) => {
                sub.update();
            })
        }
    }
    
    class Watcher {
        constructor () {
            /* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
            Dep.target = this;
        }
    
        /* 更新视图的方法 */
        update () {
            console.log("视图更新啦~");
        }
    }
    
    Dep.target = null;
    

    Dep 和 watcher 如何关联上?

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

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

    
    function noop (a, b, c) {}
    
    // lifecycle.js
    let updateComponent
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
    
    vm._watcher = new Watcher(vm, updateComponent, noop)
    

    在初始化 Watcher 的函数里调用 this.get

    var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      this.vm = vm;
      //...
      this.cb = cb;
      //...
      this.expression = expOrFn.toString();
      //...
      this.getter = expOrFn;
      //...
      this.value = this.lazy ? undefined : this.get();
    };
    

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

    // dep.js
    export function pushTarget (_target: Watcher) {
      if (Dep.target) targetStack.push(Dep.target)
      Dep.target = _target
    }
    export function popTarget () {
      Dep.target = targetStack.pop()
    }
    
    // watcher.js
    Watcher.prototype.get = function get() {
      pushTarget(this);
      var value;
      var vm = this.vm;
      //...
      value = this.getter.call(vm, vm);
      //...
      popTarget();
      this.cleanupDeps();
      //...
      return value;
    };
    
    

    上文 Watcher.prototype.get 中还要注意 this.getter.call(vm, vm), 执行的其实是上文表达式里的 vm._update(vm._render(), hydrating)。自然也就调用了

    调用到了 vm._render() 方法,要返回一个VNode,调试发现 vm.$options.render 其实就是

    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 函数就是就是输出虚拟节点的。

  • 相关阅读:
    使用Power Query从Web页面获取图像到Power BI报告中
    视频 |【2019】Power BI 8月产品功能更新讲解
    【2019】微软Power BI 每月功能更新系列——Power BI 8月版本功能完整解读
    视频 |【2019】Power BI 7月产品功能更新讲解
    2019 年 BI 平台 Top 14
    2016 黑客必备的Android应用都有哪些?
    J2EE完全手册(二)
    JavaBean ,Enterprise Bean(EJB), 三种Bean, 以及POJO
    J2EE完全手册(一)
    J2EE简介
  • 原文地址:https://www.cnblogs.com/everlose/p/12542023.html
Copyright © 2011-2022 走看看