文章目录
Vue2 源码解读一:Global API
1、准备源码,添加调试
2、Flow静态类型检查
3、Global API
4、global-api/index.js
5、global-api/assets.js
6、global-api/extend.js
7、global-api/mixin.js
8、global-api/use.js
Vue2 源码解读二:mergeOptions
Vue2 源码解读三:Vue实例
Vue2 源码解读四:Observer模块
Vue2 源码解读一:GlobalAPI
1、准备源码,添加调试
- 下载Vue2 源码 Vue Release ;
- 安装依赖
cnpm install
,使用cnpm安装; - 修改package.json中的scripts,dev脚本添加
--sourcemap
,改为"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap"
; - 启动dev脚本
cnpm run dev
; - 在demo中引用 源码目录/dist/vue.js,即可通过F12,在Sources中给源码添加断点调试了;
- 入口文件
./src/core/index.js
2、Flow静态类型检查
Flow 是FaceBook发布的开源JavaScript 静态类型检查器。
Vue 2.x版本使用Flow来做静态类型检查,语法类似于TypeScript,在flow文件夹中是一些类型定义文件,详细使用请查看Flow官网。
Vue 3.x版本已使用TypeScript重写,Flow也随之淘汰。
3、Global API
先看一下入口文件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'
// 初始化全局API
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()
,所以我们重点关注src/core/global-api文件夹的内容:
文件 | 作用 |
---|---|
global-api/assets.js | 初始化component、directive、filter全局方法 |
global-api/extend.js | 初始extend全局方法,Vue.extend() |
global-api/mixin.js | 初始mixin全局方法,Vue.mixin() |
global-api/use.js | 初始use全局方法,Vue.use() |
global-api/index.js | 调用上面的初始化方法,以及添加del、set、nextTick等全局方法 |
4、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 { observe } from 'core/observer/index'
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
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.'
)
}
}
// Vue.config 全局配置对象
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.
// 扩展的Util方法
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// 添加全局的set、del、nextTick
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
// 添加显示的全局observable方法
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
// 重置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
// 添加全局组件,keep-alive
extend(Vue.options.components, builtInComponents)
// 初始化Vue.use、Vue.mixin、Vue.extend
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
// 初始化Vue.component、Vue.directive、Vue.filter
initAssetRegisters(Vue)
}
从入口文件上看,总体分为两个部分:
- 全局属性
config
:全局配置对象options
:存放初始化的数据,在创建Vue实例时传入的配置对象最终要与这份配置属性合并
- 全局方法
util
:暴露了一些辅助方法,官方并不建议使用set
:设置响应式数据,触发视图更新。常用在更新数组中单个数据,添加对象属性delete
:删除数组元素、删除对象属性,触发视图更新nextTick
:结束此轮循环后执行回调。常用与等待dom更新、加载完毕后use
:安装插件,会规避重复插件mixin
:混合插件的功能extend
:创建基于Vue的子类并扩展初始内容component
:注册全局组件directive
:注册全局指令filter
:注册全局过滤器
下面是del、set的实现:
// src/core/observer.js
/**
* 全局的del方法,Vue.del
* 数组:通过splice删除,对象:通过delete删除
* 在必要的时候通知更改
*/
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 有效数组,调用splice删除
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 这里的splice已经被Vue拦截,会触发dep.update
target.splice(key, 1)
return
}
// 获取Vue监听器
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
if (!hasOwn(target, key)) {
return
}
// 对象:通过delete移除
delete target[key]
if (!ob) {
return
}
// 通知更新
ob.dep.notify()
}
/**
* 全局的set方法,Vue.set
* 给新的属性添加响应式
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 有效数组,调用splice添加
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// 这里的splice已经被Vue拦截,会触发dep.update
target.splice(key, 1, val)
return val
}
// 如果target属性已存在,直接设置即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 如果target不存在监听器,直接设置属性
if (!ob) {
target[key] = val
return val
}
// 添加响应式属性。后面再详解
defineReactive(ob.value, key, val)
// 通知更新
ob.dep.notify()
return val
}
5、global-api/assets.js
/* @flow */
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
// ['component', 'directive', 'filter']
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
// 返回指定asset
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// 注册组件
if (type === 'component' && isPlainObject(definition)) {
// 设置组件名称
definition.name = definition.name || id
// 完善组件内容
definition = this.options._base.extend(definition)
}
// 注册指令
if (type === 'directive' && typeof definition === 'function') {
// 如果传的是function,则设置bind、update
definition = { bind: definition, update: definition }
}
// 添加到options中
this.options[type + 's'][id] = definition
return definition
}
}
})
}
6、global-api/extend.js
/* @flow */
import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'
export 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.
*/
// 设置当前组件cid为0
Vue.cid = 0
// 递增的子组件ID
let cid = 1
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
// 用扩展选项的_Ctor属性判断是否已扩展
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
// 设置默认name为父级的name
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
// 定义子类
const Sub = function VueComponent (options) {
this._init(options)
}
// 子类原型链集成
Sub.prototype = Object.create(Super.prototype)
// 重置子类的构造器
Sub.prototype.constructor = Sub
// 自增cid
Sub.cid = cid++
// 合并父级options和扩展option
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// super属性
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
// 初始化子类的prop
if (Sub.options.props) {
initProps(Sub)
}
// 初始化子类的computed
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
// 一些属性、方法继承于父级
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
// 保留Super的options,在Vue实例化时检测options更新
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
// 缓存子类构造函数
cachedCtors[SuperId] = Sub
return Sub
}
}
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}
7、global-api/mixin.js
/* @flow */
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
// merge合并
this.options = mergeOptions(this.options, mixin)
return this
}
}
mixin混合,其实就是进行合并options操作。
8、global-api/use.js
/* @flow */
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 已安装的插件集合
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 检测当前组件是否已安装
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
// 把参数列表转成数组
const args = toArray(arguments, 1)
// 把this插入到参数列表的第一个
args.unshift(this)
if (typeof plugin.install === 'function') {
// 如果插件中,包含install方法,则调用插件的install方法
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
// 如果插件本身就是一个方法,则直接调用
plugin.apply(null, args)
}
// 标记此插件已注册
installedPlugins.push(plugin)
return this
}
}