数据观测

What

什么是数据观测?vue 是数据驱动的,数据改变就会导致视图改变。所以要点就是 vue 如何知道数据的变化,数据变化后如何更新对应的视图。其实最核心的方法便是通过 Object.defineProperty()来实现对属性的访问劫持和变化劫持。

Why

vue 的内部其实有很多个重要的部分,数据观测,模板编译,virtualDOM,整体运行流程等, 数据观测是 vue 框架中的一个重要部分,了解其原理有助于提高对 vue 的理解和使用。

How

Vue 的数据观测,核心机制是 观察者模式。 数据是被观察的一方,当数据发生变化时,通知所有的观察者,这样观察者可以做出响应。比如,重新渲染视图。 我们把依赖数据的观察者称为 watcher。这种关系可以表示为:data -> watcher 数据可以有多个观察者,怎么记录这种依赖关系呢?

Vue 通过在 data 和 watcher 间创建一个 dep 对象,来记录这种依赖关系: data - dep -> watcher dep 的结构很简单,除了唯一标识属性 id,另一个属性就是用于记录所有观察者的 subs。

接下来我们就来具体分析源码。 先找到数据观测方法的入口所在的地方,是initData或者observe,当 data 为空的时候就直接执行observe









 

 







export function initState(vm: Component) {
  vm._watchers = [];
  const opts = vm.$options;
  if (opts.props) initProps(vm, opts.props);
  if (opts.methods) initMethods(vm, opts.methods);

  //开始处理option中的data
  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);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

initData主要做了几件事情:

  • 从 vm 上获取 data 对象。
  • 判断 data 对象的每一个 key,不可以和 props、methods 中的 key 相同。否则会报警告。可以看出优先级是 methods > props > data。
  • 代理 vm._data 到 vm 上,就可以通过通过 this.xxx 访问_data 上的属性了。
  • 开始观测 data。
function initData(vm: Component) {
  let data = vm.$options.data;
  // 获取data这个json对象
  data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== "production" &&
      warn(
        "data functions should return an object:\n" +
          "https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function",
        vm
      );
  }

  const keys = Object.keys(data);
  const props = vm.$options.props;
  const methods = vm.$options.methods;
  let i = keys.length;
  while (i--) {
    const key = keys[i];
    if (process.env.NODE_ENV !== "production") {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== "production" &&
        warn(
          `The data property "${key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
          vm
        );
    } else if (!isReserved(key)) {
      // 通过this.xxx访问_data
      proxy(vm, `_data`, key);
    }
  }
  // observe data
  // 开始观察data
  observe(data, true /* asRootData */);
  // data:{
  //   a:1,
  //   b:2,
  //   c:{
  //     d:4
  //   }
  // }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

我们看到调用 observe 方法给其传了两个参数,第一个就是 data 对象,第二个就是是否作为根 data 的标志。 如下代码就是 observe 方法具体的实现过程。 这个函数最终返回了 ob,ob 就是 Observer 的实例,是通过 data 初始化的。data 必须是对象或数组,否则不继续。如果 value 是数组或普通对象且可继承,则用它来初始化一个 Observer 实例。如果 data 有__ob__属性,并且__ob__属性引用的是 Observer 的实例,则ob = value.__ob____ob__这个属性是在初始化 Observer 实例的时候加上的。有了这个属性就表示这个对象被观测过。

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 开始观测对象或数组
 */
export function observe(value: any, asRootData: ?boolean): Observer | void {
  // data必须是对象或数组,否则不继续
  if (!isObject(value) || value instanceof VNode) {
    return;
  }
  let ob: Observer | void;
  // 如果data有__ob__属性,并且__ob__属性引用的是Observer的实例,则ob = value.__ob__
  if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    // 如果value是数组或普通对象且可继承,则用它来初始化一个Observer实例
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  // 返回observer实例
  return ob;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

new Observer(value)这里面具体做了什么呢? 先看 constructor,做了几件事:

  • 把 data 自身赋值到 observer 实例的 value 属性,
  • 生成一个 dep 对象,赋值到 observer 实例的 dep 属性,
  • 给自身添加一个__ob__属性,引用 observer 实例,
  • 如果 value 是对象,就调用walk(value)方法遍历这个对象的属性,
  • 如果 value 是数组,就调用observeArray(value)方法,观测数组的每个元素。
  • 对于数组,需要拦截对数组的变异方法,当数组元素改变的时候,可以触发对应的依赖。protoAugmentcopyAugment就是做这样的事。 observeArray(value)方法会遍历数组中的每一项,执行observe(items[i]),对每一项进行观测。 walk(value)方法遍历这个对象的属性,对每个属性调用defineReactive(obj, keys[i]),设置其 getter 和 setter 方法。
/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  //value就是data,是对象或者数组
  constructor(value: any) {
    // 把data自身赋值到observer实例的value属性
    this.value = value;
    // 生成一个dep对象
    this.dep = new Dep();

    this.vmCount = 0;

    //给自身添加一个__ob__属性,引用observer实例
    def(value, "__ob__", this);

    // 如果value是数组,就观测数组的每个元素
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {
      // 如果value是对象,就遍历这个对象的属性
      this.walk(value);
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   * 遍历对象的属性
   */
  walk(obj: Object) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]);
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

接下来我们分析一下defineReactive方法,第一个参数是 data 对象,第二个参数是对象的某个 key。 这个方法做了如下几件事情。

  • new 一个 dep 实例,每个 key 都在 getter 和 setter 中引用了一个 dep。这个 dep 就是用来收集每个 key 的依赖的。

  • 获取属性原来的属性描述对象,若该属性为不可配置的,则不继续。获取属性已有的 getter 和 setter 方法,获取属性的值val

  • 观测属性的值observe(val),把观测过的值赋值给变量childOb

  • 调用Object.defineProperty(obj,key,{})定义这个属性的 getter 和 setter.

    • 先看 getter,第一句,const value = getter ? getter.call(obj) : val;这个属性若有原 getter,则执行原 getter 读取值,否则直接读取属性的值。getter 最后 return 了 value。中间的 if 语句块就是收集依赖的过程。
    // 如果有依赖,就收集依赖到这个属性的dep对象的subs里
    if (Dep.target) {
      dep.depend();
      // {
      //   person:[1,{name:2}]
      // }
      // 如果这个属性的值可观测(是对象或者数组才能被观测),就收集同样的依赖到这个属性值的observer实例的dep中
      if (childOb) {
        childOb.dep.depend();
        // 如果这个值是数组,且数组的项已被观测(是对象或者数组才能被观测),就收集同样的依赖到每个数组元素
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    • 再看 setter,前面两句对newVal做了边界和异常值NaN的判断,如果属性的新值和旧值相等或者新值和旧值都是 NaN,则不继续。接下来如果有自定义 setter 通过参数传入,则执行自定义的 setter。如果这个属性已有 setter,则执行已有 setter,否则将新值赋值给这个属性。
    if (setter) {
      setter.call(obj, newVal);
    } else {
      val = newVal;
    }
    
    1
    2
    3
    4
    5

    新值也要被观测,所以执行observe(newVal)。当值改变的时候,出发此属性收集的依赖。

    // 新值也要被观测
    childOb = !shallow && observe(newVal);
    // 触发此属性收集的依赖
    dep.notify();
    
    1
    2
    3
    4
/**
 * Define a reactive property on an Object.
 * 响应化一个属性
 */
export function defineReactive(
  obj: Object, // 对象
  key: string, // 对象的某个key
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 生成一个dep实例,每个key都在get和set中引用了一个dep
  const dep = new Dep();

  // 获取属性原来的属性描述对象,若该属性为不可配置的,则不继续
  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  // 获取属性已有的getter和setter方法
  const getter = property && property.get;
  const setter = property && property.set;

  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]; // 获取属性的值
  }

  // 观测属性的值
  let childOb = !shallow && observe(val);

  // 定义这个属性的get和set
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 读取这个属性值的时候执行get
    get: function reactiveGetter() {
      // 这个属性若有原getter,则执行原getter读取值,否则直接读取属性的值
      const value = getter ? getter.call(obj) : val;
      // 如果有依赖,就收集依赖到这个属性的dep对象的subs里
      if (Dep.target) {
        dep.depend();
        // {
        //   person:[1,{name:2}]
        // }
        // 如果这个属性的值可观测(是对象或者数组才能被观测),就收集同样的依赖到这个属性值的observer实例的dep中
        if (childOb) {
          childOb.dep.depend();
          // 如果这个值是数组,且数组的项已被观测(是对象或者数组才能被观测),就收集同样的依赖到每个数组元素
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      // 返回属性的值
      return value;
    },
    // 改变这个属性值的时候执行set
    set: function reactiveSetter(newVal) {
      // 获取属性的值
      const value = getter ? getter.call(obj) : val;

      /* eslint-disable no-self-compare */
      // 如果属性的新值和旧值相等或者新值和旧值都是NaN,则不继续
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      // 如果有自定义setter通过参数传入,则执行自定义的setter
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      // 如果这个属性已有setter,则执行已有setter,否则将新值赋值给这个属性
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 新值也要被观测
      childOb = !shallow && observe(newVal);
      // 触发此属性收集的依赖
      dep.notify();
    }
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

总结

vuedata 最后掏出 vue 官网上的一张图,这张图其实非常清晰,就是一个变化侦测的原理图。

getterwatcher 有一条线,上面写着收集依赖,意思是说 getter 里收集 watcher,也就是说当数据发生 get 动作时开始收集 watcher

setterwatcher 有一条线,写着 Notify 意思是说在 setter 中触发消息,也就是当数据发生 set 动作时,通知 watcher

WatcherComponentRenderFunction 有一条线,写着 Trigger re-render,意思就是用新的数据去执行ComponentRenderFunctionrender一个Virtual DOM Tree

render 到 getter 有一条线,写着Touch,意思就是在执行ComponentRenderFunction的时候,访问数据,执行getter

支付宝
微信