分析导入入口
- 在Web应用的 Vue.js 构建过程的 Runtime + Compiler 版本下,我们重点关注这个版本
- 它的入口是 src/platforms/web/entry-runtime-with-compiler.js
1 ) entry-runtime-with-compiler.js 入口文件
-
现在分析一下在
import Vue
的时候,都执行了哪些事情 -
entry-runtime-with-compiler.js
/* @flow */import config from 'core/config' import { warn, cached } from 'core/util/index' import { mark, measure } from 'core/util/perf'import Vue from './runtime/index' import { query } from './util/index' import { compileToFunctions } from './compiler/index' import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'const idToTemplate = cached(id => {const el = query(id)return el && el.innerHTML })const mount = Vue.prototype.$mount Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean ): Component {el = el && query(el)/* istanbul ignore if */if (el === document.body || el === document.documentElement) {process.env.NODE_ENV !== 'production' && warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)return this}const options = this.$options// resolve template/el and convert to render functionif (!options.render) {let template = options.templateif (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {template = idToTemplate(template)/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && !template) {warn(`Template element not found or is empty: ${options.template}`,this)}}} else if (template.nodeType) {template = template.innerHTML} else {if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)}return this}} else if (el) {template = getOuterHTML(el)}if (template) {/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile')}const { render, staticRenderFns } = compileToFunctions(template, {shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile end')measure(`vue ${this._name} compile`, 'compile', 'compile end')}}}return mount.call(this, el, hydrating) }/*** Get outerHTML of elements, taking care* of SVG elements in IE as well.*/ function getOuterHTML (el: Element): string {if (el.outerHTML) {return el.outerHTML} else {const container = document.createElement('div')container.appendChild(el.cloneNode(true))return container.innerHTML} }Vue.compile = compileToFunctionsexport default Vue
-
这里可以看到
export default Vue
最终导出 Vue 对象 -
而一开始的 Vue 的来源:
import Vue from './runtime/index'
-
之后,在其原型上挂载
$mount
方法
2 ) runtime/index 文件
- 我们看下 runtime/index 文件中定义的Vue, 具体位置: src/platforms/web/runtime/index.js
import Vue from 'core/index' import config from 'core/config' import { extend, noop } from 'shared/util' import { mountComponent } from 'core/instance/lifecycle' import { devtools, inBrowser, isChrome } from 'core/util/index'import {query,mustUseProp,isReservedTag,isReservedAttr,getTagNamespace,isUnknownElement } from 'web/util/index'import { patch } from './patch' import platformDirectives from './directives/index' import platformComponents from './components/index'// install platform specific utils Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement// install platform runtime directives & components extend(Vue.options.directives, platformDirectives) extend(Vue.options.components, platformComponents)// install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop// public mount method Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean ): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating) }// ...export default Vue
- 它最终也是
export default Vue
, 而这里的 Vue 是从 core/index 而来 - import 之后,它又定义了一些全局配置,挂载在 Vue.config 之上
- 在 Vue 原型上又挂载了
__patch__
方法 和$mount
方法等
- 它最终也是
3 ) core/index 文件
- 我们再进入 core/index,具体位置: src/core/index.js
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 一开始 也是从其他地方导入而来的
import Vue from './instance/index'
- 同时,
initGlobalAPI(Vue)
这里初始化了一些 全局的 API- 这里定义了
Vue.config
, 这里是全局config - 这里的 config 定义对应的在vue官网上相关的API文档可供参考
- 还定义了
Vue.util
对象,里面有对应的方法,这里没有写在公共文档上- 这里不建议外部使用,因为里面的方法可能不稳定
- 同时,这里是给 Vue 这个对象本身扩展全局的静态方法
- 定义了
Vue.set
,Vue.delete
,Vue.nextTick
,Vue.options
方法 Vue.options
用于合并方法,里面引用了ASSET_TYPES
- 在
ASSET_TYPES
中又定义了component
,directive
,filter
三个全局方法的枚举
- 定义了
- 在
Vue.options
上又挂载了 _baseVue.options._base = Vue
extend(Vue.options.components, builtInComponents)
这里的 builtInComponents 是内置组件- 里面只有 keep-alive 组件
- 之后又调用了一系列的 init 方法
initUse(Vue)
创建了vue.use
的全局方法initMixin(Vue)
定义了一个全局的mixin
方法initExtend(Vue)
定义了Vue.extend
方法initAssetRegisters(Vue)
定义了上面ASSET_TYPES中枚举的全局 component, directive, filter 方法
- 经过一些列初始化,我们才能在业务代码中使用这些代码
- 可以进入 global-api/index 文件中查看,这里不再赘述, 参考 src/core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) {// configconst configDef = {}configDef.get = () => configif (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 = setVue.delete = delVue.nextTick = nextTickVue.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 = Vueextend(Vue.options.components, builtInComponents)initUse(Vue)initMixin(Vue)initExtend(Vue)initAssetRegisters(Vue) }
- 这里定义了
- 这里的 Vue 一开始 也是从其他地方导入而来的
4)instance/index 文件
- 再进入 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) {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 对象真正的定义的位置
- 最上面,进行环境的判断,并且限制了 必须通过 new 来实例化
- 之后执行
_init()
方法 - 之后,调用了 一系列的 Minxin 的方法
initMixin
源码中,在 Vue 的原型上挂了_init
方法stateMixin
源码中,也是在 Vue中挂载了一些方法,比如:$set
,$delete
,$watch
- … 其他都类似
- 也就是说,每个 Minxin 就是在原型上汇入一些方法
- 这里通过 function 的方式来声明 Vue, 比用 Class 好处在可以方便拆分方法的挂载
- 拆分到不同文件下的好处是,方便代码的管理,有利于代码的维护
- 这是非常值得学习的代码重构方案
5 )总结
- 所以,到现在我们知道 Vue 本身就是一个基于函数实现的类,类上挂载了很多方法和属性
- 对于 Vue 的初始化过程,总结出两个步骤
- 一个是 Vue的定义,也就是最里层的那个 Vue function 的定义
- 另一个是 属性和原型方法,全局方法,静态方法的挂载