上一节我们从打包入口寻找到了初始化实例入口,并做了大致的描述,接下来,让我们看看每一步分别都做了什么
我们先从上而下阅读一下关键性代码
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this
vm._uid = uid++
vm._isVue = true
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
1. 合并选项
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
1.1 initInternalComponent
export function initInternalComponent (vm, options) {
// 获取实例的选项并阻断原型链
const opts = vm.$options = Object.create(vm.constructor.options)
// 将传入的选项内容合入当前组件选项下
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
当初始化子组件时会执行initInternalComponent,对子组件进行优化,会将组件配置对象的选项合并到子组件上,方便后续的计算和查找。
1.2 resolveConstructorOptions
// 从组件的构造函数中获取配置选项并合并
export function resolveConstructorOptions (Ctor) {
let options = Ctor.options
if (Ctor.super) {
// 递归合并基类的配置选项
const superOptions = resolveConstructorOptions(Ctor.super)
// 缓存选项,以便后续对比更新
const cachedSuperOptions = Ctor.superOptions
// 如果基类配置选项发生了修改
if (superOptions !== cachedSuperOptions) {
// 重新设置基类选项
Ctor.superOptions = superOptions
// 获取后续修改过的选项内容
const modifiedOptions = resolveModifiedOptions(Ctor)
// 合并两个选项
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// 合并选项并赋值给Crol.options
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
1.3 resolveModifiedOptions
// 处理修改的选项
function resolveModifiedOptions (Ctor) {
let modified
// 构造函数选项(修改后)
const latest = Ctor.options
// 构造函数密封选项(此前备份项)
const sealed = Ctor.sealedOptions
// 寻找选项差异并返回
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
1.4 mergeOptions
export function mergeOptions (
parent,
child,
vm
) {
// 如果合并项是函数,则取其选项
if (typeof child === 'function') {
child = child.options
}
// 标准化Props,inject,derectives
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// 当child是原始对象时(进行过mergeOptions处理才有_base),合并child的extends和mixins选项
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)
}
}
}
// 使用合并策略合并parent和child
const options = {}
let key
for (key in parent) {
// 合并父选项(已经处理了子选项同key的情况)
mergeField(key)
}
for (key in child) {
// 如果父选项中不存在该值,合并子选择
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// strats是一种合并策略,它对data,watcher,props,methods,inject,provide,computed的选项合并做了分别的处理
// 其中watcher相对特殊一些,会将父子项都放入当前key的数组中,其他项的大致思路皆为子选项优先
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
1.5normalizeProps
// 标准化props
function normalizeProps (options, vm) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = { type: null }
}
}
} else if (isPlainObject(props)) {
for (const key in props) {
val = props[key]
name = camelize(key)
res[name] = isPlainObject(val)
? val
: { type: val }
}
}
options.props = res
}
props有数组和对象两种形式,源码中分别进行了解析。做了一下两件事情 1. 标准化 props 名称,将连字符形式转为小驼峰 2. 每一个 props 的 key 必须有一个 type 属性,没有则赋值为 null。
标准化 inject 与 directives 处理也类似,关键点在于初始化属性, inject 设置了 form,directives 设置了 bind 和 update,下面贴一下代码。
1.6 normalizeInject
function normalizeInject (options, vm) {
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]
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
}
}
1.7normalizeDirectives
function normalizeDirectives (options) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
2. initLifecycle
export function initLifecycle (vm) {
const options = vm.$options
// keep-alive是一个抽象组件,不负责组件挂载,因此要往上寻找父级
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 = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
3. initEvents
export function initEvents (vm) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// 初始化父级的附加事件(listener)
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
3.1 updateComponentListeners
export function updateComponentListeners (vm, listeners, oldListeners) {
target = vm
// 其中的add, remove, createOnceHandler都是事件处理机制
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
3.2 updateListeners
export function updateListeners (on, oldOn, add, remove, createOnceHandler, vm) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name]
old = oldOn[name]
// 去除函数名称前面有可能出现的 &、~、!,分别代表passive、once、capture
event = normalizeEvent(name)
if (isUndef(old)) {
if (isUndef(cur.fns)) {
// 创建一个可捕捉错误的函数
cur = on[name] = createFnInvoker(cur, vm)
}
if (isTrue(event.once)) {
// once事件处理
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
// 添加事件绑定
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
// 重名直接替换函数
old.fns = cur
on[name] = old
}
}
// 解除事件绑定
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
4. initRender
export function initRender (vm) {
// 子树的根节点
vm._vnode = null
// v-once静态树缓存
vm._staticTrees = null
const options = vm.$options
// 父树中的占位节点
const parentVnode = vm.$vnode = options._parentVnode
const renderContext = parentVnode && parentVnode.context
// 解析_renderChildren
vm.$slots = resolveSlots(options._renderChildren, renderContext)
// 设置为空对象
vm.$scopedSlots = emptyObject
// 节点渲染函数,$createElement等同于我们熟知的h函数,和_c的区别为是否要做标准化处理(外部写的渲染函数和编译后的不太一样,需要做标准化),这个我们之后再做讨论
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
// 对$attrs和$listeners做响应式处理
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
4.1 resolveSlots
export function resolveSlots (children, context) {
if (!children || !children.length) {
return {}
}
const slots = {}
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
const data = child.data
// 如果节点解析有slot属性,删除该属性
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot
}
// 只有处于同一上下文时,才考虑命名插槽
if ((child.context === context || child.fnContext === context) && data && data.slot != null) {
const name = data.slot
const slot = (slots[name] || (slots[name] = []))
// 如果插槽的字节点标签是template,传入它的children
if (child.tag === 'template') {
slot.push.apply(slot, child.children || [])
} else {
slot.push(child)
}
} else {
(slots.default || (slots.default = [])).push(child)
}
}
// 空、注释和异步
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots
}
本篇讲述了合并选项,以及初始化生命周期,事件选项和渲染相关内容的大致过程以及思路,下一篇会继续讲述初始化inject,provide以及相当重要的state处理