本文,我将会讲述从打包入口开始,了解整个项目的初始化实例流程。
第一步,当然是先把vue项目打包到本地啦。需要注意的是,vue项目使用rollup打包,可以提前安装
https://github.com/vuejs/vue // 项目地址
npm i rollup -g // 安装rollup
为方便调试,我们在指令中开启sourcemap,然后执行命令npm run dev即可。
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
完成了前置准备工作之后,我们进入到项目中来看。
首先看到上述指令中的-c scripts/config.js 与 TARGET:web-full-dev,分别代表的是配置文件以及输入文件配置项,也就是以下信息,此时,我们可以找到相应的入口文件及各项配置。
// Runtime+compiler development build (Browser)
'web-full-dev': {
// resolve函数负责解析别名,具体使用不再赘述
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
让我们进入到此文件中,可以看到它只在当前做了一件事情--扩展$mount函数,实现render方法
// web/entry-runtime-with-compiler.js
// Vue实例创建过程需要继续向上寻找
import Vue from './runtime/index'
// 扩展mount函数,生成render并挂载到options
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// query以获取当前元素节点
el = el && query(el)
const options = this.$options
// resolve template/el and convert to render function
// 获取当前组件render函数并挂载到options上
if (!options.render) {
let template = options.template
// 如果已有template
if (template) {
if (typeof template === 'string') {
// 如果是一个id
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
} else if (template.nodeType) {
//如果template是一个元素
template = template.innerHTML
} else {
// 报错信息
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 如果template不存在,元素存在
template = getOuterHTML(el)
}
// 此时已经获取到template
if (template) {
// template => render,这是vue的编译环节,主要功能为将template编译成render函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 挂载到options下以便调用
options.render = render
options.staticRenderFns = staticRenderFns
}
}
// 扩展后,我们仍需要实现原函数
return mount.call(this, el, hydrating)
}
根据导入信息继续往上到./runtime/index,该文件依然没有完成Vue的构建,它完成了以下两件事:1. patch方法的安装 2. $mount方法的实现。
./runtime/index
import Vue from 'core/index'
import { mountComponent } from 'core/instance/lifecycle'
import { patch } from './patch'
// 安装patch方法,此为树的构建方法
Vue.prototype.__patch__ = inBrowser ? patch : noop
// $mount方法的实现
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
// mount方法实质上就是mountComponent,此为挂载环节,这是Vue实例化的最后一步
return mountComponent(this, el, hydrating)
}
再往上,我们到了core/index。core包是Vue的核心包。
// ./core/index
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
// 完成了全局API的挂载,包括use/component/directive/filter/set/delete/nextTick等
initGlobalAPI(Vue)
全局化不是我们的重点,我们接着往上。
// ./instance/index
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) {
this._init(options)
}
initMixin(Vue) // 实现Vue._init
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
终于,我们找到了Vue实例构建的地址,可是我们看到,它只调用了一个_init方法,找不到任何引用的痕迹。别急,初始化方法就藏在下面的几个混入方法中。让我们进入的initMixin函数中一探究竟。
./init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
vm._uid = uid++
vm._isVue = true
// 合并选项。在这里我们会把所有之前挂载到options上的内容合并到vm.$options上
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._self = vm
// 创建生命周期相关内容,和生命周期钩子不同,这里代表的是组件自身的生命。包括$parent,$root,$children,$refs以及一些相关参数,其中还有关于抽象组件(keep-alive)的父级绑定哦!
initLifecycle(vm)
// 创建组件相关的事件选项,获取到父组件的listeners
initEvents(vm)
// 进行了插槽以及$createElement函数(h函数)的挂载,还完成了$attrs,$listeners的响应式处理
initRender(vm)
// 调用钩子函数
callHook(vm, 'beforeCreate')
// 初始化inject
initInjections(vm) // resolve injections before data/props
// 在这里对整个Vue项目的数据进行了初始化,关键点在于响应式的处理
initState(vm)
// 初始化provide
initProvide(vm) // resolve provide after data/props
// 调用钩子函数
callHook(vm, 'created')
// 如果选项中已有el元素,将直接进行挂载,无需外部调用。
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
我们从打包入口进入,一步步往上查找,在过程中,得知了render函数的实现、patch方法的安装、$mount方法的实现(以上方法在编译,更新阶段也起到了重要作用),最后我们查找到了实现Vue初始化的方法,以及了解了该方法中实现的大致内容与方法。