Vue.extend源码分析
前言
Vue.extend生成一个组件的构造器,使用的场景其实不算多,一般来说,在需要实现一个全局的类似alert,message组件的时候,可以比较方便的使用它,动态地挂载。
开始读源码
Vue.extend = function (extendOptions: any): typeof Component {
extendOptions = extendOptions || {}
const Super = this // 定义一个this的变量Super,方便后面操作
const SuperId = Super.cid // 用来做缓存的key
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) // 初始化或者获取当前的缓存
if (cachedCtors[SuperId]) { // 如果缓存中获取到了,就直接返回缓存中的
return cachedCtors[SuperId]
}
const name =
getComponentName(extendOptions) || getComponentName(Super.options) // 获取组件名称,这个方法就是拿options.name || options.__name || options._componentTag
if (__DEV__ && name) { // 在开发环境中回去校验组件的名称是否合理,以及是否与内置的标签冲突
validateComponentName(name)
}
// 这个就是extend最终要返回的构造方法,目前还比较简单,就是会调用一个实例的_init方法去初始化
const Sub = function VueComponent(this: any, options: any) {
this._init(options)
} as unknown as typeof Component
Sub.prototype = Object.create(Super.prototype) // 把当前构造函数的原型链指向父级的,通常来说是Vue.prototype
Sub.prototype.constructor = Sub // 把原型链上的构造函数指向这个函数本身
Sub.cid = cid++ // 这个cid是在文件中的一个变量,算是递增的,起始值为1,Vue.cid = 0,每用一次extend就会加1.
Sub.options = mergeOptions(Super.options, extendOptions) // 合并options,会把原型链上父级的options跟当前的options合并
Sub['super'] = 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.
// 下面分别就是初始化props和computed,实现方法在最下面,也不是很复杂,到了下面再讲
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// 将Vue自带的extend、mixin、use层层往下传递,不管是用谁extend出来的新的构造器,都会有这些方法,相当于,可以在已经extend的基础上,继续拓展mixin或者use,形成一个链式的调用修改。
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// ASSET_TYPES = ['component', 'directive', 'filter']
// 把顶层的Vue的component、directive、filter传递给它,以便它也能支撑拓展
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// 如果存在name的话,就允许递归去寻找到它本身
if (name) {
Sub.options.components[name] = Sub
}
// 保留父级的options
Sub.superOptions = Super.options
// 记录当前的extend时的options
Sub.extendOptions = extendOptions
// extend这个方法实现了一个for...in的赋值
Sub.sealedOptions = extend({}, Sub.options)
// 记录缓存
cachedCtors[SuperId] = Sub
return Sub
}
}
// 初始化props
function initProps(Comp: typeof Component) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key) // 直接调用Object.defineProperty或者proxy去添加响应式的值
}
}
// 初始化computed
function initComputed(Comp: typeof Component) {
const computed = Comp.options.computed
for (const key in computed) {
// 这个函数有一点点复杂,所以我直接把这个函数的源码也分析一下
defineComputed(Comp.prototype, key, computed[key])
}
}
export function defineComputed(
target: any,
key: string,
userDef: Record<string, any> | (() => any)
) {
const shouldCache = !isServerRendering() // 是否是服务端渲染,服务端渲染的不缓存。
if (isFunction(userDef)) { // 定义的computed是否是一个函数形式
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key) // 缓存的时候调用它,直接可以看下面
: createGetterInvoker(userDef) // 不缓存的时候调用它,也可以看下面
sharedPropertyDefinition.set = noop // sharedPropertyDefinition最外层定义的一个对象,通过它使用defineProperty或者proxy来绑定target,达到收集依赖和set时触发更新
} else {
//这个指的是computed写的是{ set, get }的对象写法
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key) // 缓存
: createGetterInvoker(userDef.get) // 非缓存
: noop
sharedPropertyDefinition.set = userDef.set || noop // 同上,这个noop实际是一个空函数,没有任何操作,它在组件进行挂载之前会调用内部的proxy方法会重新给它赋值,所以才会有下面的判断
}
if (__DEV__ && sharedPropertyDefinition.set === noop) { // 开发环境中,如果set还是初始值,那就说明它没有可用的setter,也就是可能还没挂载或者初始化。
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key] // 获取组件的watcher对象
if (watcher) {
if (watcher.dirty) { // watcher监听到有变化
watcher.evaluate() // 开始去统计该computed内部使用了哪些变量
}
if (Dep.target) { // 发现依赖了Dep.target就会有值,默认是null
// 开发环境的一个埋点处理
if (__DEV__ && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: this,
type: TrackOpTypes.GET,
key
})
}
// 收集依赖
watcher.depend()
}
return watcher.value // 最后返回监听到的值
}
}
}
// 没有太多处理,就是绑定了一下get的执行时的this
function createGetterInvoker(fn) {
return function computedGetter() {
return fn.call(this, this)
}
}
小结
整体看起来,这个
Vue.extend
其实就是基于原型链继承实现了一个Vue的子类,可以通过这个子类去实现单独某个组件的挂载。