Vue.extend源码分析

发布时间 2023-04-18 16:01:48作者: 风行者夜色

前言

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的子类,可以通过这个子类去实现单独某个组件的挂载。