vue源码学习(二)

初始化中的函数

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_parentVodeparent挂载到组件实例$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.把组件依赖于父组件的propslisteners也挂载到options上,方便子组件调用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容