Vue 类在定义之后一直到被入口暴露出来会经历多次的再次封装,这里我们就先看看第一次被二次封装时往 Vue 这个类上都添加了那些东西;在 src/core/index.js
文件里::
//混入全局API
initGlobalAPI(Vue)
//响应式绑定服务端渲染相关属性和方法
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
return this.$vnode && this.$vnode.ssrContext
}
})
// 为ssr运行时助手安装暴露FunctionalRenderContext
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
//版本
Vue.version = '__VERSION__'
//再次暴露Vue
export default Vue
这里主要是执行 initGlobalAPI
混入一些全局的 API;在 src/core/global-api/index.js
文件中:
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.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
// 公开实效的方法。
// 注意:这些不被认为是公共API的一部分-避免依赖它们,除非你意识到风险。
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// 这用于标识“基”构造函数,以便在Weex的多实例场景中扩展所有普通对象组件。
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
文章目录
1、Vue.config
这里劫持了 Vue 的 config 属性,使的无法对其进行修改;
export default ({
//选择合并策略
optionMergeStrategies: Object.create(null),
//是否取消警告
silent: false,
//在启动时显示生产模式提示信息
productionTip: process.env.NODE_ENV !== 'production',
//是否启用devtools
devtools: process.env.NODE_ENV !== 'production',
//是否记录性能
performance: false,
//监视器错误的错误处理程序
errorHandler: null,
//观察程序警告的警告处理程序
warnHandler: null,
//忽略某些自定义元素
ignoredElements: [],
//v - on的自定义用户keyCode
keyCodes: Object.create(null),
//检查是否保留了标记,以便它不能注册为组件。这取决于平台,可能会被覆盖
isReservedTag: no,
//检查属性是否被保留,以便不能用作组件道具。这取决于平台,可能会被覆盖
isReservedAttr: no,
//检查标记是否为未知元素。取决于平台
isUnknownElement: no,
//获取元素的名称空间
getTagNamespace: noop,
//解析特定平台的真实标签名称
parsePlatformTagName: identity,
//检查是否必须使用属性(例如值)绑定属性。这个取决于平台
mustUseProp: no,
//异步执行更新。如果设置为false,将显著降低性能
async: true,
//因遗留原因暴露
_lifecycleHooks: LIFECYCLE_HOOKS
}: Config)
是 Vue 全局的配置对象,可以在引导应用程序之前修改上面列出的属性。
2、Vue.set
Vue.set 方法与 Vue.prototype.$set 方法一样,都是 set 方法;在 src/core/observer/index
文件里面:
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)}`)
}
//target是不是数组,key是不是有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
//对比key和target.length来重新赋值数组长度
target.length = Math.max(target.length, key)
//替换key位置的值
target.splice(key, 1, val)
return val
}
//如果key已经存在target里面或key属于Object的原生属性,那么直接赋值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
//避免在运行时向Vue实例或其根$data添加响应性属性——>在data选项中预先声明它
//获取observe实例,可参考Observe实例对象
const ob = (target: any).__ob__
//isVue为true说明target是Vue实例
//vmCount属性存在说明是根$data
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
}
if (!ob) {
target[key] = val
return val
}
//添加成响应式属性,并通知数据更新
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
向响应式对象添加属性,确保新属性也是响应式的,从而触发视图更新。目标对象不能是 Vue 实例,也不能是 Vue 实例的根数据对象。
3、Vue.delete
Vue.delete方法与 Vue.prototype.$delete方法一样,都是 set 方法;在 src/core/observer/index
文件里面:
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)}`)
}
//target是不是数组,key是不是有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
//直接删除key位置的值
target.splice(key, 1)
return
}
//避免删除Vue实例上的属性,或者将其根$data设置为空即可
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
}
//如果target里面没有key这个属性直接返回
if (!hasOwn(target, key)) {
return
}
//delete 属性,并通知数据更新
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
删除对象上的属性。如果对象是响应式的,要确保删除会触发视图更新。这主要用于解决 Vue 无法检测属性删除的限制。目标对象不能是 Vue 实例,也不能是 Vue 实例的根数据对象。
4、Vue.nextTick
在src/core/util/next-tick.js
文件里面,将回调延迟到下次 DOM 更新循环之后执行;
const callbacks = []
let pending = false
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// callback是上面定义的一个数组,存储下面方法
callbacks.push(() => {
// 回调函数存在则绑定到 vue实例上cb.call(ctx) 、_resolve(ctx)
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
//padding状态位
if (!pending) {
pending = true
timerFunc()
}
// 如果回调函数不存在,当前环境支持 Promise,则返回一个 Promise 对象,并赋值给 _resolve
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
let timerFunc
//当前环境是否支持promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// 设置空计数器,强制刷新微任务队列
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 在原生Promise不可用的地方使用MutationObserver
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 将padding标志位置空,数据缓存到copies里面,清空callbacks数组,遍历执行每一个方法
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
将回调函数缓存在 callbacks 数组里面,然后 调用 timerFunc 方法,timerFunc 会根据不同环境通过不同的手段调用 flushCallbacks 方法来执行每一个缓存在 callbacks 里面的方法。
nextTick的核心思路就是通过异步方法来处理任务;vue 会根据当前环境优先使用 promise.then、MutationObserver 、setImmediate,如果都不支持就使用 setTimeout 把函数延迟到 DOM 更新之后再使用。(原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务)
5、Vue.observable
2.6 版本以后新增,主要调用 observe
方法,让一个对象可响应;Vue 内部会用它来处理 data 函数返回的对象。
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
//是否存在 _ob_属性,是不是已经存在 Observer:说明已经是响应式
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
//shouldObserve 为true、不是服务端渲染、数组或者对象、对象可拓展、不是根节点
} else if (shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
判断是否存在 _ob_
以及value.__ob__
是否在 Observer 中,这说明当前数据已经是响应式的,所以直接赋值就可以了;否则调用 Observer 将对象设置为响应式;
6、initAssetRegisters(Vue.directive、Vue.filter、Vue.component)
在 src/core/global-api/assets.js
文件里
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string, //名称
definition: Function | Object //方法
): Function | Object | void {
// 未传入 definition,则表示是获取,直接return
if (!definition) {
return this.options[type + 's'][id]
} else {
// 注册组件,检查组件名是否规范
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
//在definition中没有传name,id就是组件的name
definition.name = definition.name || id
//把definition传入extend,返回Vue的子构造器,所以component的第二个参数可以是Vue.extend函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
//默认 bind和 update都是入参函数
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
一次挂载 Vue.component()、Vue.directive()和Vue.filter()
方法;方法里面区分读取和设置操作;获取操作直接返回参数里面对应的属性值;设置操作则是在 options 参数上设置对应的属性名和属性值;
7、Vue.use
注册使用插件或者组件,在 src/core/global-api/use.js
文件里
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 首次调用时初始化 _installedPlugins
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
//检测组件是否注册,避免重复注册;
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
//处理入参,将第一个参数之后的参数归集,并在首部插入 this 上下文;
const args = toArray(arguments, 1)
args.unshift(this)
// 第一个参数是对象就执行对象里面的install方法,是方法则直接执行这个方法,然后缓存
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
安装 Vue.js 插件。如果插件是一个对象,它必须公开一个install方法。如果它是一个函数本身,它将被视为安装方法。install 方法将以 Vue 作为参数被调用。
8、Vue.mixin
将传入的参数对象和vue实例的选项实行合并,在 src/core/global-api/mixin.js
文件里
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
全局应用 mixin,这会影响之后创建的每个 Vue 实例。使用插件时可以使用它来将自定义行为注入到组件中。不推荐全局使用。
9、Vue.extend
创建 Vue 构造器,是一个“子类”,需要通过 $mount 挂载到指定元素上。在 src/core/global-api/extend.js
文件里
export function initExtend (Vue: GlobalAPI) {
//每个实例构造函数(包括Vue)都有一个唯一的cid。这使我们能够为原型继承创建包装的“子构造函数”并缓存它们。
Vue.cid = 0
let cid = 1
//类继承
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this //父类
const SuperId = Super.cid // 父类的cid
//给 extendOptions添加一个_Ctor 属性
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) //缓存创建出来的子类
//是否已经创建过子类
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
//检查名称是否规范
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
//定义子构造函数 Sub
const Sub = function VueComponent (options) {
this._init(options)
}
//将父类的原型继承到子类中
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
//子类递增添加cid
Sub.cid = cid++
//合并参数
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// 对于props和computed,我们在扩展时在扩展原型上的Vue实例上定义代理getter。这避免了对每个创建的实例调用Object.defineProperty。
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// 允许进一步使用extend/mixin/use
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// 注册 directive、filter、component
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// 在扩展时保持对超级选项的引用。稍后在实例化时,我们可以检查Super的选项是否已经更新。
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// 缓存constructor
cachedCtors[SuperId] = Sub
return Sub
}
}
//将props代理到 _props,vm.name实际上可以访问到的是Sub.prototype._props.name
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
//将传进来的 userDef 判断是函数还是对象,分别定义;然后使用 Object.defineProperty 挂载到 vm 上;
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}
Vue.extend
使⽤⼀种⾮常经典的原型继承的⽅式把⼀个纯对 象转换⼀个继承于 Vue 的构造器 Sub 并返回,然后对 Sub 这个对象本⾝扩展了⼀些属性,如扩 展 options 、添加全局 API 等;并且对配置中的 props 和 computed 做了初始化⼯作;最后对于 这个 Sub 构造函数做了缓存,避免多次执⾏ Vue.extend
的时候对同⼀个⼦组件重复构造。
下面是子类得到的属性:
Sub.cid
Sub.options
Sub.extend
Sub.mixin
Sub.use
Sub.component
Sub.directive
Sub.filter
// 新增的
Sub.super // 父类
Sub.superOptions // 父类选项
Sub.extendOptions // 传入子类选项
Sub.sealedOptions // 子类目前的所有选项(父+自己)
10、Vue.compile
将一个模板字符串编译成 render 函数;只有在 entry-runtime-with-compile 模式下才会挂载;在 src/platforms/web/entry-runtime-with-compile.js
文件中;
const { compile, compileToFunctions } = createCompiler(baseOptions);
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Vue.compile = compileToFunctions;
其实就是 compileToFunctions
方法;这个后面编译过程再细说;
11、Vue.version
提供字符串形式的 Vue 安装版本号;在 src/core/index.js
文件中;
以上就是对 Vue 这个对象本身扩展的全局静态方法。