研究vue2.0源码(1)initMixin——core
为什么研究源码
事情是这样…本来只是一个前端菜鸡(虽然现在也是一个前端大菜鸡(可以菜但是必须得大对不对:))),然后有幸去年参加了一个大项目,就是那种全国性大大项目啊哈哈哈哈,也不用点名点姓说是哪个项目了,就是很大的全国性的,然后触发了程序员的被动技能——工作半年,实际工作时间一年。。。(天天加班)
然后吧,每天晚上也没有啥事,就闲来无事的时候脑子一抽(也是恋爱不好谈了,王者荣耀不好玩了)想看vue2.0的源码,(这时候内心就一个想法,你在凝视深渊的时候,深渊也在凝视你。。。),然后就点开了node_module文件夹下罪恶之源的vue文件夹,慢慢的就开始了vue源码的解读慢慢长路,哎。浪费我多少青春。
_init函数
instance(实例),首先进入init.js文件,这个文件暴露出来的方法initMixin,接收参数是一个组件实例,将参数Vue实例组件赋值给vm变量
utils/options
在下面的内容中对实例上的$options初始化,使用了函数mergeOptions方法
// merge 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 {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
mergeOptions方法来自utils/options.js,这个方法中对options有格式化操作,包括参数props,注入injects,自定义指令directives
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
处理完当前节点后自调用处理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) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
instance/lifecycle
进入重点,对生命周期处理,前文讲到刚进入初始化的时候对vm上的 o p t i o n s 初 始 化 , 经 历 了 m e r g e O p t i o n s 的 过 程 , 在 初 始 化 l i f e c y c l e 的 时 候 先 拿 到 options初始化,经历了mergeOptions的过程,在初始化lifecycle的时候先拿到 options初始化,经历了mergeOptions的过程,在初始化lifecycle的时候先拿到options,然后在vm上添加其他的vue指令
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
对于vue上的指令是在生命周期的时候添加的,但是没有被激活,只是空对象字面量赋值,在后面还对实例上的状态进行初始化赋值
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
instance/events
完成生命周期函数初始化,接下来是对event事件进行初始化initEvents,这个函数代码很少,如果是熟悉jsx写法对盆友们已经发现了熟悉的命名listeners,我通常使用jsx二次封装组件时,就会获取$listener直接赋值给原始组件,updateComponentListeners函数做的事情就是将新的listener替换之前的listener
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
instance/render
initRender这个函数有点强者的味道,无论是在react开发过程中,还是vue使用jsx写法开发过程中,都有一个无法忽视的存在render函数,在react中render函数也属于生命周期当中的一部分,执行时间是在处理props和挂载dom之间,在vue中也就是在created生命周期函数之后,在这一步执行的内容也是相当重要,之前对vm实例上的众多实力方法和判断实例状态的进行了初步赋值,那么就是在initRender的时候进行的激活。
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
instance/index
然后然后!!!终于到了生命周期函数,钩子函数,伟大的函数beforeCreate,这是见证历史的第一步!那么在创建Vue的实例过程中第一个生命周期做的操作就讲完了
接下来是生命周期当中created钩子函数的处理过程,有initInjections,initState,initProvide,特别注意在initInjections和initProvide后面有注释 resolve injections before data/props (在data/props前处理injections和provide)
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
instance/inject
在这个文件中,内容不多,一共就三个函数,分别是initProvide,initInjections和resolveInject,resolveInject函数在initInjections函数中被使用,功能是沿着$parent追踪注入provide的源头,如果没有找到,那么使用inject就会报错
warn(`Injection "${key}" not found`, vm)
返回来的inject会进行遍历,调用observe的defineReactive做响应式处理
initProvide会发现有意思的地方,通常写注入provide都是以字符串的形式,而在initProvide函数中会进行判断provide是否是function类型,聪明的小伙伴已经知道了,provide注入是可以写成函数形式的,然后给vm上的_provided属性赋值,完成。
instance/state
上文提到的在钩子函数created之前调用的三个函数就已经讲解了两个,剩下的这个才是最重要的一个,其中的操作也是最多,对于实例上的配置也控制很多。其中有最熟悉的data,props,methods,computed和watch
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps也是判断在父组件中是否传入了该参数,其次将props做响应式处理
initMethods中遍历了methods,判断每项类型是否为function类型,然后使用bind绑定this指向到vm(很react)
initData会遍历data返回到对象,然后在props和methods中找是否有相同key键的项,如果有就会报错(我看之前还以为会覆盖。。。),然后将整个data做响应式处理
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
}
initComputed最重要的部分是对computedWatcher的处理,computedWatcher有什么用?什么是computedWatcher?这个在之后的博客中解答
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
initWatch判断了传入内容是否为对象类型,然后使用实例$watch创建watcher
Good,完成了这篇博客:)