上篇博文中说到了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. // 优化组件实例,因为动态选项合并很慢,并且也没有组件的选项需要特殊对待 // 优化components属性 initInternalComponent(vm, options) } else { // 传入的options和vue自身的options进行合并 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) // 添加slot属性 callHook(vm, 'beforeCreate') // 调用beforeCreate钩子 initState(vm) // 初始化数据,进行双向绑定 state/props initProvide(vm) // resolve provide after data/props 注入provider的值到子组件中 callHook(vm, 'created') // 调用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) // 把模板转换成render函数 } }我们逐一来分析上述代码。首先缓存当前的上下文到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的实现,敬请期待!