Vue 2.6源码学习(04)-全局api(use,set,mixin,nextTick)

164 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

Vue中常见的全局api

  1. Vue.set( target, propertyName/index, valu…

Vue.set 方法是vue中的一个补丁方法 (正常我们添加属性是不会触发更新的, 我们数组无法监控到索引和长度),我们给每一个对象都增添了一个dep属性

// vm.$set(vm.obj,'b',100);
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)}`)
  }

  // vm.obj[0] = 100  -> vm.$set(vm.obj,'b',100);  -> vm.obj.splice(0,1,100)
  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)) { // vm.obj.a =100;
    target[key] = val // 已经有的属性直接修改即可
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {

    // data:{} vm.$set(vm,'a')
    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)// > vm.$set(vm.obj,'b',{a:1}); 
  ob.dep.notify() // 触发视图更新
  return val
}
  1. Vue.delete( target, propertyName/index )

/**
 * Delete a property and trigger change if necessary.
 */
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)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  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
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

  1. Vue.nextTick( [callback, context] )

nextTick这里的兼容处理:依次检查是否有Promise对象,MutationObserver对象,setImmediate对象,setTimeout方法。

nextTick内部采用了异步任务进行了包装(多个nextTick调用 会被合并成一次,内部会合并回调)最后在异步任务中批处理 主要应用场景就是异步更新 (默认调度的时候 就会添加一个nextTick任务) 用户为了获取最终的渲染结果需要在内部任务执行之后在执行用户逻辑 这时候用户需要将对应的逻辑放到nextTick中

export let isUsingMicroTask = false

const callbacks = []
let pending = false

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


let timerFunc

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]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  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)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick (cb?: Function, ctx?: Object) { // 让nextTick支持了promise的形式
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx) // 如果没有传递cb 默认会把promise的成功赋予进来
    }
  })
  if (!pending) {
    pending = true
    timerFunc() // 只会开启一次异步任务
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

  1. Vue.mixin( mixin )

mixin的核心就是合并属性 (内部采用了策略模式进行合并) 全局mixin,局部mixin。 针对不同的属性有不同的合并策略。

通过Vue.mixin来实现逻辑的复用, 问题在于数据来源不明确。 声明的时候可能会导致命名冲突。 高阶组件, vue3 采用的就是compositionAPI解决了复用问题


export function initMixin (Vue: GlobalAPI) {

  // 谁调用的this就是谁
  Vue.mixin = function (mixin: Object) { // this =Vue
    // 最终会将 mixin对象和Vue.options 合并在一起
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}
  1. Vue.use( plugin )

目的就是将 vue的构造函数传递给插件中,让所有的插件依赖的Vue是同一个版本

  • 默认调用插件 默认调用插件的install方法
export function initUse (Vue: GlobalAPI) {
  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) // [Vue,{a:1,b:2}]
    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的部分全局api的基本实现和使用。后面介绍其他全局api,如extend,component。