Vue2.x源码学习笔记(七)——全局方法原理

188 阅读3分钟

Vue.use

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)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }

installedPlugins表示已经安装的插件,如果已经安装了则直接return。

将 this也就是Vue构造函数放到第一个参数位置,然后将这些参数传递给install方法。如果插件存在install方法就调用install方法,如果没有则调用plugin这个方法进行安装插件,然后将插件添加到已安装插件列表。

Vue.mixin

 Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
 }

这里主要是通过mergeOptions来合并配置选项:将自己本身的配置和混入的配置合并。mergeOptions方法我们会在单独的一章进行梳理。

Vue.set

function set (target: Array<any> | Object, key: any, val: any): any {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
 
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

如果是数组则使用数组的splice方法来响应式更新。

如果key原本就存在target对象上,直接修改值。

如果target对象上没有__ob__属性,即不是响应式对象,直接修改值。

如果key不存在target上并且target是响应式对象,那么通过defineReactive使其添加到target上,并且使其响应式。然后触发target的依赖更新。

Vue.delete

function del (target: Array<any> | Object, key: any) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

如果是数组,则通过数组的splice方法去删除。

如果属性不存在对象上,直接return结束。

如果属性存在对象上,通过delete方法删除属性,然后通过target对应的__ob__上的dep来触发更新。

Vue.nextTick

const callbacks = []
let pending = fals
function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

将传入的回调函数通过try catch包裹成一个新函数,然后添加到callbacks数组中。

当pending为false,即浏览器任务队列中没有flushCallbacks函数了。将pending重置为true并通过执行timerFunc将flushCallbacks放入浏览器任务队列中。

也可以这样使用:Vue.nextTick().then(()=>{}),因此当环境支持Promise时,会返回一个promise,并执行_resolve

timerFunc

let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
   /**
     * 在有问题的UIWebViews中,Promise.then不会完全中断,但是它可能会陷入怪异的状态,
     * 在这种状态下,回调被推入微任务队列,但队列没有被刷新,直到浏览器需要执行其他工作,例如处理一个计时器。
     * 因此,我们可以通过添加空计时器来“强制”刷新微任务队列。
     */
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  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)) {
 setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

当浏览器支持Promise时首选Promise.resolve.then(),在微任务队列中放入 flushCallbacks。然后考虑MutationObserver。再考虑setImmediate(宏任务)。最后用setTimeout(cb,0)兜底。不管用哪种方式最终都是调用flushCallbacks函数。

flushCallbacks

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

将pending置为false,表示浏览器任务队列已经执行完了flushCallbacks,循环callbacks,执行之前存放的包裹后的函数。

Vue.extend

const ASSET_TYPES = ['component','directive','filter']
Vue.extend = function (extendOptions: Object): Function {
    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
    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
    Sub.cid = cid++
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    if (name) {
      Sub.options.components[name] = Sub
    }
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    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])
  }
}

基于Vue去扩展子类,该子类同样支持进一步的扩展,扩展时可以传递一些默认配置,就像Vue也会有一些默认配置。

Super指向this,这里的this就时Vue。利用缓存,如果存在则直接返回缓存中的构造函数,如果你在多次调用Vue.extend时使用了同一个配置项(extendOptions),这时就会启用该缓存。

定义一个Sub构造函数,和Vue构造函数一样。Sub的原型对象指向Vue的原型对象,通过原型式继承来继承Vue。选项合并,合并Vue构造函数的配置项和传递进来的组件对象到自己的配置项上来。通过Sub.super来指向自己的父类。有props就通过initProps初始化props,这里就是循环props将它代理到当前类的原型对象上,组件内通过this._props访问。有computed则通过initComputed去初始化,循环computed然后调用defineComputed,组件内可以通过this.xxx访问。

定义 extend、mixin、use 这三个静态方法,允许在Sub基础上再进一步构造子类。然后再定义 component、filter、directive 三个静态方法。

递归组件的原理:如果组件设置了 name 属性,则将自己注册到自己的 components 选项中。

在扩展时保留对父类选项的引用,保留组件本身传递进来的对象,浅拷贝保存子类合并后的配置项。最后进行缓存并返回这个子类。

Vue.Component、Vue.Directive、Vue.Filter

Vue.options._base = Vue
const ASSET_TYPES = ['component','directive','filter']
ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      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 = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })

处理Vue.Component:当definition是对象是会这样处理:如果组件配置中存在name,则使用name,否则直接使用id。这里this.options._base指向的是Vue,这里实际就是通过Vue.extend()生成一个子类。

处理Vue.Directive:如果definition是一个函数,那么它将被bind和update所使用。

最后将三者分别添加到options的components,directives,filters中,用于实例化子组件时mergeOptions合并到每个子组件配置中。