阅读 63

研究vue2.0源码(1)initMixin——core

研究vue2.0源码(1)initMixin——core

为什么研究源码

事情是这样..本来只是一个前端菜鸡(虽然现在也是一个前端大菜鸡(可以菜但是必须得大对不对:))),然后有幸去年参加了一个大项目,就是那种全国性大大项目啊哈哈哈哈,也不用点名点姓说是哪个项目了,就是很大的全国性的,然后触发了程序员的被动技能——工作半年,实际工作时间一年。。。(天天加班)

然后吧,每天晚上也没有啥事,就闲来无事的时候脑子一抽(也是恋爱不好谈了,王者荣耀不好玩了)想看vue2.0的源码,(这时候内心就一个想法,你在凝视深渊的时候,深渊也在凝视你。。。),然后就点开了node_module文件夹下罪恶之源的vue文件夹,慢慢的就开始了vue源码的解读慢慢长路,哎。浪费我多少青春。

image.png

_init函数

instance(实例),首先进入init.js文件,这个文件暴露出来的方法initMixin,接收参数是一个组件实例,将参数Vue实例组件赋值给vm变量

inport conf ig fron ..conf ig'.tiff

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上的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的实例过程中第一个生命周期做的操作就讲完了

image.png

接下来是生命周期当中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,完成了这篇博客:)

image.png

文章分类
前端
文章标签