Javascript

Vue源码学习之实例初始化选项初合并 & 选项规范化校验(2000字)

本文主要是介绍Vue源码学习之实例初始化选项初合并 & 选项规范化校验(2000字),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Vue实例初始化选项初合并 & 选项规范化校验
(本文2005字,大概会花费25-35min)

1.Vue实例初始化选项mix初合并(详细合并后续文章会提及,如果需要查看合并策略,请绕行文章——)

合并之后会挂载到实例的$options属性中,可以在实例中通过this.$options访问

  var uid$3 = 0;

  function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid 记录实例化多少个vue对象
      vm._uid = uid$3++;
      ...
      // a flag to avoid this being observed
      vm._isVue = true;
      // merge options - 选项合并,将合并值赋给实例的$options属性
      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 {
        // Vue实例不是组件,会执行mergeOptions方法
        // 将两个选项对象合并为一个新对象。
        // 用于实例化和继承的核心实用程序。
        vm.$options = mergeOptions(
          // 返回Vue构造函数自身的配置项
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
        );
      }
      ...
    };
  }

通过研究resolveConstructorOptions函数,发现:

function resolveConstructorOptions (Ctor) {
    var options = Ctor.options;
    // 有super属性,说明Vue.extend()构建的子类(下文会介绍)
    if (Ctor.super) {
      var superOptions = resolveConstructorOptions(Ctor.super);
      // Vue构造函数上的options,如directives,filters,...
      var cachedSuperOptions = Ctor.superOptions;
      if (superOptions !== cachedSuperOptions) {
        // super option changed,
        // need to resolve new options.
        Ctor.superOptions = superOptions;
        // check if there are any late-modified/attached options (#4976)
        var modifiedOptions = resolveModifiedOptions(Ctor);
        // update base extend options
        if (modifiedOptions) {
          // 对象合并
          extend(Ctor.extendOptions, modifiedOptions);
        }
        // 将两个选项对象合并为一个新对象。
        options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
        if (options.name) {
          options.components[options.name] = Ctor;
        }
      }
    }
    return options
  }

注意:有super属性,说明Vue.extend()构建的子类,会为Ctor添加一个super属性,指向其父类构造器

Vue.extend = function (extendOptions: Object): Function {
  ...
  Sub['super'] = Super
  ...
}

其中,Ctor是基础Vue构造器,通过打印superOtions,结构为下:

{
    "components": {},
    "directives": {},
    "filters": {},
    "beforeCreate": [],
    "beforeMount": [],
    "beforeDestroy": [],
    "destroyed": []
}

我们看mergeOptions的部分:

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
function mergeOptions (
  parent,
  child,
  vm
) {
 {	 // components校验
     checkComponents(child);
 }

  if (typeof child === 'function') {
    child = child.options;
  }
  // 规范化 props,inject,directives的校验和规范化
  normalizeProps(child, vm);
  normalizeInject(child, vm);
  normalizeDirectives(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) {
    // 当传入的options里有mixin/extends属性时,再次调用mergeOptions方法合并mixins和extends里的内容到实例的构造函数options上(即parent options)比如下文 例1的情况
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm);
    }
    if (child.mixins) {
      for (var i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm);
      }
    }
  }
  
      
  // 核心部分
  var options = {};
  var key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField (key) {
    // 拿到各个选择指定的选项配置,如果没有则用默认的配置
    var strat = strats[key] || defaultStrat;
    // 执行合并策略
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options
}

例1:会把传入的mounted, created钩子处理函数,还有methods方法提出来和parent options做合并处理。

 const childComponent = Vue.component('child', {
      ...
      mixins: [myMixin],
      extends: myComponent
      ...
 })
 const myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('hello from mixin')
      }
    }
  }
 const myComponent = {
      mounted: function () {
        this.yes()
      },
      methods: {
        yes: function () {
          console.log('yes from mixin')
        }
     }
  }

为了更近异步理解,引用某大佬的合并流程图说明:

**选项合并中存在不清楚使用者传递了哪些配置选项,是否符合规范达到合并配置的要求。因此每个选项的书写规则需要严格限定,原则上不允许用户脱离规则外来传递选项。**所以在合并选项之前,很大的一部分工作是对选项的校验。其中components,prop,inject,directive等都是检验的重点。

2.选项校验–components(checkComponents)

代码:

 /**
   * unicode letters used for parsing html tags, component names and property paths.
   * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
   * skipping \u10000-\uEFFFF due to it freezing up PhantomJS
   */
  // 用于解析 html 标签、组件名称和属性路径的 unicode 字母。使用 https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname,跳过 \u10000-\uEFFFF 因为它冻结了 PhantomJS
 // ps:这里的PhantomJS是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。亦称为“无头浏览器”
  var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; 


  /**
   * Validate component names
   */
  function checkComponents (options) {
    for (var key in options.components) {
      validateComponentName(key);
    }
  }

  function validateComponentName (name) {
    if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) {
      warn(
        'Invalid component name: "' + name + '". Component names ' +
        'should conform to valid custom element name in html5 specification.'
      );
    }
    // 主要是防止自定义组件使用 HTML 内置,如svg等标签,及Vue自身定义的组件名,如slot, component,
    if (isBuiltInTag(name) || config.isReservedTag(name)) {
      warn(
        'Do not use built-in or reserved HTML elements as component ' +
        'id: ' + name
      );
    }
  }

3.选项校验–props(normalizeProps)

引用Vue2.x官方原话:

  • 类型Array<string> | Object

  • 详细

    props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。

    来自:https://cn.vuejs.org/v2/api/#props

代码:

 /**
   * Ensure all props option syntax are normalized into the
   * Object-based format.
   */
  function normalizeProps (options, vm) {
    var props = options.props;
    if (!props) { return }
    var res = {};
    var i, val, name;
    // Array<string> | Object
    if (Array.isArray(props)) { // 数组
      i = props.length;
      while (i--) {
        val = props[i];
        // 如果数组的某一项为字符串的话
        if (typeof val === 'string') {
          // 名字会被规范处理(驼峰式)
          name = camelize(val);
          // res添加属性,type默认为null
          res[name] = { type: null };
          // eg:如果传入['first-prop','second-prop'],会被转化为: {firstProp: {type: null}, secondProp: {type: null}}
        } else {
          warn('props must be strings when using array syntax.');
        }
      }
    } else if (isPlainObject(props)) { // 对象
      for (var key in props) {
        // val赋值
        val = props[key];
        // 驼峰名字格式化
        name = camelize(key);
        /* 
        *  传入 props: { name: String } =>  { name: { type: String }}
        *  传入props: {  name: { type: String,required: true} } =>
        *	         { name: { type: String, reuired: true } }
        */
        res[name] = isPlainObject(val)
          ? val
          : { type: val };
      }
    } else {
      // 非数组,非对象,传递非法
      warn(
        "Invalid value for option \"props\": expected an Array or an Object, " +
        "but got " + (toRawType(props)) + ".",
        vm
      );
    }
    
    options.props = res;
  }

综上,通过此规范化校验,props Array<string> | Object经过名称驼峰规范化等一系列处理,最终都会被转换对象的形式,尤大佬yyds = = !

4.选项校验–inject (normalizeInject

引用Vue2.x官方原话:

provide/inject依赖注入

  • 类型

    • provideObject | () => Object
    • injectArray<string> | { [key: string]: string | Symbol | Object }
  • 详细

    provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property

    inject选项应该是

    • 一个字符串数组,或
    • 一个对象,对象的 key 是本地的绑定名,value 是:
      • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
      • 一个对象,该对象的:
        • from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
        • default property 是降级情况下使用的 value

    来自:https://cn.vuejs.org/v2/api/#provide-inject

    如果不太了解provide/inject的用法,可访问https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5

  /**
   * Normalize all injections into Object-based format
   */
  function normalizeInject (options, vm) {
    // 缓存inject对象,做中间变量
    var inject = options.inject;
    if (!inject) { return }
    // 将其置空,且保证normalized与options.inject指向同一个对象
    var normalized = options.inject = {};
    if (Array.isArray(inject)) {
      for (var i = 0; i < inject.length; i++) {
        normalized[inject[i]] = { from: inject[i] };
      }
    } else if (isPlainObject(inject)) {
      for (var key in inject) {
        var val = inject[key];
        normalized[key] = isPlainObject(val)
          ? extend({ from: key }, val)
          : { from: val };
      }
    } else {
      warn(
        "Invalid value for option \"inject\": expected an Array or an Object, " +
        "but got " + (toRawType(inject)) + ".",
        vm
      );
    }
  }

综上,通过此规范化校验,inject最终被转化为对象的形式与props类似

5.选项校验–directives(normalizeDirectives

类型: Object

代码:

   /**
   * Normalize raw function directives into object format.
   */
  function normalizeDirectives (options) {
    var dirs = options.directives;
    if (dirs) {
      for (var key in dirs) {
        var def$$1 = dirs[key];
        if (typeof def$$1 === 'function') {
          dirs[key] = { bind: def$$1, update: def$$1 };
        }
      }
    }
  }

通读代码,Vue允许我们使用自定义指令,并提供以下5类钩子

我们可以使用以下的方式去校验:

{
    directives: {
      'demo': function (el,binding) {
        console.log(binding.value.color) // => "white"
      }
    }
  }

通过结果,对directives进行规范化时,针对函数的写法会将行为赋予bind,update钩子,并回调

Thanks♪(・ω・)ノ

这篇关于Vue源码学习之实例初始化选项初合并 & 选项规范化校验(2000字)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!