Posted at
Node.jsDay 24

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

More than 1 year has passed since last update.

これで Node Advent Calendar としての util 小ネタ切れです。

切れた所で Advent Calendar が埋まりきって良かったです。


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

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

https://github.com/nodejs/node/pull/16084

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

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


util.isDeepStrictEqual の中身

https://github.com/nodejs/node/blob/master/lib/internal/util/comparisons.js

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 出してみるか、、、)