lodash源码分析之equalObjects

本文为读 lodash 源码的第二百一十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash

gitbook也会同步仓库的更新,gitbook地址:pocket-lodash

依赖

import getAllKeys from './getAllKeys.js'

《lodash源码分析之getAllKeys》

源码分析

equalObjects 用来比较两个对象是否相等。

equalObjects 的入参在《lodash源码分析之equalArrays》已经有详细的叙述,这里不再重复。

一些常量

const COMPARE_PARTIAL_FLAG = 1
const hasOwnProperty = Object.prototype.hasOwnProperty

COMPARE_PARTIAL_FLAG 是用来标记是否局部比较, hasOwnProperty 用来保存 Object.prototype.hasOwnProperty ,方便后续调用和优化性能。

属性长度比较

const isPartial = bitmask & COMPARE_PARTIAL_FLAG
const objProps = getAllKeys(object)
const objLength = objProps.length
const othProps = getAllKeys(other)
const othLength = othProps.length

if (objLength != othLength && !isPartial) {
  return false
}

首先比较属性的长度,如果长度不一致,又没有开启局部比较,则可以认为这两个对象不相等。

属性比较

let key
let index = objLength
while (index--) {
  key = objProps[index]
  if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
    return false
  }
}

上面比较了属性的长度,接下来比较属性,依次取出 object 的属性,逐一检查每个属性在 other 对象上是否存在。

这里需要注意的是,如果开启局部比较,则会属性在原型链上即可,如果没有开启,则必须是自身的属性。

循环依赖比较

const stacked = stack.get(object)
if (stacked && stack.get(other)) {
  return stacked == other
}
let result = true
stack.set(object, other)
stack.set(other, object)
...
stack['delete'](object)
stack['delete'](other)

循环依赖的逻辑和《lodash源码分析之equalArrays》是一致的,这里不再细叙。

自定义比较函数比较

while (++index < objLength) {
  key = objProps[index]
  const objValue = object[key]
  const othValue = other[key]

  if (customizer) {
    compared = isPartial
      ? customizer(othValue, objValue, key, other, object, stack)
    : customizer(objValue, othValue, key, object, other, stack)
  }
  ...
}

属性比较完后,就要比较每个属性上的值了。

如果有自定义比较函数 customizer ,则直接调用 customizer 比较即可,要注意的是,如果开启局部比较,othValueobjValue 的位置是互调的。

比较的结果存在 compared 变量中。

内部比较逻辑

while (++index < objLength) {
  key = objProps[index]
  ...
  if (!(compared === undefined
        ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
        : compared
       )) {
    result = false
    break
  }
  skipCtor || (skipCtor = key == 'constructor')
}

如果没有调用比较函数,则 comparedundefined ,或者有调用自定义比较函数,返回的结果是 undefined ,都使用内部的比较逻辑进行比较。

内部会首先使用 === 进行比较,如果得到的结果为不相等,则会使用 equalFunc 得到比较结果。

如果得到的结果为不相等,会立即跳出循环,返回的结果为 false

如果得到的结果为 true ,并且在遍历的过程中,遇到 keyconstructor ,即表示自身的属性有 constructor,这时 constructor 会作为正常的属性比较,会跳过后面的构造函数比较。

构造函数比较

if (result && !skipCtor) {
  const objCtor = object.constructor
  const othCtor = other.constructor

  if (objCtor != othCtor &&
      ('constructor' in object && 'constructor' in other) &&
      !(typeof objCtor === 'function' && objCtor instanceof objCtor &&
        typeof othCtor === 'function' && othCtor instanceof othCtor)) {
    result = false
  }
}

如果自身属性上没有 constructor,则会进一步比较构造函数,如果构造函数相同,则表示这两个是同一个类的实例,属性在上面的比较中是相等的,因此也认为是相等的。

但是构造函数不相等时,有两种情况,也认为这两个对象是相等的。

Object.create(null)

如:

var object1 = Object.create(null);
object1.a = 1

var object2 = { 'a': 1 }

此时 object1constructorundefinedobject2 的构造函数为 Object ,但是 lodash 认为这两种对象都是由 Object 创建的,也是相等的。

这便是第二条判断的作用:

('constructor' in object && 'constructor' in other)

在这种情况下,这条判断返回的结果为 false

跨域对象

如果不同的 iframe 中,构造函数都为 Object 创建了两个对象,此时,这两个对象的 constructor 是不相等的,因为隔离的环境的 Object 构造函数是不相等的。

但是 Object 有个很神奇的特性,Object.constructor instanceOf Object.constructortrue

因此可以利用这个特性,知道这两个对象是否都是由各自的 Object 构造函数直接创建的,如果是,则认为他们相等。

这也是第三个判断的作用:

!(typeof objCtor === 'function' && objCtor instanceof objCtor &&
 typeof othCtor === 'function' && othCtor instanceof othCtor)

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

作者:对角另一面

results matching ""

    No results matching ""