# 从一个例子开始
之前从宏观的角度来看 Vue 的设计,再逐步搞清楚 Vue 的具体实现过程
<div id="app">{{test}}</div>
let vm=new Vue({
el:'#app',
data:{
test:1
}
})
会走到vue的构造函数
function Vue (options) {
this._init(options)
}
// 传入的options为
options = {
el: '#app',
data: {
test: 1
}
}
// _init在src/core/instance/init.js
const vm: Component = this
// a uid _uid 就是一个 Vue 实例的实例属性
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
//vue.config.performance==true 开启性能追踪
//可追踪 组件初始化,编译(compile),渲染(render)渲染函数执行且生成虚拟DOM(vnode)的性能,
//打补丁(patch),虚拟DOM渲染为真实DOM的性能
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
//不是普通对象,是vue实例
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
//实例上添加了 $options 属性
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
//初始化
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
# mergeOptions
首先是resolveConstructorOptions
函数
function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
//ctor 是传进来的vm.constructor,super 是子类才有的属性
//当前例子不会进去
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const 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)
const 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
}
function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
function checkComponents (options: Object) {
for (const key in options.components) {
//校验组件的名字是否符合要求
validateComponentName(key)
function validateComponentName (name: string) {
// 普通的字符和中横线(-)组成,且必须以字母开头
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn()
}
// isBuiltInTag 检测你所注册的组件是否是内置的标签 slot 和 component 这两个名字被 Vue 作为内置标签而存在
// config.isReservedTag 检测是否是保留标签 html 标签和部分 SVG 标签被认为是保留的
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn( )
}
}
}
}
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options//是函数的话就取该函数的 options 静态属性作为新的 child
}
//规范选项
normalizeProps(child, vm)
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return//没有props return
const res = {}
let i, val, name
//如果props:['data]转换为
// props:{
// data:{
// type:null
// }
// }
if (Array.isArray(props)) {//
i = props.length
while (i--) {
val = props[i]
//说明porps数组元素必须为字符串
if (typeof val === 'string') {
name = camelize(val)//将中横线转驼峰
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {//判断是否是纯对象
for (const key in props) {
val = props[key]
name = camelize(key)
//检测 props 每一个键的值,如果值是一个纯对象那么直接使用,否则将值作为 type 的值
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
}
normalizeInject(child, vm)
//对应 inject
normalizeInject (options: Object, vm: ?Component) {
const inject = options.inject
if (!inject) return
const normalized = options.inject = {}//重写为空对象
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {//纯对象
for (const key in inject) {
const val = inject[key]
// {
// 'data':{from:'data'}
// }
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
)
}
}
normalizeDirectives(child)
//对应 directives
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
//指令是一个函数的时候,则将该函数作为对象形式的 bind 属性和 update 属性的值
dirs[key] = { bind: def, update: def }
}
}
}
}
//处理 extends 和 mixins
const extendsFrom = child.extends
if (extendsFrom) {
//mergeOptions 函数将会产生一个新的对象,此时的 parent 已经被新的对象重新赋值了。
parent = mergeOptions(parent, extendsFrom, 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) {
//如果child 对象的键也在 parent 上出现,那么就不要再调用 mergeField 了,因为在上一个 for in 循环中已经调用过了
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
// strats= config.optionMergeStrategies,还只是个空对象, 选项覆盖策略是处理如何将父选项值和子选项值合并到最终值的函数
//使用自定义选项,那么你也可以自定义该选项的合并策略,只需要在 Vue.config.optionMergeStrategies 对象上添加与自定义选项同名的函数就行
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
再看合并策略core/util/option.js
if (process.env.NODE_ENV !== 'production') {
strats.el = strats.propsData = function (parent, child, vm, key) {
//策略函数中拿不到 vm 参数,那么处理的就是子组件的选项
if (!vm) {
warn(
`option "${key}" can only be used during instance ` +
'creation with the `new` keyword.'
)
}
return defaultStrat(parent, child)
const defaultStrat = function (parentVal: any, childVal: any): any {
//只要子选项不是 undefined 那么就是用子选项,否则使用父选项。
return childVal === undefined
? parentVal
: childVal
}
}
}
//data合并
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
//没有 vm 参数时,说明处理的是子组件的选项
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
//正常使用 new 操作符创建实例时的选项,多传一个参数 vm
return mergeDataOrFn(parentVal, childVal, vm)
}
function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
//有 vm 参数时,说明处理的是子组件的选项
if (!vm) {
// in a Vue.extend merge, both should be functions
//childVal 和 parentVal 必定会有其一,否则便不会执行 strats.data 如果没有子选项则使用父选项,没有父选项就直接使用子选项
//data 选项最终被 mergeOptions 函数处理成了一个函数
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
// instance merge
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
//to 对应的是 childVal 产生的纯对象,from 对应 parentVal 产生的纯对象
function mergeData (to: Object, from: ?Object): Object {
// 没有 from 直接返回 to
if (!from) return to
let key, toVal, fromVal
const keys = Object.keys(from)
// 遍历 from 的 key
for (let i = 0; i < keys.length; i++) {
key = keys[i]
toVal = to[key]
fromVal = from[key]
// 如果 from 对象中的 key 不在 to 对象中,则使用 set 函数为 to 对象设置 key 及相应的值
if (!hasOwn(to, key)) {
set(to, key, fromVal)
// 如果 from 对象中的 key 也在 to 对象中,且这两个属性的值都是纯对象则递归进行深度合并
} else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
mergeData(toVal, fromVal)
}
// 其他情况什么都不做
}
return to
}
//合并生命钩子
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal //是否有 childVal,即判断组件的选项中是否有对应名字的生命周期钩子函数)
? parentVal //如果有 childVal 则判断是否有 parentVal
? parentVal.concat(childVal) //如果有 parentVal 则使用 concat 方法将二者合并为一个数组
: Array.isArray(childVal) //如果没有 parentVal 则判断 childVal 是不是一个数组
? childVal //如果 childVal 是一个数组则直接返回
: [childVal] //否则将其作为数组的元素,然后返回数组
: parentVal //如果没有 childVal 则直接返回 parentVal
}
// 在strats 策略对象上添加用来合并各个生命周期钩子选项的策略函数,并且这些生命周期钩子选项的策略函数相同
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
//合并处理 directives、filters 以及 components 等资源
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {//有childVal
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)//检测 childVal 是不是一个纯对象的
return extend(res, childVal)// extend 函数将 childVal 上的属性混合到 res 对象上并返回
} else {
return res
}
}
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets//+s
})
// watch 合并
strats.watch = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// work around Firefox's Object.prototype.watch... 火狐有原生的watch
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
/* istanbul ignore if */
if (!childVal) return Object.create(parentVal || null)
if (process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)//检测其是否是一个纯对象
}
if (!parentVal) return childVal
const ret = {}
//arentVal 的属性混合到 ret 中
extend(ret, parentVal)
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
//if 分支的作用就是如果 parent 存在,就将其转为数组
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
ret[key] = parent//parent 存在,此时的 parent 应该已经被转为数组了,所以直接将 child concat 进去
? parent.concat(child)
: Array.isArray(child) ? child : [child]//如果 parent 不存在,直接将 child 转为数组返回
}
return ret
}
//props methods inject computed合并策略
//分别用来合并处理同名选项的,并且所使用的策略相同
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
//存在 childVal,那么在非生产环境下要检查 childVal 的类型
if (childVal && process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)//纯对象
}
// parentVal 不存在的情况下直接返回 childVal
if (!parentVal) return childVal
// 如果 parentVal 存在,则创建 ret 对象,然后分别将 parentVal 和 childVal 的属性混合到 ret 中,注意:由于 childVal 将覆盖 parentVal 的同名属性
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
//provide 选项的合并策略与 data 选项的合并策略相同
strats.provide = mergeDataOrFn
选项处理总结
- el、propsData 选项使用默认的合并策略 defaultStrat。
- data 选项,使用 mergeDataOrFn 函数进行处理,最终结果是 data 选项将变成一个函数,且该函数的执行结果为真正的数据对象。
- 生命周期钩子 选项,将合并成数组,使得父子选项中的钩子函数都能够被执行
- directives、filters 以及 components 等资源选项,父子选项将以原型链的形式被处理,正是因为这样我们才能够在任何地方都使用内置组件、指令等。
- watch 选项的合并处理,类似于生命周期钩子,如果父子选项都有相同的观测字段,将被合并为数组,这样观察者都将被执行。
- props、methods、inject、computed 选项,父选项始终可用,但是子选项会覆盖同名的父选项字段。
- provide 选项,其合并策略使用与 data 选项相同的 mergeDataOrFn 函数。
- 以上没有提及到的选项都将使默认选项 defaultStrat。
- 默认合并策略函数 defaultStrat 的策略是:只要子选项不是 undefined 就使用子选项,否则使用父选项。
//mixins 递归调用了 mergeOptions 函数将 mixins 合并到了 parent 中,并将合并后生成的新对象作为新的 parent
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
//extends 选项,与 mixins 相同,由于 extends 选项只能是一个对象,而不能是数组,要比 mixins 的实现更为简单,连遍历都不需要