初始化中的函数
initInternalComponent
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
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
方法接受两个参数,第一个参数是组件实例,即this。第二个参数是组件构造函数中传入的option
拓展
VueJS的虚拟DOM是基于开源Snabbdom的。
前端发展很多年,直到出现了虚拟DOM,才可以从操作DOM解脱出来。JQuery的出现,简化了操作DOM的过程,但是还是摆脱不了操作DOM。而虚拟DOM的目的是,使用虚拟节点代替真实节点,所有操作都发生在虚拟节点,然后通过diff算法对比【(相关)patch打补丁函数】新旧两棵虚拟DOM,计算出更新真实DOM的最少操作,由框架代替用户执行这些操作,所以用户可以把大量的精力放在业务逻辑上。
VNode
在Vue里,使用VNode表示一个虚拟节点,在Vue里,VNode分两种类别:
- 普通VNode
- 组件VNode
它们的区别是普通VNode有children属性,而组件VNode的children属性为undefined。VNode 的结构如下:
// src/core/vdom/vnode.js
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string; // functioanl scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag // 当前节点标签名
this.data = data // 当前节点数据(VNodeData类型)
this.children = children // 当前节点子节点
this.text = text // 当前节点文本
this.elm = elm // 当前节点对应的真实DOM节点
this.ns = undefined // 当前节点命名空间
this.context = context // 当前节点上下文
this.fnContext = undefined // 函数化组件上下文
this.fnOptions = undefined // 函数化组件配置项
this.fnScopeId = undefined // 函数化组件ScopeId
this.key = data && data.key // 子节点key属性
this.componentOptions = componentOptions // 组件配置项
this.componentInstance = undefined // 组件实例
this.parent = undefined // 当前节点父节点
this.raw = false // 是否为原生HTML或只是普通文本
this.isStatic = false // 静态节点标志 keep-alive
this.isRootInsert = true // 是否作为根节点插入
this.isComment = false // 是否为注释节点
this.isCloned = false // 是否为克隆节点
this.isOnce = false // 是否为v-once节点
this.asyncFactory = asyncFactory // 异步工厂方法
this.asyncMeta = undefined // 异步Meta
this.isAsyncPlaceholder = false // 是否为异步占位
}
// 容器实例向后兼容的别名
get child (): Component | void {
return this.componentInstance
}
}
- 所有对象的 context 选项都指向了 Vue 实例;
- elm 属性则指向了其相对应的真实 DOM 节点;
- DOM 中的文本内容被当做了一个只有 text 没有 tag 的节点;
- 像 class、id 等HTML属性都放在了 data 中。
而这个合并方法是在createComponentInstanceForVnode
方法中定义的,具体如下
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
return new vnode.componentOptions.Ctor(options)
}
option
中有三个属性值,_isComponent
即组件本身;_parentVode
其实就是该组件实例的vnode
对象(createComponentInstanceForVnode
就是根据这个vnode
对象去创建一个组件实例);parent
则是该组件的父组件实例对象
然后我们来看看具体initInternalComponent
做了什么操作:
const opts = vm.$options = Object.create(vm.constructor.options)
首先,用Object.create这个函数,把组件构造函数的options
挂载到vm.$options
的__proto__
上
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
接下把传入参数的option
的_parentVode
和parent
挂载到组件实例$options
上。用我们在两种策略里的那个例子来说,parent
就是我们组件的根实例,而_parentVnode
就是<comp :msg="msg" @log-msg="logMsg"></comp>
生成的一个Vnode
对象。
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
然后把父组件里的vnode
上的四个属性挂载到我们的$options
上,还是用那个例子来说,propsData
就是根据:msg="msg"
生成的,他的值就是在根组件里定义的那个msg{msg: "props-message"}
。而_parentListeners
就是根据@log-msg="logMsg"
生成的,他的值是logMsg
这个定义在父组件中的方法
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
最后就是如果传入的option
中如果有render
,把render
相关的也挂载到$options
上。
因此,这个initInternalComponent
主要做了两件事情:1.指定组件$options
原型,2.把组件依赖于父组件的props
、listeners
也挂载到options
上,方便子组件调用