Vue应用从new Vue开始,构造函数接收一个包含组件选项的对象为参数,其中有一个render方法用于将根组件渲染为vnode;再调用$mount函数,该函数接受一个DOM节点,这样就成功的创建了一个Vue应用了。
import Vue from 'vue';
import App from './App';
new Vue({
render: h => h(App)
}).$mount('#root');
new Vue时构造函数内部做了什么?App又是一个什么?先看下Vue构造函数是什么。
Vue构造函数
由于Vue是一个支持多平台的JavaScript框架,不同平台有着不同的渲染机制,所以Vue在源码的目录结构设计上分为多个模块,首先将核心逻辑放在src/core目录下维护,而web平台的逻辑维护在src/platforms/web下,模板编译的核心逻辑维护在src/compiler,web相关的编译逻辑维护在src/platforms/web/compiler内。所以web应用程序入口文件导入的Vue是从platforms/web导出的并且对web端进行了特有的功能扩展。
web平台
web平台的代码维护在src/platforms/web目录下,该目录有多个入口文件。entry-runtime-with-compiler.js是包含compiler和runtime的入口,而entry-runtime.js是仅包含runtime的入口,引用这个文件导出的Vue构造函数时,需使用vue-loader先将template转换成render函数(本篇从runtime的Vue说起)。
上图是源码目录的截图,entry-runtime仅作为统一出口,web端的逻辑还是在platforms/web/runtime/index.js中实现。先看下platforms/web/runtime/index.js源码
// platforms/web/runtime/index.js
import Vue from 'core/index'
import { patch } from './patch'
import { mountComponent } from 'core/instance/lifecycle'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
Vue.prototype.__patch__ = inBrowser ? patch : noop
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
以上代码做了以下三件事:
- 将web端的指令(v-model, v-show)和内置组件
transition,transition-group混合到Vue.options.directives和Vue.options.components对象内 - 在浏览器环境中定义了渲染dom的
patch方法 - 定义了渲染到dom的入口
$mount方法
$mount方法接受一个DOM字符串或DOM节点,调用mountComponent并返回,该参数接受三个参数
thisVue根实例对象elVue应用程序挂载点hydratingssr相关
core
从web端的runtime/index.js中并没有看到Vue构造函数的实现而是从src/core/index.js中引入,src/core/index.js代码如下(但此处任不是Vue构造函数定义的地方)
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
核心逻辑入口对Vue构造函数也进行了一次扩展,如原型对象、Vue构造函数对象(函数也是对象)的扩展。
原型对象扩展
- 添加
$isServer属性,用于获取当前是否是在服务端环境 - 添加
$ssrContext属性,用于获取ssrContext上下文
Vue静态属性扩展
initGlobalAPI函数接受Vue构造函数作为入参,并在函数内部对Vue构造函数对象做扩展,主要是给Vue构造函数添加一下静态方法和属性。
import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'
export function initGlobalAPI (Vue) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = (obj) => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
以上代码可以看到initGlobalAPI内部将一些常用的全局API添加到Vue对象上
Vue.set向响应式对象中添加一个属性,并确保这个属性也同样是响应式的Vue.delete响应式对象被删除属性时,确保能触发更新视图(由于Vue响应式系统不能检测到属性被删除)Vue.observable手动将一个非响应式对象转换成响应式对象Vue.use安装一个Vue插件Vue.mixin全局注册一个混入,混入会被合并到vm.$options内,所以会影响所有创建的Vue实例Vue.extend通过基础Vue构造器,创建子构造函数VueComponent,参数是一个包含组件选项的对象Vue.component创建或获取一个全局子构造函数(组件),创建时被添加到vm.options.components对象中Vue.directive创建或获取一个全局指令,创建时,被添加到vm.options.directives对象中Vue.filter创建或获取一个全局过滤器,创建时,被添加到vm.options.filters对象中
同时创建了Vue.options空对象,并在Vue.options中创建了components,directives,filters,_base属性,这些options最终会以$options对象挂载到实例上。
_base在构造子构造函数时被使用;Vue.options.components内添加了一个内置组件keep-alive,这些组件都是全局注册的组件,可以在子组件中直接使用而无需引入和局部注册。以下代码就是构造Vue.options对象的实现。
// ASSET_TYPES = ['component', 'directive', 'filter']
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
core/runtime
从src/core/index文件中可以看到Vue是从./instance/index.js文件中引入,所以真正定义Vue构造函数的是src/core/instance/index.js文件内
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
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)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
以上代码可以看到Vue构造函数仅仅是一个接受options的普通函数,内部调用了this._init方法。之前看到的Vue构造函数的扩展都没有_init实例方法,所以_init应该是在core/runtime中实现的。接下来看Vue构造函数下方的几个函数调用。
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
initMixin定义了Vue.prototype._init方法,该方法既时创建实例时调用的函数,初始化实例的一些属性与方法stateMixin定义了Vue.prototype.$set,Vue.prototype.$delete,Vue.prototype.$watch方法和Vue.prototype.$data,Vue.prototype.$props属性,都是与Vue实例的状态相关eventMixin定义了Vue.prototype.$on,Vue.prototype.$emit,Vue.prototype.$once,Vue.prototype.$off方法,实现了Vue自定义事件系统lifecycleMixin定义了Vue.prototype._update,Vue.prototype.$forceUpdate,Vue.prototype.$destory方法,实现了Vue实例的更新与卸载renderMixin定义了Vue.prototype._render,Vue.prototype.$nextTick方法,用于创建vnode
所以在我们web应用程序中导入的Vue时,它是一个扩展了很多原型属性和方法的构造函数:
原型对象方法属性见下图
回答开头的问题:new Vue时构造函数内部做了什么?App又是一个什么?
1、new Vue时创建了一个Vue实例,Vue构造函数内部调用了Vue.prototype._init方法,该方法完成了实例属性与方法的初始化,若提供了el属性,会调用$mount方法完成整个渲染流程。
2、App是一个组件选项的对象,h(App)内部会将App构造成一个VueComponent子类构造函数,后续流程会将其创建为一个组件实例对象。
总结
Vue构造函数利用了原型链继承,将初始化实例方法、实例状态操作方法、自定义事件方法、vnode渲染与更新操作方法挂载在原型上这样所有实例都继承这些方法。同时将一些参数配置与全局方法添加到Vue对象上。