博客更新地址啦~,欢迎访问:https://jerryyuanj.github.io/
开始之前,我们先看看src目录的结构
参考官网:Vue完整版与编译版的区别 — Vue.js
补充一下,这段判断的代码位于:src/core/instance/lifecycle.js 中的 mountComponent
方法中,
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
}
通过判断条件我们可以大致的了解到,当传递 template
并且 template
的第一个字符不是#
号,或者 el
存在的时候,runtime-only
版本会报错,提示你需要使用带compiler的完整版。具体逻辑我们后面会说,这里先有个了解。
我们使用Vue的时候都是new一个Vue实例,很显然我们要找的入口就是Vue的构造函数。
Vue的构造函数位于 src/core/instance/index.js
中,代码如下:
function Vue (options) {
// 在非生产环境下, 如果不是使用new来调用这个构造器则会报警告
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
啰嗦一句,关于如何找到这个构造函数,如果你经常看源码,那么可以凭经验找到
core/index.js
中来,这里面从其他地方import
了Vue,再顺着依赖找就可以直接定位到这个构造器了。但是标准方法应该是先看根目录下的/scripts/config.js
,这里面包含着rollup打包的各项配置,随便找一个entry
,再顺着依赖,就可以一步一步定位到Vue的构造器中。
Vue的构造函数非常简单,只执行了一句代码:
this._init(options)
这个_init()
方法从哪来,我们后面会讲。
好了,今天的源码分析到此结束~~
hahahaha,开个玩笑,接下来才是正文。
我们看到这个文件中还有五个看上去就很厉害的方法。
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
为什么说呢,因为这就像数学题一样,往往字数越少的题目越难。OK,我们先来看看这五个函数吧。
位于: src/core/instance/init.js
这个函数中其实就定义了一个方法,所以这个initMixin
实际上就做了一件事,就是在给Vue的原型添加了一个_init
方法(对,就是Vue构造器里面的唯一的方法):
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function(){
//....具体实现逻辑
}
}
虽然就一个,但是为了跟下面保持一致,还是先列出来吧:
位于:src/core/instance/state.js
我们还是像上面一样,先粗略的看看吧:
...
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function(expOrFn, cb, options){
// 返回一个函数
}
看上去也蛮清晰的,也是初始化了一些变量啊方法啊什么的,我们先列出来:
等等,慢着,这些方法我好像在哪儿看过?
soga~原来这些是Vue的API啊~~
你可以在API中找到这五个属性或方法哦:API — Vue.js
好像有点意思了吧,我们继续往下看
位于: src/core/instance/events.js
好像找到规律了,这个方法做了这些事:
Vue.prototype.$on = function(..){...}
Vue.prototype.$once = function(..){...}
Vue.prototype.$off = function(..){...}
Vue.prototype.$emit = function(..){...}
列出来:
这些方法是干啥的,我想你应该知道在哪看了吧。传送门:Vue实例 方法-事件 API — Vue.js
位于:src/core/instance/lifecycle.js
同样的:
Vue.prototype._update = function(){}
Vue.prototype.$forceUpdate = function(){}
Vue.prototype.$destroy = function(){}
列一下:
位于:src/core/instance/render.js
老样子:
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function(){}
Vue.prototype._render = function(){}
列一下…慢着,这个有点不太一样啊,这个installRenderHelpers
是干什么的?
点进去看看会发现,这实际上还是给Vue的原型添加了方法,不过这些方法是运行时用到的,相当于运行时的助手一样。
该方法位于: src/core/instance/render-helpers/index.js。
这个文件的方法就这一个,我们来看看:
/* @flow */
import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'
import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'
import { renderList } from './render-list'
import { renderSlot } from './render-slot'
import { resolveFilter } from './resolve-filter'
import { checkKeyCodes } from './check-keycodes'
import { bindObjectProps } from './bind-object-props'
import { renderStatic, markOnce } from './render-static'
import { bindObjectListeners } from './bind-object-listeners'
import { resolveScopedSlots } from './resolve-slots'
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
呃。。。也没什么嘛,就是给target扩充方法呗,这个target是我们的Vue.prototype
,所以老样子,该列还得列:
你以为结束了吗?不,还差点东西。。。。
我们上面分析的,是最最最靠里面的Vue,即最原始的。这里暴露出去的Vue,在 src/core/index.js 中又做了一些工作。实际上,其他地方引入的Vue,都是这个 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
首先的一句 initGlobalAPI(Vue)
我们先不看,下面的一些代码也是给Vue添加属性或方法的,只不过用的是ES5的Object.defineProperty()
来定义的。
首先,Vue.prototype.$isServer
和 Vue.prototype.$ssrContext
这样定义后,是只读的(无set),而且不可枚举(enumurable默认false)。然后再给Vue定义了一个FunctionRenderContext
的属性,并且也是只读的不可枚举的。至于这几个属性是干什么的,看上去是跟ssr还有render相关的,后面分析到会讲。
还有这句:
Vue.version = '__VERSION__'
很显然是当前Vue的版本,那么这个 __VERSION__
是什么东西呢?
它的定义是在: /scripts/config.js 中,rollup在构建的时候会自动给它赋值,用什么值呢?请看:
const version = process.env.VERSION || require('../package.json').version
OK, 一目了然啦。
再来看 initGlobalAPI(Vue)
, 它来自:src/core/global-api/index.js,代码也不多,参考注释:
/* @flow */
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 {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
export function initGlobalAPI (Vue: GlobalAPI) {
// config
// Vue 的全局配置对象
// 不可以使用 Vue.config = XXX 的形式来修改这个 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.
// 工具
// 注意: 这些不算是公共的API, 所以除非你很清楚它的风险,否则不要使用他们
// 所以你在 Vue 的 API 里面,看不到这四个util方法
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 给 options 添加 'components', 'directives', 'filters' 三个属性, 初始值 {}
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
// Vue.options.components 的扩充(目前只有一个 keep-alive 组件)
extend(Vue.options.components, builtInComponents)
// 插件: Vue.use
initUse(Vue)
// 混合: Vue.mixin
initMixin(Vue)
// 继承: Vue.extend
initExtend(Vue)
// 注册一些资源属性,'component', 'directive', 'filter'
// 即: Vue.component, Vue.directive, Vue.filter
initAssetRegisters(Vue)
}
OK,有点熟悉,也是给Vue扩充方法,不过这里是直接给Vue扩充而不是给Vue.prototype扩充。比较简单,而且注释上写的也比较详细,就直接罗列了:
{warn, extend, mergeOptions, defineReactive}
{components: {keepAlive}, directives: {}, filters: {}, _base: Vue}
OK,这下真的差不多了,这节表面上啥也没干,但是至少对Vue整个初始化对过程有个大概对印象了吧。从上面对分析中我们不难发现,_xxx
一般都是Vue内部对方法,$xxx
一般都是api对方法,所以当我们不知道某个 $xxx
方法是干啥的话,可以去api文档看看,至少可以带着结果去看源码,效率也高一点。
下一节我们将开始深入分析这些函数都做了哪些事。