响应式系统

先阅读一遍官方文档的这一节 https://cn.vuejs.org/v2/guide/reactivity.html

Object.defineProperty

首先了解Object.defineProperty这个 API

Object.defineProperty(obj, prop, descriptor)

configurable enumerable value writable get set
改变/删除,默认false 默认false 默认undefined 可写
默认false
默认undefined 默认undefined
数据描述符
存取描述符
操作方法 判断方法 configurable writable enumerable
不可扩展,不能加属性 Object.preventExtensions(obj) Object.isExtensible(obj) - - -
密封,不能加/删 属性 Object.seal(obj) Object.isSealed(obj)
冻结,不能加/删/改 属性 Object.freeze(obj) Object.isFrozen(obj)
  • Object.isFrozen(obj)=ture 则 Object.isSealed(obj)=true, Object.isExtensible(obj) =false
  • Object.isSealed(obj)= true 则 Object.isExtensible(obj) =false

    无论是不可扩展,密封,还是冻结,都是浅层控制的

通过上面三种方法都可以控制 Vue是否对数据进行响应式数据拦截

Vue 中对数据的劫持:

  1. function defineReactive (obj,key,val) {
  2. var dep = new Dep();
  3. var property = Object.getOwnPropertyDescriptor(obj, key);
  4. if (property && property.configurable === false) {
  5. return
  6. }
  7. // cater for pre-defined getter/setters
  8. var getter = property && property.get;
  9. var setter = property && property.set;
  10. if ((!getter || setter) && arguments.length === 2) {
  11. val = obj[key];
  12. }
  13. var childOb = observe(val);
  14. Object.defineProperty(obj, key, {
  15. enumerable: true,
  16. configurable: true,
  17. get: function reactiveGetter () {
  18. var value = getter ? getter.call(obj) : val;
  19. if (Dep.target) {
  20. dep.depend();
  21. if (childOb) {
  22. childOb.dep.depend();
  23. if (Array.isArray(value)) {
  24. dependArray(value);
  25. }
  26. }
  27. }
  28. return value
  29. },
  30. set: function reactiveSetter (newVal) {
  31. var value = getter ? getter.call(obj) : val;
  32. /* eslint-disable no-self-compare */
  33. if (newVal === value || (newVal !== newVal && value !== value)) {
  34. return
  35. }
  36. // #7981: for accessor properties without setter
  37. if (getter && !setter) { return }
  38. if (setter) {
  39. setter.call(obj, newVal);
  40. } else {
  41. val = newVal;
  42. }
  43. childOb = !shallow && observe(newVal);
  44. dep.notify();
  45. }
  46. });
  47. }
  1. function observe (value) {
  2. if (!isObject(value) || value instanceof VNode) {
  3. return
  4. }
  5. var ob;
  6. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  7. ob = value.__ob__;
  8. } else if (
  9. (Array.isArray(value) || isPlainObject(value)) &&
  10. Object.isExtensible(value) &&
  11. !value._isVue
  12. ) {
  13. ob = new Observer(value);
  14. }
  15. return ob
  16. }
  1. var Observer = function Observer (value) {
  2. this.value = value;
  3. this.dep = new Dep();
  4. def(value, '__ob__', this);
  5. if (Array.isArray(value)) {
  6. if (hasProto) {
  7. protoAugment(value, arrayMethods);
  8. } else {
  9. copyAugment(value, arrayMethods, arrayKeys);
  10. }
  11. this.observeArray(value);
  12. } else {
  13. this.walk(value);
  14. }
  15. };
  16. Observer.prototype.walk = function walk (obj) {
  17. var keys = Object.keys(obj);
  18. for (var i = 0; i < keys.length; i++) {
  19. defineReactive(obj, keys[i]);
  20. }
  21. };
  22. Observer.prototype.observeArray = function observeArray (items) {
  23. for (var i = 0, l = items.length; i < l; i++) {
  24. observe(items[i]);
  25. }
  26. };

存放 Dep 实例的两个位置

  • 在defineReactive 函数的闭包中(getter/setter 中使用了dep实例)
  • 在Observer实例中(ob实例就是存在 value.__ob__上的)
  1. 第一种情况:dep实例是和对象的某个key的getter/setter 绑定的
  2. 第二种情况:dep实例是和对象某个key的value(object类型) 绑定的,有两个用处:
    1. 数组不是通过defineReactive处理的,是通过拦截数组的原型方法,去触发通知,所以dep只能存在数组本身上面。
    2. 给 data 中的对象新增属性 Vue 是检测不到变化的,需要使用Vue.$set,由于属性是刚加的没法触发闭包中的dep通知,那么需要这个 value 本身存在一个dep收集依赖,对进行 set 的时候会触发通知。

Watcher

  • RenderWatcher

    1. new Watcher(vm, updateComponent, noop/* 表示空函数 */, {
    2. before: function before () {
    3. if (vm._isMounted && !vm._isDestroyed) {
    4. callHook(vm, 'beforeUpdate');
    5. }
    6. }
    7. }, true /* isRenderWatcher */);
  • user Watcher

    1. Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
    2. options.user = true; // 标记为用户watcher
    3. // 核心就是创建个watcher
    4. const watcher = new Watcher(this, exprOrFn, cb, options);
    5. if(options.immediate){
    6. cb.call(vm,watcher.value)
    7. }
    8. }
  • computed watcher

    1. function initComputed(vm, computed) {
    2. // 存放计算属性的watcher
    3. const watchers = vm._computedWatchers = {};
    4. for (const key in computed) {
    5. const userDef = computed[key];
    6. // 获取get方法
    7. const getter = typeof userDef === 'function' ? userDef : userDef.get;
    8. // 创建计算属性watcher
    9. watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
    10. // 将 computed 属性的 getter/setter 挂载到 vm 上
    11. defineComputed(vm, key, userDef)
    12. }
    13. }
    14. function createComputedGetter (key) {
    15. return function computedGetter () {
    16. var watcher = this._computedWatchers && this._computedWatchers[key];
    17. if (watcher) {
    18. if (watcher.dirty) {
    19. watcher.evaluate(); //get一次
    20. }
    21. // 如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher
    22. if (Dep.target) {
    23. watcher.depend();
    24. }
    25. return watcher.value
    26. }
    27. }
    28. }
  • 每个计算属性都是一个watcher,且 lazy=true,初始化watcher时不会立即调用get方法

  • dirty 用来缓存,一开始为true,get时会计算出值,并置为 false;如果依赖的数据变化,则dirty置为true,get 时会重新触发计算。
  • 如果计算属性在模板中使用,就让计算属性 watcher 中依赖的数据也记录渲染 watcher