本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
1. 前言
官方文档
分析Vue的源码, 总是会遇到这些全局api的调用, 所以先把它分析一下.
全局
api就是挂载在Vue上的静态方法, 大致有以下这些
- set 新增或修改对象一个属性的值, 使得这个
property也是响应式的, 且触发视图更新 - delete 删除对象的一个属性, 并且触发更新
- nextTick 将回调函数统一放到一个数组里, 等下一个
tick, 按照添加的顺序统一逐个回调 - observable 将一个对象变为响应式的
- use 注册插件
- mixin 混入选项
- extend 生成
Vue子类
剩下的三个 'component', 'directive', 'filter' 后面专门写文章
2. set
这个方法同时挂载到了
Vue的静态上和实例上
// 静态上
Vue.set = set
// 实例上
Vue.prototype.$set = set
//
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
export function set(
target: Array<any> | Record<string, any>,
key: any,
val: any
): any {
// 设置数组索引的值
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 可能设置的index大于数组的length
target.length = Math.max(target.length, key)
// 数组的splice方法可以监听到修改
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
// 如果 key是target的本就有的属性, 直接赋值即可, 因为 target 是响应式的了
target[key] = val
return val
}
const ob = (target as any).__ob__
if (!ob) {
// target 不是响应式的值, 直接赋值结束
target[key] = val
return val
}
// 把val也弄成响应式的
defineReactive(ob.value, key, val)
// target的触发一下通知
ob.dep.notify()
return val
}
总结: 它用来给对象的属性赋值, 如果该对象是响应式的, 会触发它的更新通知
注意点: 不能给
Vue实例赋值, 也不能给$data赋值, 为了稳定性, 防止造成应用异常
3. delete
这个方法同时挂载到了
Vue的静态上和实例上
// 静态上
Vue.delete = del
// 实例上
Vue.prototype.$delete = del
/**
* Delete a property and trigger change if necessary.
*/
export function del(target: Array<any> | Object, key: any) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 数组的方法删除, 修改会触发更新
target.splice(key, 1)
return
}
const ob = (target as any).__ob__
if (!hasOwn(target, key)) {
// 删除的属性不存在
return
}
delete target[key]
if (!ob) {
// 被删属性的对象不是响应式的
return
}
// 如果是响应式的, 触发一下更新
ob.dep.notify()
}
总结: 它是用来删除一个对象的属性的, 如果这个对象是响应式的, 会触发它的更新通知
注意点: 不能删除
Vue实例的属性, 也不能删除$data上的属性, 为了稳定性, 防止造成应用异常
4. nextTick
5. extend
这个方法只挂载到了
Vue静态上
/**
* 通过基础Vue构造器, 创建一个"子类", 这里并不是严格意义上的子类, 因为并不是继承得到的
* @param extendOptions 扩展的组件 options
* @returns 返回扩展的子类
*/
Vue.extend = function (extendOptions: any): Component {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
// 在扩展的对象上缓存下这个扩展来的子类, 如果在同一个父类上对同一个对象进行扩展, 可以使用缓存
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
// 定义子类, 最后会返回这个子类
const Sub = (function VueComponent(this: any, options: any) {
this._init(options)
} as unknown) as Component
// 用这种方式继承父类的原型, 修改子类的原型不会影响父类, 修改父类的原型会影响到子类原型方法的查找
Sub.prototype = Object.create(Super.prototype)
// 修正 constructor 指向, 不然就指向父类了
Sub.prototype.constructor = Sub
// 每一个Vue类(子类)都有一个 cid,用来标识唯一, Vue 的cid是0, 然后构建的子类会递增
Sub.cid = cid++
// 合并父类的选项到子类上
Sub.options = mergeOptions(Super.options, extendOptions)
// 指定父类, 只在一个地方用到了, _init 中 resolveConstructorOptions
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.
if (Sub.options.props) {
initProps(Sub)
}
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.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
// 缓存这个子类
cachedCtors[SuperId] = Sub
return Sub
}
总结: 用来扩展子类, 所有的自定义组件是通过这个方法得到的
以后分析组件的时候, 再详细往下分析
6. use
这个方法只挂载到了
Vue静态上
Vue.use = function (plugin: Function | any) {
// 读取缓存
const installedPlugins =
this._installedPlugins || (this._installedPlugins = [])
// 缓存中有, 证明已安装过这个插件, 直接返回
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
// 得到 [this, ...arguments]
const args = toArray(arguments, 1)
args.unshift(this)
// 插件执行时, 得到的第一个参数是 Vue , 剩下的是安装插件时传入的
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内部的代码中, 没有安装过任何一个插件
我们可以把给
Vue任何增强功能封装成"插件".
7. mixin
注册一个全局混入, 代码及其简单
Vue.mixin = function (mixin: Object) {
// 将传入的mixin合并到 options 上
this.options = mergeOptions(this.options, mixin)
return this
}
在
Vue内部的代码中, 没有混入过任何选项
8. compile
将模板字符串编译成 render 函数
Vue.compile = compileToFunctions
这是个大坑, 留着以后细细写
9. observable
将一个对象变成响应式的
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
响应式对象, 就是读取它的属性会被它知道, 当它的属性变化了, 它会告诉读取了它属性的地方, 如果这个地方有需要, 就可以做一些事情了, 例如在 render 中, 会触发重新 render, 在 computed 中, 会触发重新计算, 在 watch 中, 会触发函数重新执行, 就这仨地方, 其它地方就属于没有需要的地方, 在
Vue中, 通过Dep.target标识是否有需要.
这里也留个坑
10. 最后
这里只是初略的写了一下
Vue上面的静态方法, 留下了三(五)个大坑, 后面会补上.
附上我之前写的几篇文章