vue 选项初始化流程源码分析(原型属性方法定义、静态方法定义、内置选项、合并场景、规则校验)

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
// 定义Vue原型上的init方法(内部方法)
initMixin(Vue);
// 定义原型上跟数据相关的属性方法
stateMixin(Vue);
//定义原型上跟事件相关的属性方法
eventsMixin(Vue);
// 定义原型上跟生命周期相关的方法
lifecycleMixin(Vue);
// 定义原型上渲染相关的函数
renderMixin(Vue); 


initMixin定义了内部在实例化Vue时会执行的初始化代码

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
  	...
  }
}

stateMixin

  • 定义原型上跟数据相关的属性方法,例如代理数据的访问,我们可以在实例上通过this.$data和this.$props访问到data,props的值,并且也定义了使用频率较高的this.$set,this.$delete、this.$watch等方法
function stateMixin (Vue) {
    var dataDef = {};
    dataDef.get = function () { return this._data };
    var propsDef = {};
    propsDef.get = function () { return this._props };
    {
      dataDef.set = function () {
        warn(
          'Avoid replacing instance root $data. ' +
          'Use nested data properties instead.',
          this
        );
      };
      propsDef.set = function () {
        warn("$props is readonly.", this);
      };
    }
    // 代理了_data,_props的访问
    Object.defineProperty(Vue.prototype, '$data', dataDef);
    Object.defineProperty(Vue.prototype, '$props', propsDef);
    // $set, $del
    Vue.prototype.$set = set;
    Vue.prototype.$delete = del;

    // $watch
    Vue.prototype.$watch = function (expOrFn,cb,options) {};
  }

eventsMixin

  • 对原型上的事件相关方法做定义
function eventsMixin(Vue) {
  // 自定义事件监听
  Vue.prototype.$on = function (event, fn) {};
  // 自定义事件监听,只触发一次
  Vue.prototype.$once = function (event, fn) {}
  // 自定义事件解绑
  Vue.prototype.$off = function (event, fn) {}
  // 自定义事件通知
  Vue.prototype.$emit = function (event, fn) {
}

lifecycleMixin

  • 定义原型上跟生命周期相关的方法
// 定义跟生命周期相关的方法
  function lifecycleMixin (Vue) {
    Vue.prototype._update = function (vnode, hydrating) {};

    Vue.prototype.$forceUpdate = function () {};

    Vue.prototype.$destroy = function () {}
  }

renderMixin

  • 定义原型上跟渲染相关的方法
  function renderMixin (Vue) {
    Vue.prototype.$nextTick = function (fn) {};
    // _render函数,后面会着重讲
    Vue.prototype._render = function () {};
  }

定义静态属性方法

  • 除了原型方法外,Vue还提供了丰富的全局api方法,这些都是在initGlobalAPI中定义的

initGlobalAPI

  • 为源码里的config配置做一层代理,可以通过Vue.config拿到默认的配置,并且可以修改它的属性值,具体哪些可以配置修改,可以先参照官方文档。
  • 定义内部使用的工具方法,例如警告提示,对象合并等。
  • 定义set,delet,nextTick方法,本质上原型上也有这些方法的定义。
  • 对Vue.components,Vue.directive,Vue.filter的定义,这些是默认的资源选项,后续会重点分析。
  • 定义Vue.use()方法
  • 定义Vue.mixin()方法
  • 定义Vue.extend()方法
/* 初始化构造器的api */
function initGlobalAPI (Vue) {
    // config
    var configDef = {};
    configDef.get = function () { return config; };
    {
      configDef.set = function () {
        warn(
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    // 通过Vue.config拿到配置信息
    Object.defineProperty(Vue, 'config', configDef);

    // 工具类不作为公共暴露的API使用
    Vue.util = {
      warn: warn,
      extend: extend,
      mergeOptions: mergeOptions,
      defineReactive: defineReactive###1
    };

    // Vue.set = Vue.prototype.$set
    Vue.set = set;
    // Vue.delete = Vue.prototype.$delete
    Vue.delete = del;
    // Vue.nextTick = Vue.prototype.$nextTick
    Vue.nextTick = nextTick;

    // 2.6 explicit observable API
    Vue.observable = function (obj) {
      observe(obj);
      return obj
    };
		
	var ASSET_TYPES = [
	  'component',
	  'directive',
	  'filter'
	];
	...
    // 构造函数的默认选项默认为components,directive,filter, _base
    Vue.options = Object.create(null);
    ASSET_TYPES.forEach(function (type) {
      Vue.options[type + 's'] = Object.create(null);
    });

    // options里的_base属性存储Vue构造器
    Vue.options._base = Vue;
    //这里的extends是个合并属性的方法
    extend(Vue.options.components, builtInComponents);
    ...
    
    // Vue.use()
    initUse(Vue);
    // Vue.mixin()
    initMixin$1(Vue);
    // 定义extend扩展子类构造器的方法
    // Vue.extend()
    initExtend(Vue);
    // Vue.components, Vue.directive, Vue.filter
    initAssetRegisters(Vue);
  }


其中包括内置组件、选项的合并

// Vue内置组件
var builtInComponents = {
  KeepAlive: KeepAlive
};
var platformComponents = {
  Transition: Transition,
  TransitionGroup: TransitionGroup
};
// Vue 内置指令,例如: v-model, v-show
var platformDirectives = {
  model: directive,
  show: show
}
extend(Vue.options.components, builtInComponents);  //keep-alive基本内置组件
extend(Vue.options.components, platformComponents); // 扩展内置组件
extend(Vue.options.directives, platformDirectives);  // 扩展内置指令

extend方法:

// 将_from对象合并到to对象,属性相同时,则覆盖to对象的属性
function extend (to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to
}

默认配置如下:

Vue.options = {
  components: {
    KeepAlive: {}
    Transition: {}
    TransitionGroup: {}
  },
  directives: {
    model: {inserted: ƒ, componentUpdated: ƒ}
    show: {bind: ƒ, update: ƒ, unbind: ƒ}
  },
  filters: {}
  _base
}

选项合并

export function initMixin (Vue: Class<Component>) {
  // 在原型上添加 _init 方法
  Vue.prototype._init = function (options?: Object) {
    // 保存当前实例
    const vm: Component = this
    // 记录实例化多少个vue对象
    vm._uid = uid++;
    
    // 合并配置
    if (options && options._isComponent) {
      // 把子组件依赖父组件的 props、listeners 挂载到 options 上,并指定组件的$options
      initInternalComponent(vm, options)
    } else {
 	  // 选项合并,将合并后的选项赋值给实例的$options属性
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor), // 返回Vue构造函数自身的配置项
        options || {},
        vm
      )
    }
    vm._self = vm
    initLifecycle(vm) // 初始化实例的属性、数据:$parent, $children, $refs, $root, _watcher...等
    initEvents(vm) // 初始化事件:$on, $off, $emit, $once
    initRender(vm) // 初始化渲染: render, mixin
    callHook(vm, 'beforeCreate') // 调用生命周期钩子函数
    initInjections(vm) // 初始化 inject
    initState(vm) // 初始化组件数据:props, data, methods, watch, computed
    initProvide(vm) // 初始化 provide
    callHook(vm, 'created') // 调用生命周期钩子函数

    if (vm.$options.el) {
      // 如果传了 el 就会调用 $mount 进入模板编译和挂载阶段
      // 如果没有传就需要手动执行 $mount 才会进入下一阶段
      vm.$mount(vm.$options.el)
    }
  }
}


mergeOptions

function mergeOptions (parent,child,vm) {
    {
      checkComponents(child);
    }
    if (typeof child === 'function') {
      child = child.options;
    }
    // props,inject,directives的校验和规范化
    normalizeProps(child, vm);
    normalizeInject(child, vm);
    normalizeDirectives(child);
    
    // 针对extends扩展的子类构造器
    if (!child._base) {
      // extends
      if (child.extends) {
        parent = mergeOptions(parent, child.extends, vm);
      }
      // mixins
      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);
    }
    // console.log(options)
    return options
  }

子类构造器,Vue.extend
选项合并的场景就是为了Vue,extend进行组件选项的合并

  • Vue提供了一个Vue.extend的静态方法,它是基于基础的Vue构造器创建一个“子类”,而这个子类所传递的选项配置会和父类的选项配置进行合并。这是选项合并场景的由来。

基本使用:


var Parent = Vue.extend({
  data() {
    test: '父类',
    test1: '父类1'
  }
})
var Child = Parent.extend({
  data() {
    test: '子类',
    test2: '子类1'
  }
})
var vm = new Child().$mount('#app');
console.log(vm.$data);
// 结果 
{
  test: '子类',
  test1: '父类1',
  test2: '子类1'
}

代码实现:

Vue.extend = function (extendOptions) {
  extendOptions = extendOptions || {};
  var Super = this;

  var name = extendOptions.name || Super.options.name;
  if (name) {
    validateComponentName(name); // 校验子类的名称是否符合规范
  }

  // 创建子类构造器
  var Sub = function VueComponent (options) {
    this._init(options);
  };
  Sub.prototype = Object.create(Super.prototype); // 子类继承于父类
  Sub.prototype.constructor = Sub;
  Sub.cid = cid++;
  // 子类和父类构造器的配置选项进行合并
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  );

  return Sub // 返回子类构造函数
};

选项校验:
components规范检验

// components规范检查函数
function checkComponents (options) {
  // 遍历components对象,对每个属性值校验。
  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.'
    );
  }
  // 不能使用Vue自身自定义的组件名,如slot, component,不能使用html的保留标签,如 h1, svg等
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    );
  }
}

props规范检验

  • 数组形式 { props: [‘a’, ‘b’, ‘c’] },
  • 带校验规则的对象形式 { props: { a: { type: ‘String’, default: ‘prop校验’ } }} 从源码上看,两种形式最终都会转换成对象的形式。
// props规范校验
  function normalizeProps (options, vm) {
    var props = options.props;
    if (!props) { return }
    var res = {};
    var i, val, name;
    // props选项数据有两种形式,一种是['a', 'b', 'c'],一种是{ a: { type: 'String', default: 'hahah' }}
    // 数组
    if (Array.isArray(props)) {
      i = props.length;
      while (i--) {
        val = props[i];
        if (typeof val === 'string') {
          name = camelize(val);
          // 默认将数组形式的props转换为对象形式。
          res[name] = { type: null }; 
        } else {
          // 规则:保证是字符串
          warn('props must be strings when using array syntax.');
        }
      }
    } else if (isPlainObject(props)) {
      for (var key in props) {
        val = props[key];
        name = camelize(key);
        res[name] = isPlainObject(val)
          ? val
          : { type: val };
      }
    } else {
      // 非数组,非对象则判定props选项传递非法
      warn(
        "Invalid value for option \"props\": expected an Array or an Object, " +
        "but got " + (toRawType(props)) + ".",
        vm
      );
    }
    options.props = res;
  }

camelize是种缓存写法

function cached (fn) {
  var cache = Object.create(null); // 创建空对象作为缓存对象
  return (function cachedFn (str) {
    var hit = cache[str];
    return hit || (cache[str] = fn(str)) // 每次执行时缓存对象有值则不需要执行函数方法,没有则执行并缓存起来
  })
}

var camelizeRE = /-(\w)/g;

// 缓存会保存每次进行驼峰转换的结果
var camelize = cached(function (str) {
  // 将诸如 'a-b'的写法统一处理成驼峰写法'aB'
  return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});


inject的规范校验

  • 提供的数据是非响应式的

基本使用:

// 父组件
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}
// 后代组件
var Child = {
  // 数组写法
  inject: ['foo'],
  // 对象写法
  inject: {
    foo: {
      from: 'foo',
      default: 'bardefault'
    }
  }
}

和props的校验规则一致,最终inject都会转换为对象的形式存在

// inject的规范化
function normalizeInject (options, vm) {
    var inject = options.inject;
    if (!inject) { return }
    var normalized = options.inject = {};
    //数组的形式
    if (Array.isArray(inject)) {
      for (var i = 0; i < inject.length; i++) {
        // from: 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
        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
      );
    }
  }

directive的规范校验

  • 函数的写法会触发bind,update钩子,并且不关心其他钩子。这个行为就是定义的函数。因此在对directives进行规范化时,针对函数的写法会将行为赋予bind,update钩子
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 };
        }
      }
    }
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值