vue原理分析(七)研究new Vue()中的mergeOptions

今天我们来分析使用new Vue()中的mergeOptions

在Vue.prototype._init中有这么一段

复制代码
if (options && options._isComponent) {

    // optimize internal component instantiation

    // since dynamic options merging is pretty slow, and none of the

    // internal component options needs special treatment.

    initInternalComponent(vm, options);

}

else {

    vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);

}

这里用了mergeOptions()

找到这里用了mergeOptions代码f

复制代码
unction mergeOptions(parent, child, vm) {

  {

      checkComponents(child);

  }

  if (isFunction(child)) {

      // @ts-expect-error

      child = child.options;

  }

  normalizeProps(child, vm);

  normalizeInject(child, vm);

  normalizeDirectives$1(child);

  // Apply extends and mixins on the child options,

  // but only if it is a raw options object that isn't

  // the result of another mergeOptions call.

  // Only merged options has the _base property.

  if (!child._base) {

      if (child.extends) {

          parent = mergeOptions(parent, child.extends, vm);

      }

      if (child.mixins) {

          for (let i = 0, l = child.mixins.length; i < l; i++) {

              parent = mergeOptions(parent, child.mixins[i], vm);

          }

      }

  }

  const options = {};

  let key;

  for (key in parent) {

      mergeField(key);

  }

  for (key in child) {

      if (!hasOwn(parent, key)) {

          mergeField(key);

      }

  }

  function mergeField(key) {

      const strat = strats[key] || defaultStrat;

      options[key] = strat(parent[key], child[key], vm, key);

  }

  return options;

}

接下来,我们逐步分析代码

1. checkComponents检查components 中的组件名是否规范

复制代码
function mergeOptions(parent, child, vm) {

  {

      checkComponents(child);

  }

  ......

}

我们去找下checkComponents代码

复制代码
function checkComponents(options) {

    for (const key in options.components) {

        validateComponentName(key);

    }

}

在checkComponents中,用for in 循环options.components,调用validateComponentName

复制代码
function validateComponentName(name) {

    if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {

        warn$2('Invalid component name: "' +

            name +

            '". Component names ' +

            'should conform to valid custom element name in html5 specification.');

    }

    if (isBuiltInTag(name) || config.isReservedTag(name)) {

        warn$2('Do not use built-in or reserved HTML elements as component ' +

            'id: ' +

            name);

    }

}

如果组件名称未通过正则判断,则会有警告提示

2. 对入参child进行类型判断

复制代码
function mergeOptions(parent, child, vm) {

  ......

  if (isFunction(child)) {

      // @ts-expect-error

      child = child.options;

  }

  ......

}

类型判断,如果child是个方法,则child = child.options;

3. 对props、inject、directives 进行标准化

复制代码
unction mergeOptions(parent, child, vm) {

  ......

  normalizeProps(child, vm);

  normalizeInject(child, vm);

  normalizeDirectives$1(child);

  ......

}

这三个函数所做的事情很简单,就是将 options 中配置的 props、inject、directives 进行标准化

举个例子

复制代码
props: ['age']

标准化下为

复制代码
props: {

  age: {

    type: Number,

    default: 0,

    required: true,

    validator: function (value) {

      return value >= 0

    }

  }

}

inject: ['foo']

标准化下为

复制代码
inject: {

  foo: {

    from: 'bar',

    default: () => [1, 2, 3]

  }

}

directives 的函数简写形式转换成对象形式

复制代码
directives: {

  'color-swatch': function (el, binding) {

    el.style.backgroundColor = binding.value

  }

}

标准化位

复制代码
directives: {

  'color-swatch': {

    bind: function (el, binding) {

      el.style.backgroundColor = binding.value

    },

    update: function (el, binding) {

      el.style.backgroundColor = binding.value

    }

  }

}
  1. extends 和 mixins 选项的处理

    function mergeOptions(parent, child, vm) {

    ......

    //在子选项上应用extends和mixins;

    //但只有当它是一个原始的选项对象

    //另一个mergeOptions调用的结果。

    //只有合并选项才有_base属性。

    // 注意,这里是个递归调用!!!

    if (!child._base) {

    复制代码
       // 配置选项中可以使用 extends 配置项,如果使用了该配置项的话,底层则递归调用 mergeOptions 方法,
    
       // 对 parent 和 extendsFrom 中的配置项进行合并
    
       if (child.extends) {
    
           parent = mergeOptions(parent, child.extends, vm);
    
       }
    
       // 配置选项中可以使用 mixins 配置项,如果使用了该配置项的话,底层则递归调用 mergeOptions 方法,
    
       // 对 parent 和 child.mixins[i] 中的配置项进行合并
    
       if (child.mixins) {
    
           for (let i = 0, l = child.mixins.length; i < l; i++) {
    
               parent = mergeOptions(parent, child.mixins[i], vm);
    
           }
    
       }

    }

    ......

    }

  2. 开始真正的合并处理

    function mergeOptions(parent, child, vm) {

    ......

    const options = {};

    let key;

    for (key in parent) {

    复制代码
       mergeField(key);

    }

    for (key in child) {

    复制代码
       // 如果当前遍历的 key 在 parent 中不存在的话,再执行 mergeField(key)
    
       if (!hasOwn(parent, key)) {
    
           mergeField(key);
    
       }

    }

    // 用于合并某一个 key 的方法

    function mergeField(key) {

    复制代码
       // strats 是指合并策略集
    
       // strat 是指特定选项的合并策略,是一个函数
    
       const strat = strats[key] || defaultStrat;
    
       // 使用这个合并策略对 parent 和 child 中指定的 key 进行合并
    
       options[key] = strat(parent[key], child[key], vm, key);

    }

    return options;

    }

首先创建一个 options 对象,这个对象就是最终返回的合并对象。

接下来遍历 parent options 对象,以遍历到的 key 作为参数执行 mergeField 方法,这样的话,parent options 对象中的所有 key 都已经处理到 options 对象中了

然后遍历 child options 对象,这一遍遍历是为了处理 child options 对象中独有的 key,内部的处理也是以 key 作为参数执行 mergeField 方法。

这里再研究下 const strat = strats[key] || defaultStrat;

该方法的作用是:用于合并 parent options 和 child options 对象中某一个 key。

在 Vue 中,不同的配置项有不同的合并策略,这些合并策略都存储在 strats 对象中,strats 对象的数据结构如下所示:

复制代码
strats = {

  data: function(){},

  watch: function(){},

  props: function(){},

  methods: function(){},

  created: function(){},

  mounted: function(){},

  ......

}
相关推荐
我是伪码农3 小时前
Vue 1.23
前端·javascript·vue.js
wqwqweee3 小时前
Flutter for OpenHarmony 看书管理记录App实战:搜索功能实现
开发语言·javascript·python·flutter·harmonyos
HIT_Weston4 小时前
107、【Ubuntu】【Hugo】搭建私人博客:模糊搜索 Fuse.js(三)
linux·javascript·ubuntu
henujolly8 小时前
ethers.js读取合约信息
开发语言·javascript·区块链
毕设源码-郭学长8 小时前
【开题答辩全过程】以 基于Web的高校课程目标达成度系统设计与实现为例,包含答辩的问题和答案
前端
wuhen_n8 小时前
高阶函数与泛型函数的类型体操
前端·javascript·typescript
POLITE39 小时前
Leetcode 437. 路径总和 III (Day 16)JavaScript
javascript·算法·leetcode
難釋懷9 小时前
解决状态登录刷新问题
java·开发语言·javascript
ヤ鬧鬧o.9 小时前
多彩背景切换演示
前端·css·html·html5
一起养小猫10 小时前
Flutter实战:从零实现俄罗斯方块(三)交互控制与事件处理
javascript·flutter·交互