博客列表 >写于vue3.0发布前夕的helloworld之三

写于vue3.0发布前夕的helloworld之三

子龙的博客搬家啦httpswwwcnblogscomZiLongZiLong
子龙的博客搬家啦httpswwwcnblogscomZiLongZiLong原创
2022年01月28日 10:27:50752浏览

接上,watcher构造函数:

var Watcher = function Watcher (
    vm,
    expOrFn,
    cb,
    options,
    isRenderWatcher
  ) {
    this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if (options) {
      this.deep = !!options.deep;
      this.user = !!options.user;
      this.lazy = !!options.lazy;
      this.sync = !!options.sync;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb;
    this.id = ++uid$2; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } else {
      this.getter = parsePath(expOrFn);
      if (!this.getter) {
        this.getter = noop;
        warn(
          "Failed watching path: \"" + expOrFn + "\" " +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        );
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get();
  };

watcher构造函数,首先和当前vm组件相互引用,然后初始化了一些下面执行中会用到的属性,然后检查expOrFn是否符合要求,有则用之,无则警告,最后执行:

this.value = this.lazy
      ? undefined
      : this.get();

因为this.lazy为false,进入Wathcer.prototype.get方法:

Watcher.prototype.get = function get () {
    pushTarget(this);
    var value;
    var vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      if (this.user) {
        handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  };

这个时候我们发现他调用了pushTarget方法:

function pushTarget (target) {
    targetStack.push(target);
    Dep.target = target;
  }

pushTarget方法很简单,就是将当前watcher实例push进了一个叫做targetStack的栈中,然后将Dep.target设置为当前watcher实例的引用,仔细想想你在哪里看到过这个,Dep.target

想起来没,就是设置data响应式数据的时候,get里的那个Dep.target,所以有了Dep.target,意味着我们要开始依赖收集了,然后代码继续走回到 Watcher.prototype.get中,调用了this.getter.call(vm, vm),这个方法其实就是我们上边说的updateComponent方法。然后走:

 vm._update(vm._render(), hydrating);

这里先会执行vm._render(),hydrating是一个变量表示是否是服务端生成模式,大可不必关心,我们现在要执行的是vm._render:

Vue.prototype._render = function () {
      var vm = this;
      var ref = vm.$options;
      var render = ref.render;
      var _parentVnode = ref._parentVnode;

      if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
          _parentVnode.data.scopedSlots,
          vm.$slots,
          vm.$scopedSlots
        );
      }

      // set parent vnode. this allows render functions to have access
      // to the data on the placeholder node.
      vm.$vnode = _parentVnode;
      // render self
      var vnode;
      try {
        // There's no need to maintain a stack becaues all render fns are called
        // separately from one another. Nested component's render fns are called
        // when parent component is patched.
        currentRenderingInstance = vm;
        vnode = render.call(vm._renderProxy, vm.$createElement);
      } catch (e) {
        handleError(e, vm, "render");
        // return error render result,
        // or previous vnode to prevent render error causing blank component
        /* istanbul ignore else */
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
            handleError(e, vm, "renderError");
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      } finally {
        currentRenderingInstance = null;
      }
      // if the returned array contains only a single node, allow it
      if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0];
      }
      // return empty vnode in case the render function errored out
      if (!(vnode instanceof VNode)) {
        if (Array.isArray(vnode)) {
          warn(
            'Multiple root nodes returned from render function. Render function ' +
            'should return a single root node.',
            vm
          );
        }
        vnode = createEmptyVNode();
      }
      // set parent
      vnode.parent = _parentVnode;
      return vnode
    };
  }

这里他主要做了几件事呢,其实就是将render函数,当前实例,当前实例的相关配置包含data那些东西取出来,因为我们_parentVnode为空,故直接走try里的render,render执行的时候是上下文为我们最开始定义的那个代理的对象,传进去了一个叫做vm.$createElement的方法,这个方法是在initRender的时候创建的,用于创建一个VNode的节点,然后进入render函数,因为render函数这个玩意啊,像我们一般人写的时候基本上不咋写,这里我们看看他长啥样子:

function anonymous() {
    with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(msg))])}
}

好啦,解释一下with干了个啥,相当于将this也就是vm._renderProxy加入到了当前作用域链的头,那么这个时候with里边的语句的变量可以访问到的东西第一个能访问的就是vm._renderProxy里的,然后执行_s(msg)的时候因为是一个读值操作,故而,触发了get方法,由于之前将vm.key,代理到了vm._data.key,所以首先会触发,vm.key的get方法:

sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    };

然后,在执行this[sourceKey][key]的时候会触发data上响应式数据的get方法,也就是这个时候开始收集依赖,并且见到了我们的Dep.target:

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
      }

前面说过Dep.target就是当前vue实例的watcher,然后执行dep.depend:

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

这里又调用了watcher的addDep并把当前的dep实例传了过去:

 /**
   * Add a dependency to this directive.
   */
  Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  };

判断了几下有没有之后,最终将watcher实例加入到了dep实例的subs数组中,get执行完毕,回到render函数中。

楼下继续

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议