当前位置:   article > 正文

Vue 2.0源码分析之响应式数据生成的原理_vue 2.0 响应式数据的原理

vue 2.0 响应式数据的原理

概要

本文通过分析Vue 2.0 源码,探讨一下在Vue 2.0的初始化过程中,如何生成响应式数据。最后我们将关键的代码抽取出来,模拟出具体的实现过程。

代码调用关系

  1. 调用src\core\instance\index.js _init方法。该方法定义在src\core\instance\init.js中的initMixin方法中。
    在这里插入图片描述
  2. 调用src\core\instance\state.js中initState方法
    在这里插入图片描述
  3. 调用src\core\instance\state.js中的initData方法
    在这里插入图片描述
  4. 调用src\core\observer\index.js中的observe方法
    在这里插入图片描述
    所有的响应式数据均在observe方法中创建。

关键代码分析

生成响应式数据过程中都做了什么?

在使用Vue过程中,我们通常会将要绑定到页面HTML元素中的数据作为data方法的返回值。Vue实例会将这些数据转换为响应式数据,以支持其单项或双向的数据绑定。

observe方法

考虑到Vue使用者的业务数据结构可能非常复杂,例如对象中包含数组,数组中每项又是一个js对象,如下代码中的情况:

data(){
	return {
		stulist:[
		{id: 1, name: 'Tom'},
		{id: 2, name: 'Jack'}
	]};
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Observe方法通过递归调用的方式,为数据中的每个属性逐个生成getter和setter方法,为每个子对象逐一生成依赖收集中使用到的数据。

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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  1. 如果是Primitive值,例如数字,字符串等直接返回。
  2. 生成响应式数据后,用户数据的每个子对象包含一个Observer对象,key为__ob__。
  3. 由于生成响应式数据的过程是一个函数递归调用的过程,为了避免在复杂数据结构情况下重复生成Observer,所以增加当前子对象是否包含__ob__的检查,如果包含,则直接赋值。
  4. 如果当前对象是用户数据,并且不包含__ob__,则调用Observer的构造方法,为用户数据创建Observer对象。

Observer对象的构造方法

该构造方法主要用于区分对象和数组两种数据结构,每种数据结构对应不同的处理方法。

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)
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 当前用户数据中的子对象作为参数,传入Observer的构造器。
  2. 实例化依赖收集对象Dep。
  3. 调用def方法为当前子对象生成__ob__。
  4. 如果是数组调用protoAugment方法和observeArray方法
  5. 如果不是数组,调用walk方法。

walk方法/defineReactive方法

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 遍历当前子对象。
  2. 调用defineReactive,为每个属性生成响应式数据。
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()
    }
  })
}
  • 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
  1. 如果该属性存在,但是不可以删除,即不能通过delete进行删除并重新定义,则直接返回,不进行响应式处理。
  2. 获取对应属性的值。
  3. 递归调用observe方法,如果属性值为对象,则重新为其生成Observer,否则退出observe方法。
  4. 为该属性创建getter和setter,其他代码为依赖收集相关,请参看依赖收集的章节。
  5. 在setter方法中考虑到原有属性值可能被一个对象修改,所以要重新调用observe方法,生成 响应式数据。

protoAugment方法/observeArray方法

protoAugment方法用于修改数组对象的原型属性__proto__,对数组对象的七个方法进行重新定制,从而到达监控数组变化的需求。

function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
  • 1
  • 2
  • 3
  • 4
  • 5

传入的scr内容来自src\core\observer\array.js

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

  • 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
  1. 备份数组对象的原型。
  2. 基于备份的原型链,创建一个新的Array.prototype原型对象。
  3. Vue监控的数组操作包括:‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, ‘reverse’,其他任何数组操作,Vue对象都无法监控到。
  4. 通过defineProperty重新定义上述七个方法,每个key值对应一个既完成原有数组操作,又可以监控数组变化的新方法。
  5. 新的方法处理包括:
    1. 利用备份的数组原型链,调用原生数组操作方法,完成具体数组操作。
    2. 如果是新的元素插入到了数组中,调用observeArray方法,将新的数据转化成生成响应式数据。
    3. 返回数组操作结果。
 observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 遍历数组中的每个元素
  2. 调用observe方法为数组的每个元素生成响应式数据。

FAQ

数组内数据是否可以通过索引值进行修改?
不可以,只有通过’push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, 'reverse’这七个方法对数组的修改,Vue才能监控到。

通过splice方法修改数组,为什么从要忽略前两个参数, 通过args.slice(2)来取得新添加的元素?

splice方法包含两个required参数,第一个是原数组中要添加的新元素的位置,第二个是要添加多少个新元素,从第三个参起才是要添加的新元素。所以要slice方法的参数是2,即忽略前两个参数。

在defineReactive 方法中为每个属性定义getter和setter方式的时候,为什么要通过一个中间变量value或val来获取或设置属性值,不能直接通过obj[key]方式完成具体操作?

这样做会触发死循环,在getter中,如果通过obj[key]返回属性值,会再次触发obj的getter方法,从而形成死循环调用。setter方法同理。

代码模拟

模拟代码并不需要nodejs环境,可以直接在Chrome浏览器的Console中运行。

(function () {
    var uid = 0;
    function Vue(optioanl) {
        if (!(this instanceof Vue)) {
            console.error('Vue is constructor and should be called with new keyword');
        }
        const vm = this;
        const { isPlainObject } = Utils();
        const initData = function (optioanl) {
            let data = vm._data = typeof optioanl.data === 'function'
                ? optioanl.data.call(this) : {};
            if (!isPlainObject(data)) {
                data = {};
                console.error('data function should return an object.');
            }
            var keys = Object.keys(data);
            for (let key of keys) {
                proxy(vm, '_data', key);
            }
            const { observe } = Reactive();
            observe(data);
        };

        const proxy = function (target, sourceKey, key) {
            const handler = {
                get: function () {
                    return this[sourceKey][key];
                },
                set: function (val) {
                    this[sourceKey][key] = val;
                }
            }
            Object.defineProperty(target, key, handler);
        };
        initData(optioanl);
    }

    function Observer(target) {
        const { walk, observeArray } = Reactive();
        const { def } = Utils();
        this.dep = new Dep();
        this.value = target;
        this.observeArray = observeArray.bind(this);
        def(target, "__ob__", this);
        if (Array.isArray(target)) {
            target = setArrayProto(target);
            observeArray(target);
        } else {
            console.log(target);
            walk(target);
        }
    }

    function Dep() {
        this.id = uid++;
        this.notity = function(){
            console.log("Notify");
        };
        this.subs = [];
        this.addSub = function(sub){
            this.subs.push(sub);
        }.bind(this);
        this.depend = function(){
            if (window.__target != null){
                this.addSub(window.__target);
            }
        }.bind(this);
    }

    function setArrayProto(target) {
        const arrayProto = {};
        const { def } = Utils();
        const methods = ["splice", "push", "pop", "reverse", "sort", "shift", "unshift"];
        methods.forEach(method => {
            def(arrayProto, method, function (...args) {
                const protoMethod = Array.prototype[method];
                const ob = this.__ob__;
                const result = protoMethod.apply(this, args);
                let inserted;
                switch (method) {
                    case "push":
                    case "unshift":
                        inserted = args;
                        break;
                    case "splice":
                        inserted = args.slice(2);
                        break;
                    default:
                        break;
                }
                if (inserted) {
                    ob.observeArray(inserted);
                    ob.dep.notity();
                }
                return result;
            }, false);
        });
        target.__proto__ = arrayProto;
        return target;
    }

    function Reactive() {
        const { hasOwn, isObject } = Utils();
        const defineReactive = function (obj, key) {
            let value = obj[key];
            let childObj = observe(value);
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                configurable: true,
                enumerable: true,
                get: function () {
                    console.log(`Get ${key}'s value`);
                    return value;
                },
                set: function (newVal) {
                    if (value === newVal) {
                        return;
                    }
                    console.log(`${key}'s value is updated from ${value} to ${newVal}`);
                    value = newVal;
                    childObj = observe(newVal);
                    dep.notity();
                }
            });

        }
        const walk = function (target) {
            const keys = Object.keys(target);
            for (let key of keys) {
                defineReactive(target, key);
            }
        }
        const observe = function (obj) {
            if (!isObject(obj)) {
                return;
            }
            let ob;
            if (hasOwn(obj, "__ob__") && obj["__ob__"] instanceof Observer) {
                ob = obj["__ob__"];
            }
            ob = new Observer(obj);
            return ob;

        };
        const observeArray = function (target) {
            for (var i = 0, l = target.length; i < l; ++i) {
                observe(target[i]);
            }
        }
        return { defineReactive, walk, observe, observeArray };
    }

    function Utils() {
        const hasOwn = function (target, key) {
            const hasOwnProperty = Object.prototype.hasOwnProperty;
            return hasOwnProperty.call(target, key);
        }
        const isObject = function (target) {
            if (target != null && typeof target === "object") {
                return true;
            }
            return false;
        }
        const isPlainObject = function (obj) {
            return (Object.prototype.toString.call(obj) === "[object Object]");
        }
        const def = function (target, key, val, enumerable) {
            Object.defineProperty(target, key, {
                value: val,
                configurable: true,
                enumerable: !!enumerable,
                writable: true
            })
        }
        return { hasOwn, isObject, isPlainObject, def };
    }
    var v = new Vue({
        data() {
            return {
                name: "ly",
                age: 21,
                address: {
                    district: "ABC",
                    street: "DEF"

                },
                cards: [
                    { id: 1, title: "credit card" },
                    { id: 2, title: "visa card" },
                ]
            }
        }
    });

})();

  • 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
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196

附录

isObject方法

src\shared\util.js

export function isObject (obj: mixed): boolean %checks {
  return obj !== null && typeof obj === 'object'
}

  • 1
  • 2
  • 3
  • 4

def方法

src\core\util\lang.js

function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/248233
推荐阅读
相关标签
  

闽ICP备14008679号