此文章适合准备第一次阅读vuejs源码的童鞋,因为vuejs的源码很是多,并且分布在各个文件夹中,所以想要读懂源码,须要理清整个框架的脉络。此文章就是从编译入口出发,找到源码中的关键点。html
浏览器调试比单纯的阅读源码更有效率,那么如何为vuejs添加sourceMap?vue
--sourcemap
。npm run dev
进行打包,生成带sourcemap的vue文件。在命令行中执行npm run build
,会打包全部版本的vue文件,打包结果以下:node
其中:webpack
表示运行时版本,不包含模版编译功能。也就是在声明组件的时候没法编译template模版,只能使用render函数。 vue-cli构建的项目中,因为打包的时候会将template模版编译成render函数,因此其打包后引用的vue版本为运行时版本。git
在vue-cli建立的项目中执行vue inspect > out.js。能够将全部的webpack配置输出到out.js文件中,查看其中的resolve配置能够看到其打包的vue版本:github
resolve: { alias: { vue$: 'vue/dist/vue.runtime.esm.js' } }
表示是umd
规范文件,此文件能够支持commonjs,ES Module,AMD,或者直接经过window.Vue方式引用。web
表示完整版本,包含运行时和编译器。总体代码比运行时版本多。vue-cli
vuejs项目的打包入口文件,能够看成是源码阅读的入口文件。npm
打包的时候执行的是以下命令:json
rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap
其中scripts/config.js为rollup配置文件所在路径,rollup配置文件要求导出一个对象,其中input
属性指定打包入口文件。
在该文件的最后:
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相关的代码。
此文件的功能并不复杂,只是修改了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
是模版编译的入口,等到后面编译部分再继续。
entry-runtime-with-compiler.js
文件中的Vue类引入自platforms/web/runtime/index.js
这个文件,此文件为Vue类添加了web平台特有功能,如Dom操做。
此文件能够分为三大块:
// 添加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渲染过程。
platforms/web/runtime/index.js
中的Vue类引入自core/index.js
文件。
core文件夹包含了vue核心代码,其与平台没有任何关系。
此文件只包含一个关键代码:
// 为Vue类添加全局静态方法如Vue.extend, Vue.component等。 initGlobalAPI(Vue) // ... 剩余的是和ssr服务端渲染相关的全局属性,此处省略。
定义在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/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针对代码逻辑文件的划分,将不一样的功能划分到不一样的文件中。
定义在core/instance/init.js文件中。
export function initMixin(Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { // ... 省略 } }
该文件主要为Vue实例添加_init方法,当建立Vue实例的时候,此方法会被当即调用。
下一部分Vue初始化过程的入口就是此方法。
定义在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 { // ... 省略,在响应式源码部分详解 } }
定义在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方法执行的过程当中初始化的。
定义在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的数据放生变化的时候,会被调用。
定义在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进行处理。