前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue源码分析之defineReactive方法中为什么有两种dep在收集依赖?

vue源码分析之defineReactive方法中为什么有两种dep在收集依赖?

作者头像
玖柒的小窝
修改2021-10-08 11:28:36
1.6K1
修改2021-10-08 11:28:36
举报
文章被收录于专栏:各类技术文章~各类技术文章~

了解vue响应式原理对童鞋,想必对defineReactive方法有印象,这是vue响应式的核心方法。在这个方法,给obj的每个key对应的都new了一个dep,这个dep保存在defineReactive闭包中。这个dep的作用非常明显,收集当前watcher,以便在触发obj属性set方法的时候通知watcher更新。

代码语言:javascript
复制
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep();
  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }
  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }
  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const 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) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    },
  });
}
复制代码

但是从上面的defineReactive方法中可以看到,除了obj每个key对应的dep收集依赖(下面称为闭包中dep),还有一个 childOb.dep.depend()在收集依赖。

令人一头雾水的是,在reactiveSetter方法中,只有闭包中的dep在通知watcher, childOb.dep并没有通知watcher,而且这个dep也没必要通知watcher。那 childOb.dep的作用是什么?这里先说作用,是给Vue.set()方法用的。如果还不清楚Vue.set方法的童鞋,建议先了解Vue.set的原理。

我们先看看childOb是什么

从下面代码可以知道,childOb是Observer的实例,在constructor中给childOb添加了dep属性

代码语言:javascript
复制
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor(value: any) {
    this.value = value;
    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);
    }
  }
  walk(obj: Object) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]);
    }
  }

  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  }
}
复制代码

我们举个实际的例子看看

代码语言:javascript
复制
data(){
 return {
		a: {
			b: 1
		}
	}
}
复制代码

在vue初始化,会调用observe对data返回obj进行响应式处理(后面称为dataObj)。接着在new Observer中,会通过walk方法,循环♻️遍历拿到dataObj的每个key,进行defineReactive

代码语言:javascript
复制
export function observe(value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return;
  }
  let ob: Observer | void;
  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;
}
复制代码

在对a属性defineReactive的时候,这里我们称a属性对应的val为aVal。 此时的childOb就是经过Observer包装过的aVal。childOb.value指向aVal,并且多了一个dep属性用来收集依赖。

下面是最核心的一行代码,通过def又给value(这个value是aVal)添加了__ob__属性,指向childOb自己。经过这一步骤,我们就可以通过aVal.ob.dep访问到aVal对应的dep。

代码语言:javascript
复制
def(value, "__ob__", this);
复制代码

众所周知,Vue不能检测到对象属性的添加,为了解决这个问题,Vue添加了一个Vue.set方法。我们看一下set方法

代码语言:javascript
复制
export function set(target: Array<any> | Object, key: any, val: any): any {
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    target.splice(key, 1, val);
    return val;
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  const ob = (target: any).__ob__;
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid adding reactive properties to a Vue instance or its root $data " +
          "at runtime - declare it upfront in the data option."
      );
    return val;
  }
  if (!ob) {
    target[key] = val;
    return val;
  }
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}
复制代码

到这里想必大家都恍然大悟了,对某个对象进行Vue.set后,还需要通知watcher去更新。这个对象新增的属性虽然通过defineReactive进行了响应式处理,但是它对应dep存在闭包中,根本取不到。所以最后可以通过这个对象身上的__ob__对象,取出dep通知对应的watcher更新。

代码语言:javascript
复制
ob.dep.notify();
复制代码

从上面源码分析我们还可以知道一个注意点⚠️,Vue 不允许动态添加根级响应式属性

代码语言:javascript
复制
this.$set(this.data,”key”,value')
复制代码

这种写法是🈲️的,set方法在非生产环境会给出警告

代码语言:javascript
复制
if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid adding reactive properties to a Vue instance or its root $data " +
          "at runtime - declare it upfront in the data option."
      );
    return val;
  }
复制代码

原因就是初始化observe(this.data)的时候,我们无法通过返回的ob收集依赖。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档