此文章适宜筹备第一次浏览 vuejs 源码的童鞋,因为 vuejs 的源码十分多,而且散布在各个文件夹中,因而想要读懂源码,须要理清整个框架的脉络。此文章就是从编译入口登程,找到源码中的关键点。
筹备工作
打包源码
浏览器调试比单纯的浏览源码更有效率,那么如何为 vuejs 增加 sourceMap?
- fork vue 源码仓库到本人的 github 仓库中,这样,能够轻易增加正文和批改。
- 下载我的项目,关上 package.json 文件,找到文件中:
- vuejs 应用 rollup 打包,在 dev 命令最初增加
--sourcemap
。 - 执行
npm run dev
进行打包,生成带 sourcemap 的 vue 文件。 - 找到 examples 文件夹,轻易找一个用例,将 vue 文件的援用地址改为新打包生成的文件。
- 用浏览器关上 html 文件,关上控制台,就能够看到源码。
理解打包文件
在命令行中执行npm run build
,会打包所有版本的 vue 文件,打包后果如下:
其中:
- common 示意合乎 commonjs 标准的文件。
- ems 示意合乎 ES Module 标准的文件。
- dev 示意文件内容未压缩,是可读的。
- prod 示意文件内容是压缩过的。
- runtime
示意运行时版本,不蕴含模版编译性能。也就是在申明组件的时候无奈编译 template 模版,只能应用 render 函数。vue-cli 构建的我的项目中,因为打包的时候会将 template 模版编译成 render 函数,所以其打包后援用的 vue 版本为运行时版本。
在 vue-cli 创立的我的项目中执行 vue inspect > out.js。能够将所有的 webpack 配置输入到 out.js 文件中,查看其中的 resolve 配置能够看到其打包的 vue 版本:
resolve: {
alias: {vue$: 'vue/dist/vue.runtime.esm.js'}
}
- 未加 common,es
示意是 umd
标准文件,此文件能够反对 commonjs,ES Module,AMD,或者间接通过 window.Vue 形式援用。
- 未加 rentime
示意残缺版本,蕴含运行时和编译器。整体代码比运行时版本多。
入口文件
vuejs 我的项目的打包入口文件,能够当作是源码浏览的入口文件。
打包的时候执行的是如下命令:
rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap
其中 scripts/config.js 为 rollup 配置文件所在门路,rollup 配置文件要求导出一个对象,其中 input
属性指定打包入口文件。
scripts/config.js
在该文件的最初:
if (process.env.TARGET) {module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
因为执行的打包命令中蕴含--environment TARGET:web-full-dev
,所以此时 process.env.TARGET 值为 web-full-dev,也就是通过
genConfig 获取配置并导出。
在 getConfig 办法中,通过 const opts = builds[name]
获取内置的配置,其中 name 为 web-full-dev。通过 opts.entry
指定 input 属性值,其最终值为platforms/web/entry-runtime-with-compiler.js
。
platforms 文件加中寄存的是和平台相干的代码,其中 web 文件夹是和 web 相干的代码,weex 文件夹是和 weex 相干的代码。
platforms/web/entry-runtime-with-compiler.js
此文件的性能并不简单,只是批改了 Vue 原型上的 $mount
办法和增加静态方法compile
:
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {// 具体逻辑}
// 静态方法 compile
Vue.compile = compileToFunctions
在原有 $mount 办法的根底上增加判断,当 vue 组件没有定义 render 时,判断是否传入了 templete,如果传了,就将其编译成 render。具体逻辑可精简为:
const options = this.$options
// 如果没有传入 render
if (!options.render) {
// 获取 template
let template = options.template
// .... 此处蕴含 template 的各种状况判断
if (template) {
// 编译 template 为 render 函数
const {render, staticRenderFns} = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 在 options 下面增加 render 函数
options.render = render
// 动态 render 函数,用于优化 Dom 渲染过程
options.staticRenderFns = staticRenderFns
}
}
// 调用原有的 mount 办法
return mount.call(this, el, hydrating)
此处
compileToFunctions
是模版编译的入口,等到前面编译局部再持续。
platforms/web/runtime/index.js
entry-runtime-with-compiler.js
文件中的 Vue 类引入自 platforms/web/runtime/index.js
这个文件,此文件为 Vue 类增加了 web 平台特有性能,如 Dom 操作。
此文件能够分为三大块:
- 扩大 config
// 增加 web 平台特有的一些辅助办法
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag // 判断是否是保留 tag,如 input
Vue.config.isReservedAttr = isReservedAttr // 是否是保留属性
Vue.config.getTagNamespace = getTagNamespace // 获取元素的命名空间
Vue.config.isUnknownElement = isUnknownElement
此处增加的办法大部分是 Vue 外部应用,平时工作上简直用不到,所以不再详解。
- 增加全局内置组件和指令
// 增加 web 平台相干的全局内置组件和指令
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
此处增加的组件有:transition
和 transition-group
。
此处增加的指令有:v-model
和v-show
。
- 增加原型办法
增加了两个要害办法:Dom 挂载和 Dom 更新。
// 增加虚构 Dom 更新操作
Vue.prototype.__patch__ = inBrowser ? patch : noop
// 增加挂载办法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefined
// 渲染虚构 Dom
return mountComponent(this, el, hydrating)
}
mountComponent
是 Vnodes 渲染的入口办法,后续会具体降到 Vnodes 渲染过程。
core/index.js
platforms/web/runtime/index.js
中的 Vue 类引入自 core/index.js
文件。
core 文件夹蕴含了 vue 外围代码,其与平台没有任何关系。
此文件只蕴含一个要害代码:
// 为 Vue 类增加全局静态方法如 Vue.extend, Vue.component 等。initGlobalAPI(Vue)
// ... 残余的是和 ssr 服务端渲染相干的全局属性,此处省略。
initGlobalAPI
定义在 core/global-api/index.js
中:
export function initGlobalAPI(Vue: GlobalAPI) {
// 定义 config
Object.defineProperty(Vue, 'config', configDef)
// 帮忙函数,不要间接应用,vuejs 不保障会正确执行
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// 设置全局的 set,delete 和 nextTick
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 增加 observable 办法
Vue.observable = <T>(obj: T): T => {observe(obj)
return obj
}
// 创立全局的 options 对象
Vue.options = Object.create(null)
// 初始化 options 中的 components,directives,filters 三个属性。ASSET_TYPES.forEach(type => {Vue.options[type + 's'] = Object.create(null)
})
// Vue.use
initUse(Vue)
// Vue.mixin
initMixin(Vue)
// Vue.extend
initExtend(Vue)
// Vue.component, Vue.directive, Vue.filter
initAssetRegisters(Vue)
}
core/instance/index.js
core/index.js
中的 Vue 类引入自 core/instance/index.js
文件,此文件定义了 Vue 的构造函数和实例办法。
定义构造函数
function Vue (options) {
// 确保 Vue 不会被当作函数调用
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {warn('Vue is a constructor and should be called with the `new` keyword')
}
// 执行_init 办法,此办法在 initMixin 中定义
this._init(options)
}
申明实例属性办法
// 增加实例办法属性
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
此处体现了 vuejs 针对代码逻辑文件的划分,将不同的性能划分到不同的文件中。
- initMixin
定义在 core/instance/init.js 文件中。
export function initMixin(Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {// ... 省略}
}
该文件次要为 Vue 实例增加_init 办法,当创立 Vue 实例的时候,此办法会被立刻调用。
下一部分 Vue 初始化过程的入口就是此办法。
- stateMixin
定义在 core/instance/state.js
中:
export function stateMixin(Vue: Class<Component>) {
// 定义 $data 属性
Object.defineProperty(Vue.prototype, '$data', dataDef)
// 定义 $props 属性
Object.defineProperty(Vue.prototype, '$props', propsDef)
// 定义 get,set 办法
Vue.prototype.$set = set
Vue.prototype.$delete = del
// 定义 watch 办法
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {// ... 省略,在响应式源码局部详解}
}
- eventsMixin
定义在 core/instance/events.js
中:
export function eventsMixin(Vue: Class<Component>) {
// 定义 $on
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {// ... 省略}
// 定义 $once
Vue.prototype.$once = function (event: string, fn: Function): Component {// ... 省略}
// 定义 $off
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {// ... 省略}
// 定义 $emit
Vue.prototype.$emit = function (event: string): Component {// ... 省略}
}
这里定义的事件注册办法逻辑很近似,都是将注册的办法存储在 Vue 实例的 _events
属性中。
_events
属性是在_init 办法执行的过程中初始化的。
- lifecycleMixin
定义在 core/instance/lifecycle.js
中:
export function lifecycleMixin(Vue: Class<Component>) {
// 定义_update 办法
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
// 调用__patch__执行更新渲染
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
// 定义强制更新办法
Vue.prototype.$forceUpdate = function () {}
// 定义销毁办法
Vue.prototype.$destroy = function () {}
}
此文件中定义的_update 办法是响应式过程中的要害一环,作为观察者 Watcher 的回调函数,当 vm 的数据放生变动的时候,会被调用。
- renderMixin
定义在 core/instance/render.js
中:
export function renderMixin(Vue: Class<Component>) {
// 定义 nextTick 办法
Vue.prototype.$nextTick = function (fn: Function) {//。。。省略}
Vue.prototype._render = function (): VNode {// 外部调用 options 中的 render 办法,生成虚构 Dom}
}
_render 办法将配合_update,_update 更新时比照的是虚构 Dom,而_render 办法就是用于生成虚构 Dom。
总结
至此,vuejs 整个我的项目的 web 平台文件关系理顺,如下:
core/instance/index.js
:申明 Vue 构造函数和实例办法属性。core/index.js
:为 Vue 增加静态方法。platforms/web/runtime/index.js
:针对 web 平台,增加 Dom 渲染和加载办法。platforms/web/entry-runtime-with-compiler.js
:扩大 Vue 模版编译能力,如果是不带编译的运行时版本就无需针对 template 进行解决。