Vue多个组件到底new Vue了几次?

问题来源:时常在思考,vue每个组件实例大体一致,但是组件都是导出的一个组件构造对象,并没有看到组件new Vue成一个实例的一个操作,在Vue中每个组件都是一个vue实例,那么多个vue组件是不是会new Vue多次呢?伴随这个问题我百度了很多,有的说一个vue文件/组件就会new一次,有多少个就new多少次,还有的说vue作为个单页面应用,只new一次...。啊这我到底该听哪个?随着这个答案不确定,还是自己去解答吧!

1、我们可以看到唯一的一次new Vue是在main.js中,在new Vue以后调用的是this._init方法。


//vue初始化
Vue.prototype._init = function (options?: Object) {
  //vue实例: 在new Vue函数后的this._init,所以this就是Vue实例
  const vm: Component = this
  // a uid
  //每个vue实例都有一个uid,并且是依次递增的,类似于key避免重复
  vm._uid = uid++

  let startTag, endTag
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
  }

  // a flag to avoid this being observed
  vm._isVue = true
  // 处理组件配置项
  if (options && options._isComponent) {
    /**
     * 每个子组件初始化走这里,只做一些性能优化
     * 将组件配置项对象的一些深层次属性放到 vue.$options 选项中,以提高代码的执行效率
     */
    initInternalComponent(vm, options)
  } else {
    /**
     * 初始化根组件走这里,合并 vue 的全局配置到根组件的局部配置,比如Vue.component 注册的全局组件会合并到 根实例的 components 选项
     * 至于每个子组件的选项合并则发生在两个地方:
     *  1、Vue.component 方法注册的全局组件在注册时做了选择合并
     *  2、{ components: { xx } } 方式注册的局部组件在执行编译器生产的 render 函数进行了选项合并,包括根组件中的 components 配置
     * @type {Object}
     */
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor), //vm的构造器:Vue函数对象,也就是全局配置对象
      options || {}, //vm上的属性
      vm
    )
  }
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    // 设置代理,将 vm 实例上的属性代理到 vm._renderProxy
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  // expose real self
  vm._self = vm
  // 初始化组件实例关系属性,比如 $parent、$children、$root、$refs 等
  // 也可以理解为找出组件实例的生命周期所在位置
  initLifecycle(vm)
  /**
   * 初始化自定义事件,这里需要注意一点,所以我们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件,
   * 而是子组件本身,也就是说事件的派发和监听者都是子组件本身,和父组件无关
   */
  initEvents(vm)
  // 解析组件的插槽信息,得到 vm.$slot,处理渲染函数,得到 vm.$createElement 方法,即 h 函数
  initRender(vm)
  // 调用 beforeCreate 钩子函数
  callHook(vm, 'beforeCreate')
  // 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个 key 到 vm 实例
  initInjections(vm) // resolve injections before data/props
  // 数据响应式的重点,处理 props、methods、data、computed、watch
  initState(vm)
  // 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
  initProvide(vm) // resolve provide after data/props
  // 调用 created 钩子函数
  callHook(vm, 'created')

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
  }

  //如果发现配置项有el属性,则自动调用$mount方法,也就是有了el属性就不需要手动调用$mount,反之没有el则必须手动调用$mount
  if (vm.$options.el) {
    //调用$mount方法,进入挂载阶段
    vm.$mount(vm.$options.el)
  }
}

组件的属性合并、数据响应式等都是在这个函数里面完成的,那么组件每次导出的都是export default { ...属性 },那组件是在哪初始化的?

2、为了理解这个问题我试着去找在哪一步有处理子组件的。当分析组件嵌套的生命周期时,我们可以看到子组件开始创建是在父组件beforeMount之后

image

3、那么推断与render函数有关,于是找到_init函数内的initRender(vm)函数,我们可以看到函数内部得到了$createElement,即h渲染函数。在父组件开始渲染的时候会触发这个$createElement,也就是执行createElement

export function initRender (vm: Component) {
  //省略
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  //省略
}

4、createElement函数返回一个_createElement,这里就直接贴_createElement的代码了。


export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  //...省略
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      //...省略
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // 仔细看这一步,当判断当前实例有components时,就执行createComponent
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      //...省略
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  //...省略
}

5、接着往下走,createComponent做了什么操作,主要是以下代码,先拿到baseCtor也就是Vue这个构造函数,然后判断这个子组件export的是不是一个对象,是的话执行Vue.extend把这个子组件的构造对象传入进去

const baseCtor = context.$options._base

// plain options object: turn it into a constructor
if (isObject(Ctor)) {
  Ctor = baseCtor.extend(Ctor)
}

6、那么Vue身上的extend是从哪里来的,又做了什么。看以下代码可以看到extedn方法导出了一个Sub构造函数,并且继承自Vue构造函数的,然后最后执行baseCtor.extend等于调用了Sub方法也就是this._init做子组件的初始化创建。

Vue.extend = function (extendOptions: Object): Function {
  //...省略
  // 定义 Sub 构造函数,和 Vue 构造函数一样
  const Sub = function VueComponent (options) {
    // 初始化
    this._init(options)
  }
  //...省略
  return Sub
}

总结:Vue作为个单页面实际只new了一次,后续组件都是通过this._init创建的实例。所以关于父子组件的生命周期也大体明白,这也是为什么父组件的beforeMount后,就到了子组件的创建环节。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容