Vue源码系列二——数据劫持+发布订阅模式

334 阅读6分钟

前言

上一篇讲述了Vue源码系列第一章Vue源码系列——— 流程,现在讲下发布订阅模式的实现,本来应该分开讲述data、watch、computed,不过掘金上很多人都讲的很好,也就不再赘述,所以我打算从发布订阅(初始化时收集依赖,改变数据触发发布流程)入手,将props、data、watch、computed顺带一起讲清楚。

依照惯例,先从一段简单的代码入手

// main.js
import App from './App'
new Vue({
  el: '#app',
  components: { App},
  template: `<app :name="name"></app>`,
  data () {
    return {
      name: 'Wayag'
    }
  }
})
// App.js
import HelloWorld from './components/HelloWorld'
export default {
  name: 'App',
  props: {
    name: {
      type: String,
      default: ''
    }
  },
  computed: {
    total () {
      return this.num + 1
    }
  },
  watch: {
    total () {
      console.log(this.total)
    },
    num () {
      console.log(this.num)
    }
  },
  data () {
    return {
      num: 0
    }
  },
  methods: {
    add () {
      this.num++
    }
  }
}

从vm._render执行_c('app',{attrs:{name: 'Wayag' }})开始分析,这个时候会先进入createComponent函数生成组件的构造函数Sub,并传入data = { attrs: { name: 'Wayag' } }

function createComponent (
  Ctor,
  data,
  context,
  children,
  tag
) {
  var baseCtor = context.$options._base; // Vue
  //  Vue.extend = function (extendOptions) {
  //    extendOptions = extendOptions || {};
  //    var Super = this;
  //    var SuperId = Super.cid;
  //    var Sub = function VueComponent (options) {
  //      this._init(options);
  //    };
  //    Sub.prototype = Object.create(Super.prototype);
  //    Sub.prototype.constructor = Sub;
  //    Sub.cid = cid++;
  //    Sub.options = mergeOptions(
  //      Super.options,
  //      extendOptions
  //    );
  //    if (Sub.options.props) {
  //      设置代理,访问vm.name代理访问vm._props_.name
  //      // Object.defineProperty(Sub.prototype, 'name', {
  //      //   get: function() {
  //      //     return this['_props'][key]}
  //      //   }
  //      //   set: function(val) {
  //      //     this['_props'][key]} = val
  //      //     }
  //      // })
  //      initProps$1(Sub);
  //    }
  //    if (Sub.options.computed) {
  //      设置代理,将computed对象属性添加到Sub.prototype上,设置成响应式数据
  //      // Object.defineProperty(Sub.prototype, 'total', createComputedGetter)
  //      initComputed$1(Sub);
  //    }

  //    Sub.extend = Super.extend;
  //    Sub.mixin = Super.mixin;
  //    Sub.use = Super.use;

  //    if (name) {
  //      Sub.options.components[name] = Sub; //
  //    }

  //    Sub.superOptions = Super.options;
  //    Sub.extendOptions = extendOptions;
  //    Sub.sealedOptions = extend({}, Sub.options);
  //    cachedCtors[SuperId] = Sub;
  //    return Sub
  //  };
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor); // 相当于调用了Vue.extend函数
  }

  data = data || {};
  //  function extractPropsFromVNodeData (
  //    data,
  //    Ctor,
  //    tag
  //  ) {
  //    var propOptions = Ctor.options.props; // 子组件内部定义的props
  //    var res = {};
  //    var attrs = data.attrs; // 父组件传递过来的参数
  //    var props = data.props;
  //    if (isDef(attrs) || isDef(props)) {
  //      for (var key in propOptions) {
            // 这里2个checkprop函数就是看是否父组件传递了,并且子组件的props里定义了
  //        checkProp(res, props, key, altKey, true) ||
  //        checkProp(res, attrs, key, altKey, false);
  //      }
  //    }
  //    return res
  //  }
  var propsData = extractPropsFromVNodeData(data, Ctor, tag); // { name: 'Wayag' }
  // 给data添加hook属性hook: { init: function() {}, prepatch: function() {}, insert: 
  // function() {}, destroy: function() {}}
  installComponentHooks(data); 
  var name = Ctor.options.name || tag;
  // propsData = { name: 'Wayag' },这时propsData、Ctor属性就被保存到了, 
  // vnode.componentOptions内,Ctor就是构造函数Sub,这里的data: {attrs: {}},attrs里面的属性
  // 被移除了,propsData的赋值和data.attrs移除都是在extractPropsFromVNodeData函数内实现的。
  var vnode = new VNode(
    ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
    data, undefined, undefined, undefined, context,
    { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
    asyncFactory
  );

  return vnode
}

这里有必要解释一下initComputed$1函数里面的createComputedGetter,Object.defineProperty(target, key, {get: createComputedGetter, set: noop}),computed属性不允许直接修改。

function createComputedGetter (key) {
  return function computedGetter () {
    // 这里this._computedWatchers[key]有值,是在vm._init初始化initComputed之后才有的,
    // 所以其实在这里将computed的属性变为响应式时并不会执行computedGetter函数,这是在
    // user Watcher中监听了或者在render函数渲染时访问vm.total,也就是computed属性时才会调用这
    // 里的computedGetter函数,进而
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) { 
        // 是否进行重新计算,重新计算都会涉及依赖收集,主要看computed watcher依赖了哪个属性。
        watcher.evaluate();
      }
      // 执行完watcher.evaluate函数,computedWatcher已经从栈里面被删除了,Dep.target也从computedWatcher到userWatcher或者渲染Watcher了。
      if (Dep.target) {
        // 这里就是“月老牵线”的重点,让data属性xxx与userWatcher或者渲染watcher建立关系
        watcher.depend(); 
      }
      return watcher.value
    }
  }
}

现在知道子组件vm是如何访问到父组件传递的参数的了,如下代码所示: vm.$options.propsData

function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;

  var vnodeComponentOptions = parentVnode.componentOptions;
  // vm.$options.propsData = parentVnode.componentOptions.propsData
  opts.propsData = vnodeComponentOptions.propsData; 
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;
}

前面讲了vm.$options获取父组件传递的参数,接下来进入文章主题,发布订阅模式的前提,就是将数据变成响应式数据。在_init函数里面。

function initState(vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

// 初始化props
function initProps (vm, propsOptions) {
  var propsData = vm.$options.propsData || {};
  var props = vm._props = {};
  var loop = function ( key ) {
    keys.push(key);
    var value = validateProp(key, propsOptions, propsData, vm);
    defineReactive$$1(props, key, value);
    if (!(key in vm)) {
      proxy(vm, "_props", key);
    }
  };

  for (var key in propsOptions) loop( key );
  toggleObserving(true);
}
// 初始化data
function initData (vm) {
  var data = vm.$options.data;
  data = vm._data
  var keys = Object.keys(data);
  while (i--) {
    var key = keys[i];
    proxy(vm, "_data", key);
  }
  observe(data, true /* asRootData */);
}

// initData时的observe函数
function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  // 如果存在.__ob__属性说明此data已经被响应式处理过,之后进来就不再需要重新被定义为响应式
  // 场景:如果name是来自父级传入的对象,data中两个数据引用了同一个name对象,此时就会进入下面第 
  // 一个if语句。
  //  props: {
  //    name: {
  //      type: Object,
  //      default: () => {
  //        return {}
  //      }
  //    }
  //  },
  //  data () {
  //    return {
  //    num: 0,
  //    b: this.name,
  //    c: this.name
  //  }
  },
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}
// initData时observe函数里面的Observer函数
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  // 将对象添加__ob__属性,数据是否已经被监听
  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);
  }
};
// 将数据变成响应式函数,包括data,和props上的数据
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  // props和data里的属性用于收集依赖的框dep
  var dep = new Dep();
  
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow && observe(val);
  // 其实这里监听的是vm._data[key]
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 除非手写getter、setter函数,不然都是直接使用val
      var value = getter ? getter.call(obj) : val; 
      // new Watcher()执行时会访问props或者data内定义属性的将自身这个watcher赋值给Dep.target = 
      // this,所以dep收集依赖是看当前是执行哪个new Watcher(),基本是三个:computedWatcher、
      // userWatcher、renderWatcher
      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;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter();
      }
      if (getter && !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}
// 初始化computed
function initComputed (vm, computed) {
  // 先定义vm._computedWatchers
  var watchers = vm._computedWatchers = Object.create(null);
  // computed properties are just getters during SSR
  var isSSR = isServerRendering();
  // debugger
  for (var key in computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        ("Getter is missing for computed property \"" + key + "\"."),
        vm
      );
    }

    if (!isSSR) {
      // 往vm._computedWatchers里面加入computedWatcher,将computed里面的属性加入到
      // vm._computedWatchers里面,后续访问
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(("The computed property \"" + key + "\" is already defined in data."), vm);
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
      }
    }
  }
}

// 初始化watch
 function initWatch (vm, watch) {
  for (var key in watch) {
    var handler = watch[key];
    if (Array.isArray(handler)) {
      for (var i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

  function createWatcher (
    vm,
    expOrFn,
    handler,
    options
  ) {
    if (isPlainObject(handler)) {
      options = handler;
      handler = handler.handler;
    }
    if (typeof handler === 'string') {
      handler = vm[handler];
    }
    return vm.$watch(expOrFn, handler, options)
  }

  Vue.prototype.$watch = function (
    expOrFn,
    cb,
    options
  ) {
    var vm = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {};
    options.user = true;
    var watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value);
      } catch (error) {
        handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
      }
    }
    return function unwatchFn () {
      watcher.teardown();
    }
  };
// 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 = process.env.NODE_ENV !== 'production'
    ? expOrFn.toString()
    : '';
  // parse expression for getter
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    // parsePath函数,watch里面的变量会调用这个函数,然后访问到vm.total,然后会触发
    // initComputed里面做的响应式监听,触发evaluate函数,执行computedWatcher的get函数。
    // function parsePath (path) {
    //   if (bailRE.test(path)) {
    //     return
    //   }
    //   var segments = path.split('.');
    //   return function (obj) {
    //     for (var i = 0; i < segments.length; i++) {
    //       if (!obj) { return }
    //       obj = obj[segments[i]];
    //     }
    //     return obj
    //   }
    // }
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = noop;
      process.env.NODE_ENV !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  // 如果这里是computedWatcher的话,this.lazy = true,是不会执行this.get的,是当触发下面的
  // evaluate函数的时候执行的(userWatcher或者renderWatcher访问computed)
  // Watcher.prototype.evaluate = function evaluate () {
  //   this.value = this.get();
  //   this.dirty = false;
  // };
  this.value = this.lazy
    ? undefined
    : this.get();
};

// this.get
Watcher.prototype.get = function get () {
  // 这里就是当前watcher去调用get的时候会将当前watcher赋值给Dep.target
  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
};

说明:watch监听了total属性,或者渲染函数读取到了vm.total都会触发computed的total,触发computed的过程被称为“月老牵线”,假如是watch里面监听了total属性,那么会触发computedWatcher的evaluate 函数,也就是Watcher.prototype.get,然后会执行computed里面的total函数,如果(一般来说)依赖了initdata里面的xxx属性的话,会触发vm.xxx,xxx开始使用dep收集依赖,此时Dep.target = computedWatcher,就将computedWatcher(total)收集到了xxx属性里面的dep.subs里,反过来computedWatcher也将dep收集到了this.deps里,之后将computedWatcher从栈里面删除,Dep.targer从computedWatcher到userWatcher或者renderWatcher,执行watcher.depend(),让Computedwatcher里面this.deps收集的dep依次执行dep.depend收集dep.target,此时Dep.target是userWatcher或者renderWatcher,就把他们收集到了xxx.subs里面。

Watcher.prototype.depend = function depend () {
  var i = this.deps.length;
  while (i--) {
    this.deps[i].depend();
  }
};

讲完了订阅(收集依赖的过程),后面讲发布(数据更新)的过程。 前面我们知道了data属性的dep收集到了subs也就是订阅者们,通过修改data属性数据,会触发上面的Object.defineProperty中的set函数,其实set函数只需要做两件事:1.将val = newVal赋值 2.执行dep.notify(),实现发布

   Object.defineProperty(vm._data, key, {
     set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter();
      }
      if (getter && !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      // 
      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.sort(function (a, b) { return a.id - b.id; });
  }
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

Watcher.prototype.update = function update () {
  if (this.lazy) { // this.lazy是当watcher是computedWatcher的时候,只需要将this.dirty =   
                   // false就行了,后续访问到computed属性的时候就可以触发重新计算。
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else { // 其他的userWatcher或者renderWatcher会进入这里的条件
    queueWatcher(this);
  }
};

function queueWatcher (watcher) {
  var id = watcher.id; 
  if (has[id] == null) {
    has[id] = true; // 保存watcher.id,避免重复添加watcher
    if (!flushing) {
      queue.push(watcher);
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      var i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    if (!waiting) {
      waiting = true;

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue();
        return
      }
      // nextTick是一个微任务,作用是将flushSchedulerQueue放到下一个微任务(或宏任务)再执行,这
      // 样做的好处就是同步多次修改同一个数据时,肯定会多次触发该数据收集的watcher,前面已经将重
      // 复触发watcher的步骤过滤掉了,加入nextTick不是一个微任务(也可能是宏任务)而是立即执行的
      // 话,只能获取第一次修改的值,而微任务则会因为异步获取到最后修改的值。
      nextTick(flushSchedulerQueue);
    }
  }
}

function nextTick (cb, ctx) {
  var _resolve;
  callbacks.push(function () {
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    pending = true;
    timerFunc();
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(function (resolve) {
      _resolve = resolve;
    })
  }
}
// timerFunc是一个异步,优先级Promise > setImmediate > setTimeout
var timerFunc;
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve();
  timerFunc = function () {
    p.then(flushCallbacks);
    if (isIOS) { setTimeout(noop); }
  };
  isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  var counter = 1;
  var observer = new MutationObserver(flushCallbacks);
  var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true
  });
  timerFunc = function () {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = function () {
    setImmediate(flushCallbacks);
  };
} else {
  // Fallback to setTimeout.
  timerFunc = function () {
    setTimeout(flushCallbacks, 0);
  };
}
// flushSchedulerQueue函数
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow();
  flushing = true;
  var watcher, id;

  queue.sort(function (a, b) { return a.id - b.id; });
  // 这里为什么不用let len = queue.length,因为其实这里的queue是不断变化的,根据什么变化?是根
  // 据变化的子组件决定的,那我们知道flushSchedulerQueue是在微任务里面,而在当前组件中,数据变
  // 化会执行依赖该数据的watcher,而且只有当前组件才有机会进入这里,那根据props传值的子组件如何
  // 能够根据父组件传递的数据将其watcher加入进来这里的queue队列中呢?其实是下面的watcher.run()
  // 做到的,而且是当前组件的渲染watcher。如下图可以看出执行流程(从下往上),渲染watcher去执行
  // 渲染过程也就是patch过程的时候,prepatch之后执行updateChildComponent中有一句
  // props[key] = validateProp(key, propOptions, propsData, vm)这句会触发props的set过程,
  // 之后便是notify -> update -> queueWatcher往queue队列里面去添加watcher,所以前面为什么说
  // queue.length是一直变化的了。这个过程也可以说是父组件data属性变化时,子组件如何根据父组件
  // data的变化而重新渲染。
  
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    // 其实就是生命周期updateBefore
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null; // 释放has[id]
    // 主要逻辑,执行this.get(),并且执行回调this.cb()
    watcher.run();
  }

  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();
  // 重置
  resetSchedulerState(); // has = {}, queue.length = 0
}

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;
      // 调用ComputedWatcher和userWatcher的回调函数并传入value,这样就可以在回调函数中获
      // 到值。
      this.cb.call(this.vm, value, oldValue);
    }
  }
};

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
};

微信图片_20210413102447.png