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 };
}
}
}
}