• 1.6 常规选项的合并
    • 1.6.1 el的合并
    • 1.6.2 data合并

    1.6 常规选项的合并

    1.6.1 el的合并

    el提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标,因此它只在创建Vue实例才存在,在子类或者子组件中无法定义el选项,因此el的合并策略是在保证选项只存在于根的Vue实例的情形下使用默认策略进行合并。

    1. strats.el = function (parent, child, vm, key) {
    2. if (!vm) { // 只允许vue实例才拥有el属性,其他子类构造器不允许有el属性
    3. warn(
    4. "option \"" + key + "\" can only be used during instance " +
    5. 'creation with the `new` keyword.'
    6. );
    7. }
    8. // 默认策略
    9. return defaultStrat(parent, child)
    10. };

    1.6.2 data合并

    常规选项的重点部分是在于data的合并,读完这部分源码,可能可以解开你心中的一个疑惑,为什么datavue创建实例时传递的是一个对象,而在组件内部定义时只能传递一个函数。

    1. // data的合并
    2. strats.data = function (parentVal, childVal, vm) {
    3. // vm代表是否为Vue创建的实例,否则是子父类的关系
    4. if (!vm) {
    5. if (childVal && typeof childVal !== 'function') { // 必须保证子类的data类型是一个函数而不是一个对象
    6. warn('The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.',vm);
    7. return parentVal
    8. }
    9. return mergeDataOrFn(parentVal, childVal)
    10. }
    11. return mergeDataOrFn(parentVal, childVal, vm); // vue实例时需要传递vm作为函数的第三个参数
    12. };

    data策略最终调用的mergeDataOrFn方法,区别在于当前vm是否是实例,或者是单纯的子父类的关系。如果是子父类的关系,需要对data选项进行规范校验,保证它的类型是一个函数而不是对象。

    1. function mergeDataOrFn ( parentVal, childVal, vm ) {
    2. // 子父类
    3. if (!vm) {
    4. if (!childVal) { // 子类不存在data选项,则合并结果为父类data选项
    5. return parentVal
    6. }
    7. if (!parentVal) { // 父类不存在data选项,则合并结果为子类data选项
    8. return childVal
    9. }
    10. return function mergedDataFn () { // data选项在父类和子类同时存在的情况下返回的是一个函数
    11. // 子类实例和父类实例,分别将子类和父类实例中data函数执行后返回的对象传递给mergeData函数做数据合并
    12. return mergeData(
    13. typeof childVal === 'function' ? childVal.call(this, this) : childVal,
    14. typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
    15. )
    16. }
    17. } else {
    18. // Vue实例
    19. // vue构造函数实例对象
    20. return function mergedInstanceDataFn () {
    21. var instanceData = typeof childVal === 'function'
    22. ? childVal.call(vm, vm)
    23. : childVal;
    24. var defaultData = typeof parentVal === 'function'
    25. ? parentVal.call(vm, vm)
    26. : parentVal;
    27. if (instanceData) {
    28. // 当实例中传递data选项时,将实例的data对象和Vm构造函数上的data属性选项合并
    29. return mergeData(instanceData, defaultData)
    30. } else {
    31. // 当实例中不传递data时,默认返回Vm构造函数上的data属性选项
    32. return defaultData
    33. }
    34. }
    35. }
    36. }

    从源码的实现看,data的合并不是简单的将两个数据对象进行合并,而是直接返回一个mergedDataFn或者mergedInstanceDataFn函数,而真正合并的时机是在后续初始化数据响应式系统的环节进行的,初始化数据响应式系统的第一步就是拿到合并后的数据,也就是执行mergeData逻辑。(关于响应式系统的构建请移步后面的章节)

    1. function mergeData (to, from) {
    2. if (!from) { return to }
    3. var key, toVal, fromVal;
    4. // Reflect.ownKeys可以拿到Symbol属性
    5. var keys = hasSymbol
    6. ? Reflect.ownKeys(from)
    7. : Object.keys(from);
    8. for (var i = 0; i < keys.length; i++) {
    9. key = keys[i];
    10. toVal = to[key];
    11. fromVal = from[key];
    12. if (!hasOwn(to, key)) {
    13. // 子的数据父没有,则将新增的数据加入响应式系统中。
    14. set(to, key, fromVal);
    15. } else if (
    16. toVal !== fromVal &&
    17. isPlainObject(toVal) &&
    18. isPlainObject(fromVal)
    19. ) {
    20. // 处理深层对象,当合并的数据为多层嵌套对象时,需要递归调用mergeData进行比较合并
    21. mergeData(toVal, fromVal);
    22. }
    23. }
    24. return to
    25. }

    mergeData方法的两个参数是父data选项和子data选项的结果,也就是两个data对象,从源码上看数据合并的原则是,将父类的数据整合到子类的数据选项中, 如若父类数据和子类数据冲突时,保留子类数据。如果对象有深层嵌套,则需要递归调用mergeData进行数据合并。

    最后回过头来思考一个问题,为什么Vue组件的data是一个函数,而不是一个对象呢?我觉得可以这样解释:组件设计的目的是为了复用,每次通过函数创建相当于在一个独立的内存空间中生成一个data的副本,这样每个组件之间的数据不会互相影响。