Vue2 源码解读 util.js

文件位置

https://github.com/vuejs/vue/blob/dev/src/shared/util.js

下面我们对 util.js 中的一些方法进行解读。

// 非 undefined、非 null
export function isDef (v: any): boolean %checks {
  return v !== undefined && v !== null
}
// 判断是否是 Promise
export function isPromise (val: any): boolean {
  // 存在then 和 catch 属性,并且它们的 typeof 值都为 ‘function’ 时,说明这是一个 Promise
  return (
    isDef(val) &&
    typeof val.then === 'function' &&
    typeof val.catch === 'function'
  )
}

对于 Promise 的判断,在平时的项目开发中用得比较少。

/**
 * Convert an input value to a number for persistence.
 * If the conversion fails, return original string.
 */
export function toNumber (val: string): number | string {
  const n = parseFloat(val)
  // 如果是 NaN 则返回值本身,不处理。
  return isNaN(n) ? val : n
}

字符串转数字,比如将 input 值转成 Number 类型。对于字符串类型的数字,我们也可以通过 val * 1 的形式来进行转换。是实践的项目开发中可以使用后者乘以 1 的方式进行转化,但如果一个页面到处都是变量 * 1,也让人够烦的。所以作为一个有追求的程序员,应该尽量地让代码美观大方。在用法上作权衡。

/**
 * Get the raw type string of a value, e.g., [object Object].
 */
const _toString = Object.prototype.toString
export function toRawType (value: any): string {
  // 参数调用 toString 方法,此时会返回形如 [object Number] 的字符串,然后通过 slice(8, -1) 截取后面的关键字符串(即参数的类型)
  return _toString.call(value).slice(8, -1)
}

一般情况下可以通过上面的这个方法来判断变量类型。

比如:util.js 中判断是否是一个纯对象也是通过 toString 方法来判断:

export function isPlainObject (obj: any): boolean {
  return _toString.call(obj) === '[object Object]'
}

对象判断:isObject()

/**
 * Quick object check - this is primarily used to tell
 * Objects from primitive values when we know the value
 * is a JSON-compliant type.
 */
export function isObject (obj: mixed): boolean %checks {
  return obj !== null && typeof obj === 'object'
}

这里直接使用 typeof 来判断一个变量是否为对象,注意,typeof null 时也是对象。

宽松的相等判断:looseEqual()

/**
 * Quick object check - this is primarily used to tell
 * Objects from primitive values when we know the value
 * is a JSON-compliant type.
 */
// 对象判断(数组、对象)
export function isObject (obj: mixed): boolean %checks {
  // 由于 null typeof 也返回 'object',所以先排除掉 null
  return obj !== null && typeof obj === 'object'
}
/**
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 */
export function looseEqual (a: any, b: any): boolean {
  // 如果两个值相等,直接返回,这里是对基本类型的判断
  if (a === b) return true
  const isObjectA = isObject(a)
  const isObjectB = isObject(b)
  // 如果两个都对象(数组、对象)
  if (isObjectA && isObjectB) {
    try {
      const isArrayA = Array.isArray(a)
      const isArrayB = Array.isArray(b)
      // 如果都是数组
      if (isArrayA && isArrayB) {
        // 这里的判断我觉得有点意思,先判断数组长度,长度相等才遍历数组,先比较长度可以避免多余的遍历,只要长度不等数组肯定不等
        return a.length === b.length && a.every((e, i) => {
          return looseEqual(e, b[i])
        })
      } else if (a instanceof Date && b instanceof Date) { // 如果是时间对象,则对比时间戳
        return a.getTime() === b.getTime()
      } else if (!isArrayA && !isArrayB) { // 如果两个都不是数组,就只剩对象了
        // 通过 Object.keys() 方法分别拿到两个对象的 key 值所组件的数组
        const keysA = Object.keys(a)
        const keysB = Object.keys(b)
        // 同样的,对数组进行长度判断,相等则去遍历两个对象相同 key 的值。
        return keysA.length === keysB.length && keysA.every(key => {
          return looseEqual(a[key], b[key])
        })
      } else {
        /* istanbul ignore next */
        return false
      }
    } catch (e) {
      /* istanbul ignore next */
      return false
    }
  } else if (!isObjectA && !isObjectB) { // 如果不是对象
    // 转化成字符串后再比较,这样可以处理掉 NaN 不等于自身的问题
    return String(a) === String(b)
  } else {
    return false
  }
}

为什么说 looseEqual() 方法是宽松的相等?我们测试一下就知道了:looseEqual(1, '1'),结果为 true。原因就在于这个方法对两个都不是对象的变量的处理是先转为字符串再进行比较 String(a) === String(b)。所以就出现了上面的这个结果。

额外收获:可以通过 Array.isArray() 方法来判断一个对象是否为数组。