LoginSignup
11
3

More than 5 years have passed since last update.

util.isDeepStrictEqual がリリースされた経緯

Posted at

これで Node Advent Calendar としての util 小ネタ切れです。
切れた所で Advent Calendar が埋まりきって良かったです。

util.isDeepStrictEqual がNode v9でリリースされた経緯

蓋を開けてみればなんてこと無い話で、元々 assert.deepStrictEqual 関数が内部で使っている関数を public に変更したという話です。元からあったものをみんなにも使える形にするというだけなので、 Less is More の原則からも反していないわけですね。

object 同士を比較するというよくあるケースは lodashunderscore にもあるし、 ava にも qunit にも存在しているということで、moduleとしては公開してもいいか、という発想ですね。

ちなみに中を見ると、かなり複雑なことをしています。

util.isDeepStrictEqual の中身

function strictDeepEqual(val1, val2, memos) {

  // deepStrictEqual(1, 1) => true
  // deepStrictEqual(NaN, NaN) => true (NaN でも同じことになる)
  // deepStrictEqual(1, 2) => false
  if (typeof val1 !== 'object') {
    return typeof val1 === 'number' && Number.isNaN(val1) &&
      Number.isNaN(val2);
  }

  // deepStrictEqual(null, undefined) => false (null, undefined 違い)
  // deepStrictEqual(null, null) => true 
  if (typeof val2 !== 'object' || val1 === null || val2 === null) {
    return false;
  }
  const val1Tag = objectToString(val1);
  const val2Tag = objectToString(val2);

  // deepStrictEqual([1,2], {a:1, b:2}) => false (型違い)
  if (val1Tag !== val2Tag) {
    return false;
  }

  // class A {}
  // class B extends A {}
  // deepStrictEqual(B, A) => false (prototype 違い)
  if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) {
    return false;
  }

  // deepStrictEqual([1,2,3], [1,2,3]) => true Arrayのときの比較
  if (val1Tag === '[object Array]') {
    // Array の長さが違ったらfalse
    if (val1.length !== val2.length)
      return false;
    // 長さが等しければ中身のチェック
    return keyCheck(val1, val2, true, memos);
  }

  // deepStrictEqual({a: 1, b: 2}, {a: 1, b: 2}) => true objectのときの比較
  if (val1Tag === '[object Object]') {
    // 中身チェック
    return keyCheck(val1, val2, true, memos);
  }

  // Date の時は getDate したもの同士で比較
  if (isDate(val1)) {
    if (val1.getTime() !== val2.getTime()) {
      return false;
    }
  // Regexp の場合は source と flag のチェックして、違ったら false
  } else if (isRegExp(val1)) {
    if (!areSimilarRegExps(val1, val2)) {
      return false;
    }
  // Error の場合はメッセージでのみチェック
  // (ここは本来 Error のIDがNodeの場合はあるからそれを確認してもいいか?)
  } else if (val1Tag === '[object Error]') {

    if (val1.message !== val2.message) {
      return false;
    }
  // Bufferの時は Buffer.compare で比較する
  // 普通のTypedArrayは一つ一つ中身をチェックする、ただし300個の要素だけ比較する
  // (全部比較するのは高コストのため)
  } else if (isArrayBufferView(val1)) {
    if (!areSimilarTypedArrays(val1, val2,
                               isFloatTypedArrayTag(val1Tag) ? 0 : 300)) {
      return false;
    }
    // ラフなチェックで true になる場合は中身をきっちりチェックする。
    return keyCheck(val1, val2, true, memos, val1.length,
                    val2.length);
  // valueOf 関数がある場合
  } else if (typeof val1.valueOf === 'function') {
    const val1Value = val1.valueOf();
    // valueOf を実行後、もう一度 deepStrictEqualを実行する
    if (val1Value !== val1) {
      if (!innerDeepEqual(val1Value, val2.valueOf(), true))
        return false;
      var lengthval1 = 0;
      var lengthval2 = 0;

      // valueOf 関数実行後の value が 文字列の場合は長さを入れて比較する 
      if (typeof val1Value === 'string') {
        lengthval1 = val1.length;
        lengthval2 = val2.length;
      }
      return keyCheck(val1, val2, true, memos, lengthval1,
                      lengthval2);
    }
  }
  // 全ての型にマッチしない場合は中身をきっちりチェックする
  return keyCheck(val1, val2, true, memos);
}

Map だったり Set だったりの場合は keyCheck 関数の中で詳細にチェックしています。ただし、 WeakMap だったり、 WeakSet は対象外です。弱参照のコレクション型はそのGC状況によって中身がどうなるかわからないですし、何よりも比較するコレクションではないです。

ちなみに WeakMap もしくは WeakSet 同士の比較を util.isDeepStrictEqual を実行すると 必ず true になります。
(ただこれは必ず false の方が誤解を生まないのでまだ良いような。。。 issue 出してみるか、、、)

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3