# equalByTag

# Description

equalByTag 会根据 tag 类型的不同来比较 object 或者 array 是否相等

此函数仅支持将值与Boolean,Date,Error,Number,RegExp或String的标签进行比较。

# Params

(object, other, tag, bitmask, customizer, equalFunc, stack)

{Object} object - 需要比较的对象

{Object} other - 另外一个需要比较的对象

tag - Object.prototype.toString.call 的值

{number} bitmask - 标志位,可以用来控制 部分比较(1) 和 无序的比较(2)

{Function} customizer - 自定义比较的函数。

{Function} equalFunc - 判断值是否相等的函数

{Object} stack - Stack 实例,用来防止循环引用

# Return

Boolean

# Depend

import eq from '../eq.js'
import equalArrays from './equalArrays.js'
import mapToArray from './mapToArray.js'
import setToArray from './setToArray.js'

eq 源码分析

equalArrays 源码分析

mapToArray 源码分析

setToArray 源码分析

# Code

/** Used to compose bitmasks for value comparisons. */
const COMPARE_PARTIAL_FLAG = 1
const COMPARE_UNORDERED_FLAG = 2

/** `Object#toString` result references. */
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const numberTag = '[object Number]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'

const arrayBufferTag = '[object ArrayBuffer]'
const dataViewTag = '[object DataView]'

/** Used to convert symbols to primitives and strings. */
const symbolValueOf = Symbol.prototype.valueOf

# Analyze

function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
  switch (tag) {
    case dataViewTag:
      if ((object.byteLength != other.byteLength) ||
          (object.byteOffset != other.byteOffset)) {
        return false
      }
      object = object.buffer
      other = other.buffer

    case arrayBufferTag:
      if ((object.byteLength != other.byteLength) ||
          !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
        return false
      }
      return true

    case boolTag:
    case dateTag:
    case numberTag:
      // Coerce booleans to `1` or `0` and dates to milliseconds.
      // Invalid dates are coerced to `NaN`.
      return eq(+object, +other)

    case errorTag:
      return object.name == other.name && object.message == other.message

    case regexpTag:
    case stringTag:
      // Coerce regexes to strings and treat strings, primitives and objects,
      // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
      // for more details.
      return object == `${other}`

    case mapTag:
      let convert = mapToArray

    case setTag:
      const isPartial = bitmask & COMPARE_PARTIAL_FLAG
      convert || (convert = setToArray)

      if (object.size != other.size && !isPartial) {
        return false
      }
      // Assume cyclic values are equal.
      const stacked = stack.get(object)
      if (stacked) {
        return stacked == other
      }
      bitmask |= COMPARE_UNORDERED_FLAG

      // Recursively compare objects (susceptible to call stack limits).
      stack.set(object, other)
      const result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack)
      stack['delete'](object)
      return result

    case symbolTag:
      if (symbolValueOf) {
        return symbolValueOf.call(object) == symbolValueOf.call(other)
      }
  }
  return false
}

# Remark

整个函数根据 switch 的不同 case 来做不同的处理逻辑,在函数一开始定义了一些常量,来表示 Object.prototype.toString.call 的值,也就是数据类型

# DataView (opens new window)

  if ((object.byteLength != other.byteLength) ||
      (object.byteOffset != other.byteOffset)) {
    return false
  }
  object = object.buffer
  other = other.buffer
  1. byteLength - 此 DataView 对象的字节长度。如果未指定,这个视图的长度将匹配 buffer 的长度。
  2. byteOffset - 此 DataView 对象的第一个字节在 buffer 中的字节偏移。如果未指定,则默认从第一个字节开始。

如果两个 DataView 相等的话, byteLength 和 byteOffset 的值应当是相等的,如果不相等,则返回 false,表示两个值不匹配,如果这两个值相等了,接下来就该比较 buffer 的值了,在 DataView 的 case 中,只是取出了两个 buffer 的值,赋值给了 object 和 other,但是也没有 break ,所以会走接下来的 case

# ArrayBuffer (opens new window)

  if ((object.byteLength != other.byteLength) ||
      !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
    return false
  }
  return true
  1. byteLength - 只读属性,表示 ArrayBuffer 的 byte 的大小,在 ArrayBuffer 构造完成时生成,不可改变。

判断两个值 byteLength 是否相等,如果 byteLength 相等,还会使用 equalFunc 函数来判断 Uint8Array (opens new window) 视图是否相等

# Boolean (opens new window)Date (opens new window)Number (opens new window)

  // Coerce booleans to `1` or `0` and dates to milliseconds.
  // Invalid dates are coerced to `NaN`.
  return eq(+object, +other)

会将 boolean 转换为 1, 0;date 转为毫秒的时间戳,这里使用一元正号 (opens new window) 进行转换,无效的时期会转为NaN,调用一元正号来转换Date 对象时,会调用到 Date.prototype.valueOf ,返回时间戳

然后调用 eq 来判断是否相等

# Error (opens new window)

  return object.name == other.name && object.message == other.message

只要 Error 的 name 和 message 相等,就认为这两个 Error 对象相等

# RegExp (opens new window)String (opens new window)

  // Coerce regexes to strings and treat strings, primitives and objects,
  // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
  // for more details.
  return object == `${other}`
  1. RegExp.prototype.toString() (opens new window)

在这里是将正则转换为了字符串,如果转为字符串后,二者相等,则认为是相等的

# Map (opens new window)

  let convert = mapToArray

Map 这里也只是定义了将 Map 转换为 array,并没有break,没有return,所以会走到后面的 case

# Set (opens new window)

  const isPartial = bitmask & COMPARE_PARTIAL_FLAG
  convert || (convert = setToArray)

  if (object.size != other.size && !isPartial) {
    return false
  }
  // Assume cyclic values are equal.
  const stacked = stack.get(object)
  if (stacked) {
    return stacked == other
  }
  bitmask |= COMPARE_UNORDERED_FLAG

  // Recursively compare objects (susceptible to call stack limits).
  stack.set(object, other)
  const result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack)
  stack['delete'](object)
  return result
  1. 首先判断了是否部分比较,其次定义了转换方法,承接上一个 casemap,这里 set 使用的是 setToArraySetMap 不同的也就是这里的转换函数的不同
  2. 判断了 objectother 二者的 size 长度,如果 size 不同,又没有开启部分比较,则二者是不相等的,返回 false
  3. 这里也是使用 stack 来处理了循环引用的问题,这里的判断过程和 equalArrays 的一致
  4. 取或赋值,MapSet 都是无序的,这里直接将 bitmask 包含了无序比较的模式
  5. 使用 equalArrays 来比较 objectother 二者数组的相等性,最终返回结果

# Symbol (opens new window)

  if (symbolValueOf) {
    return symbolValueOf.call(object) == symbolValueOf.call(other)
  }

Symbol.prototype.valueOf - 返回 Symbol 对象的初始值.

如果存在 Symbol.prototype.valueOf ,则使用 Symbol.prototype.valueOf 来获取二者的值进行比较,如果相等,则认为两个 Symbol 是相等的

# Example

const a = new Map
const b = new Map

a.set('k', 'v')
b.set('k', 'v')

const stack = new Stack
const fun = (arrValue, othValue, bitmask, customizer, stack) => {
  return equalArrays(arrValue, othValue, bitmask, customizer, fun, stack)
}

console.log(equalByTag(a,b,'[object Map]',undefined,undefined,fun,stack)) // true