vue2中值得关注的全局Api之util对象

164 阅读2分钟

本系列

-# vue2中值得关注的全局Api之config对象

Vue2中这些全局Api很重要,谈到源码大家都关注响应原理模板编译更新策略这些点,但是Vue在第一步做的事情是注册全局Api,没有全局Api就没有后面的一切

全局Api都在initGlobalAPI函数中初始化

function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

Vue.util提供的工具方法

Vue.util.mergeOptions 函数合并对象

这个方法粗看像是 lodash 中的merge方法或者 Object.assign 方法,其实不止这么简单,并不是简单的对象深浅拷贝合并。

在lodash 中的merge方法或者 Object.assign 方法中处理合并,相同key是用后一个覆盖前一个。

但是对于Vue中,参数对象可以包含 props,data,computd,methods,inject,provide,watch,component,directive,filter,created生命周期等等属性,就不能是直接覆盖,应该是根据不同属性key,采取不同的合并策略,这就关联上vue2中值得关注的全局Api之config对象文中讲到的 config.optionMergeStrategies 参数了

function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

Vue.util.defineReactive 方法 、 Vue.set 方法 、 Vue.observable 方法区别

我们在头部 initGlobalAPI 看到,全局提供了 Vue.util.defineReactive 方法 、 Vue.set 方法 、 Vue.observable 方法,这三个方法都是提供响应式的,为什么要写三个呢,当然 Vue.set 方法 、 Vue.observable 方法 最终都是调用的 Vue.util.defineReactive 这一个方法,具体有什么区别呢?

核心部分 defineReactive 代码

defineReactive 中:

  • 用于给对象设置新属性

  • obj对象可以是响应式对象,也可不是响应式对象,这里没有要求,但是会observe(val) 把val做成响应式

function defineReactive (
  obj: Object,
  key: string,
  val: any,
) {
  const dep = new Dep()

  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

核心部分 set 代码

set 比 defineReactive 更严格,其中:

  • 用于给对象设置新属性

  • target 对象必须是响应式对象

  • 再调用 defineReactive 方法

function set (target: Array<any> | Object, key: any, val: any): any {
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    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)
  ob.dep.notify()
  return val
}

核心部分 observe 代码

  • 用于给对象做响应式,不同于set和defineReactive是给属性设置响应式
function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}