前言

本文是vue2.x源码分析的第三篇,主要讲解选项合并过程mergeOptions!

先看调用形式

  1. vm.$options = mergeOptions(
  2. resolveConstructorOptions(vm.constructor),
  3. options || {},
  4. vm
  5. );

1、执行resolveConstructorOptions(vm.constructor)

  1. function resolveConstructorOptions (Ctor) {
  2. var options = Ctor.options; //Ctor即Vue$3,Vue$3.options是在第一节中通过extend函数赋值的
  3. if (Ctor.super) { //这里由于没有super属性,故直接返回了options
  4. var superOptions = resolveConstructorOptions(Ctor.super);
  5. var cachedSuperOptions = Ctor.superOptions;
  6. if (superOptions !== cachedSuperOptions) {
  7. // super option changed,
  8. // need to resolve new options.
  9. Ctor.superOptions = superOptions;
  10. // check if there are any late-modified/attached options (#4976)
  11. var modifiedOptions = resolveModifiedOptions(Ctor);
  12. // update base extend options
  13. if (modifiedOptions) {
  14. extend(Ctor.extendOptions, modifiedOptions);
  15. }
  16. options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
  17. if (options.name) {
  18. options.components[options.name] = Ctor;
  19. }
  20. }
  21. }
  22. return options
  23. }

返回的options长这个样子:

  1. options = {
  2. components: {
  3. KeepAlive,
  4. Transition,
  5. TransitionGroup
  6. },
  7. directives: {
  8. model,
  9. show
  10. },
  11. filters: {},
  12. _base: Vue
  13. }

2、执行mergeOptions (parent,child,vm)

parent即上述返回的options,child即new Vue(options)传入的options

  1. function mergeOptions (parent,child,vm) {
  2. {
  3. //检查传入的components选项的名字是否符合规定(非内置标签)
  4. checkComponents(child);
  5. }
  6. //将传入的child.props转换为驼峰式结构的对象表达形式.props能使用数组和对象语法,
  7. 但在内部都会重新遍历封装到一个新对象中。
  8. normalizeProps(child);
  9. //directives使用对象语法,对象中属性的值只能为函数,该操作会将函数绑定到指令的bind和update函数上
  10. normalizeDirectives(child);
  11. //若存在extends,则将其内容合并到父对象parent中保存,最后再和自身child合并,extends最好是对象语法
  12. var extendsFrom = child.extends; //暂不清楚作用,记为Unknown3.1
  13. if (extendsFrom) {
  14. parent = typeof extendsFrom === 'function'
  15. ? mergeOptions(parent, extendsFrom.options, vm)
  16. : mergeOptions(parent, extendsFrom, vm);
  17. }
  18. //若存在mixins,则将其内容合并到父对象parent中保存,最后再和自身child合并,mixins只能是数组语法,数组中元素可以是对象
  19. if (child.mixins) {
  20. for (var i = 0, l = child.mixins.length; i < l; i++) {
  21. var mixin = child.mixins[i];
  22. if (mixin.prototype instanceof Vue$3) {
  23. mixin = mixin.options;
  24. }
  25. parent = mergeOptions(parent, mixin, vm);
  26. }
  27. }
  28. //初始化一个对象,用于存储parent和child合并后的内容,并作为mergeOptions函数的结果返回
  29. var options = {};
  30. var key;
  31. for (key in parent) {
  32. mergeField(key);
  33. }
  34. for (key in child) {
  35. if (!hasOwn(parent, key)) {
  36. mergeField(key);
  37. }
  38. }
  39. //使用策略对象对parent和child进行合并
  40. function mergeField (key) {
  41. var strat = strats[key] || defaultStrat;
  42. options[key] = strat(parent[key], child[key], vm, key);
  43. }
  44. return options
  45. }

2.1、分析checkComponents(child)

  1. function checkComponents (options) {
  2. for (var key in options.components) {//对象遍历形式,故components选项只能用对象,不用数组
  3. var lower = key.toLowerCase();
  4. //组件名字不能是内置标签(slot,component)和保留标签(html标签和svg标签),用了闭包实现
  5. if (isBuiltInTag(lower) || config.isReservedTag(lower)) {
  6. warn(
  7. 'Do not use built-in or reserved HTML elements as component ' +
  8. 'id: ' + key
  9. );
  10. }
  11. }
  12. }

2.2、分析normalizeProps(child);

  1. function normalizeProps (options) {
  2. var props = options.props;
  3. if (!props) { return }
  4. var res = {};
  5. var i, val, name;
  6. if (Array.isArray(props)) { //props是数组
  7. i = props.length;
  8. while (i--) {
  9. val = props[i];
  10. if (typeof val === 'string') {
  11. name = camelize(val);//若val是*-i*的形式,则将其转为*I*,如v-header会被转为vHeader并被缓存
  12. res[name] = { type: null }; //每个val的值设为含有type属性的对象
  13. } else {
  14. warn('props must be strings when using array syntax.');
  15. }
  16. }
  17. } else if (isPlainObject(props)) {//props是对象
  18. for (var key in props) {
  19. val = props[key];
  20. name = camelize(key);
  21. res[name] = isPlainObject(val)//若用对象语法,则每个key的value对象最好包含type属性
  22. ? val
  23. : { type: val };
  24. }
  25. }
  26. options.props = res;
  27. }
  28. // 调用前:options.props=['header-value'];
  29. // 调用后:options.props={
  30. // 'headerValue':{type:null}
  31. // }

2.3、分析normalizeDirectives(child)

  1. function normalizeDirectives (options) {
  2. var dirs = options.directives;
  3. if (dirs) {
  4. for (var key in dirs) { //数组也可以用for in遍历,故directives选项能用对象和数组语法,当只关心数组的value而不关心key时可以用for in遍历
  5. var def = dirs[key];
  6. if (typeof def === 'function') {
  7. dirs[key] = { bind: def, update: def };
  8. }
  9. }
  10. }
  11. }
  12. // 调用前:options.directives={
  13. // v-focus:function some(){}
  14. // };
  15. // 调用后:options.directives={
  16. // 'v-focus':{
  17. // bind:some,
  18. // update:some
  19. // }
  20. // }

2.4、分析extends和mixins

  二者作用都是进一步丰富parent选项,其本身是调用mergeOptions,这里不做分析,注意mixins只能用数组语法

2.5、分析合并策略对象strats

  • 10个生命周期函数—mixins中同名属性用数组保存,mixins中的优先级高,先调用,
    合并后类似这样:
    1. beforeCreate:[mixins中的beforeCreatechild中的beforeCreate]
  • 3个assets(directives、components、filters)—继承策略,以parent中同名属性的值为原型对象,以child中同名属性的值做实例属性,指令合并后类似这样:
    1. directives:{'v-focus':obj1,__proto__:{model:obj2,show:obj3}}
  • el与propsData、也是用defaultStrat函数,只是会先判断vm实例是否存在

  • data 返回一个函数mergedInstanceDataFn

  • watch 同生命周期函数的合并策略

  • props与methods与computed 覆盖策略,mixins中的同名属性会被child覆盖
    props合并后类似这样:

    1. props:[child中的属性]
  • 其余选项的合并策略采用defaultStrat函数

3、小结

  • 下一步分析内容
    ☐ 主要执行过程-2(实例的初始化过程)
    ☐ 主要执行过程-3(依赖收集、组件挂载等过程)
  • 遗留问题解决情况
    ☐ Unknown3.1