# 了解vue
网上对vue源码的分析很多,大致了解下,先宏观再微观 阅读源码前需了解的有
- es6
- Rollup(vue采用的构建工具,打包库适用)
- flow(类型检查,和typescript类似)
├── src ----------------------------------- 这个是最应该关注的目录,包含了源码
│ ├── compiler -------------------------- 编译器代码的存放目录,将 template 编译为 render 函数
│ ├── core ------------------------------ 存放通用的,与平台无关的代码
│ │ ├── observer ---------------------- 响应系统,包含数据观测的核心代码
│ │ ├── vdom -------------------------- 包含虚拟DOM创建(creation)和打补丁(patching)的代码
│ │ ├── instance ---------------------- 包含Vue构造函数设计相关的代码
│ │ ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码
│ │ ├── components -------------------- 包含抽象出来的通用组件
│ ├── server ---------------------------- 包含服务端渲染(server-side rendering)的相关代码
│ ├── platforms ------------------------- 包含平台特有的相关代码,不同平台的不同构建的入口文件也在这里
│ │ ├── web --------------------------- web平台
│ │ │ ├── entry-runtime.js ---------- 运行时构建的入口,不包含模板(template)到render函数的编译器,所以不支持 `template` 选项,我们使用vue默认导出的就是这个运行时的版本。大家使用的时候要注意
│ │ │ ├── entry-runtime-with-compiler.js -- 独立构建版本的入口,它在 entry-runtime 的基础上添加了模板(template)到render函数的编译器
│ │ │ ├── entry-compiler.js --------- vue-template-compiler 包的入口文件
│ │ │ ├── entry-server-renderer.js -- vue-server-renderer 包的入口文件
│ │ │ ├── entry-server-basic-renderer.js -- 输出 packages/vue-server-renderer/basic.js 文件
│ │ ├── weex -------------------------- 混合应用
│ ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包
│ ├── shared ---------------------------- 包含整个代码库通用的代码
构建相关=> 运行时版 + Compiler = 完整版
,compiler将字符串模板编译为 render 函数
# 构造函数
打开./instance/index.js
// 从五个文件导入五个方法(不包括 warn)
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'
// 定义 Vue 构造函数
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)
}
// 将 Vue 作为参数传递给导入的五个方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// 导出 Vue
export default Vue
vue实际上就是一个用 Function 实现的类,那为什么不用Class实现呢,后面有很多 xxxMixin 的函数调用,并把 Vue 当参数传入,它们的功能都是给 Vue 的 prototype 上扩展一些方法,Vue 按功能把这些扩展分散到多个模块中去实现,而不是在一个模块里实现所有,这种方式是用 Class 难以实现的。这么做的好处是非常方便代码的维护和管理
先看initMixin
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ... _init 方法的函数体,
}
}
// 添加_init内部初始化方法 在上面构造函数上有执行
再看stateMixin
export function stateMixin (Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function (newData: Object) {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
//设置set让 $data,$props只读
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
//添加$set $delete
Vue.prototype.$set = set
Vue.prototype.$delete = del
//添加$watch
Vue.prototype.$watch = function {
//$watch 函数体
}
}
接下来是eventsMixin
//添加4个事件方法
export function eventsMixin (Vue: Class<Component>) {
Vue.prototype.$on = function {
//$on
}
Vue.prototype.$once = function {
//$once
}
Vue.prototype.$off = function {
//$off
}
Vue.prototype.$emit = function{
// $emit
}
}
下一个是lifecycleMixin
//添加3个方法
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function{}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}
}
最后一个renderMixin
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
//$nextTick
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode{}
//在vue原型添加一系列方法
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
}
# vue构造函数的静态方法属性(全局api)
打开core/index.js
//Vue 的出生文件导入 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
})
//ssr相关
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 的版本号
Vue.version = '__VERSION__'
export default Vue
initGlobalAPI
export function initGlobalAPI (Vue: GlobalAPI) {
// 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.'
)
}
}
//加config
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// 以及 util 下的四个方法都不被认为是公共API的一部分,要避免依赖他们,
//但是你依然可以使用,只不过风险你要自己控制
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Vue.options = Object.create(null)//空对象
//ASSET_TYPES=['component', 'directive', 'filter']
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
//extend来自于shared/util.js,builtInComponents 的属性混合到 Vue.options.components
//builtInComponents 代码
//import KeepAlive from './keep-alive'
// export default {
// KeepAlive
// }
extend(Vue.options.components, builtInComponents)
//到这options
// Vue.options = {
// components: {
// KeepAlive
// },
// directives: Object.create(null),
// filters: Object.create(null),
// _base: Vue
// }
//use方法,安装插件
function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// ...
}
}
initUse(Vue)
function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
//合并options
this.options = mergeOptions(this.options, mixin)
return this
}
}
initMixin(Vue)
function initExtend (Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0
let cid = 1
//Class inheritance
Vue.extend = function (extendOptions: Object): Function {
// ...
}
//添加cid extend
}
initExtend(Vue)
function initAssetRegisters (Vue: GlobalAPI) {
//Create asset registration methods.
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// ......
}
})
}
//vue又多三个方法component,directive,filter
initAssetRegisters(Vue)
}
# 平台化
Vue是一个 Multi-platform 的项目(web和weex),不同平台可能会内置不同的组件、指令,或者一些平台特有的功能,主要看web平台
原来的config在core/config.js
中
Vue.config = {
optionMergeStrategies: Object.create(null),
silent: false,
productionTip: process.env.NODE_ENV !== 'production',
devtools: process.env.NODE_ENV !== 'production',
performance: false,
errorHandler: null,
warnHandler: null,
ignoredElements: [],
keyCodes: Object.create(null),
isReservedTag: no,
isReservedAttr: no,
isUnknownElement: no,
getTagNamespace: noop,
parsePlatformTagName: identity,
mustUseProp: no,
_lifecycleHooks: LIFECYCLE_HOOKS
}
打开platforms/web/runtime/index.js
// 覆盖初始化的config
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
//安装平台指令和组件
platformDirectives={
model,
show
}
extend(Vue.options.directives, platformDirectives)
platformComponents={
Transition,
TransitionGroup
}
extend(Vue.options.components, platformComponents)
//vue.options变为
Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: Object.create(null),
_base: Vue
}
//是浏览器__patch__为patch方法,否则为空函数noop
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method 添加$mount方法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
# with compiler
运行版本已经成型,打开entry-runtime
import Vue from './runtime/index'
export default Vue
完整版的 Vue,入口文件是 entry-runtime-with-compiler.js
// 导入 运行时 的 Vue
import Vue from './runtime/index'
// ... 其他 import 语句
// 从 ./compiler/index.js 文件导入 compileToFunctions
import { compileToFunctions } from './compiler/index'
// 根据 id 获取元素的 innerHTML
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
// 使用 mount 变量缓存 Vue.prototype.$mount 方法
const mount = Vue.prototype.$mount
// 重写 Vue.prototype.$mount 方法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// ... 函数体省略
}
/**
* 获取元素的 outerHTML
*/
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 上添加一个全局API `Vue.compile` 其值为上面导入进来的 compileToFunctions
Vue.compile = compileToFunctions
// 导出 Vue
export default Vue