为了账号安全,请及时绑定邮箱和手机立即绑定

人人都能懂的Vue源码系列(二)—Vue构造函数

上篇博文中说到Vue源码的目录结构是什么样的,每个目录的作用应该也有所了解。我们知道core/instance目录主要是用来实例化Vue对象,所以我们在这个目录下去寻找Vue构造函数。果然找到了Vue构造函数的定义。

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

当你新建一个Vue实例时候,会判断如果当前的环境不是生产环境,且你在调用Vue的时候,没有用new操作符。就会调用warn函数,抛出一个警告。告诉你Vue是一个构造函数,需要用new操作符去调用。这个warn函数不是单纯的console.warn,它的实现我们后面的博文会介绍。

接下来,把options作为参数调用_init方法。options不做过多的介绍了,就是你调用new Vue时候传入的参数。在深入_init方法之前,我们先把目光移到index.js文件里

function Vue (options) {
  ...
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

在Vue的构造函数定义之后,有一系列方法会被调用,这些方法主要用来给Vue函数添加一些原型属性和方法的。其中就有接下来要介绍的Vue.prototyoe._init

Vue.prototype._init

在core/instance/init.js中我们找到了_init的定义。

  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    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
    // merge options
    // 有子组件时,options._isComponent才会为true
    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
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm) 
    initEvents(vm) 
    initRender(vm) 
    callHook(vm, 'beforeCreate')
    initState(vm)
    initProvide(vm) 
    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)
    }
    if (vm.$options.el) {
      vm.$mount(vm.$options.el) 
    }
  }

我们逐一来分析上述代码。首先缓存当前的上下文到vm变量中,方便之后调用。然后设置_uid属性。_uid属性是唯一的。当触发init方法,新建Vue实例时(当渲染组件时也会触发)uid都会递增。

下面这段代码主要是用来测试代码性能的,在这个时候相当于打了一个"标记点"来测试性能。

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

对这部分内容感兴趣的朋友们可以点击我的另一篇文章Performance API查看。接下来执行这行代码vm._isVue = true,Vue的作者对这句话做了注释。

an flag to avoid this being observed

乍看起来好像不太明白,好像是说为了防止this被observed实例化。那这究竟是什么意思呢?我们来看observer的代码。

export function observe (value: any, asRootData: ?boolean): Observer | void {
  ...
  else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  ...
}

如果传入值的_isVue为ture时(即传入的值是Vue实例本身)不会新建observer实例(这里可以暂时理解新建observer实例就是让数据响应式)。

再回到init源码部分

if (options && options._isComponent) {
    initInternalComponent(vm, options)
} else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
        options || {},
        vm
     )
}
  • 当符合第一个条件是,即当前这个Vue实例是组件。则执行initInternalComponent方法。(该方法主要就是为vm.$options添加一些属性, 后面讲到组件的时候再详细介绍)。
  • 当符合第二个条件时,即当前Vue实例不是组件。而是实例化Vue对象时,调用mergeOptions方法。

mergeOptions主要调用两个方法,resolveConstructorOptions和mergeOptions。这两个方法牵涉到了很多知识点,为了我们文章篇幅的考虑。接下来准备通过两篇博文来介绍这两个方法。

下篇博文主要介绍resolveConstructorOptions相关的内容,涉及到原型链和构造函数以及部分Vue.extend的实现,敬请期待!

点击查看更多内容
25人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消