Skip to content

Commit

Permalink
[Refactor] use side-channel for memoizing circular refs
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Dec 2, 2019
1 parent 8e04219 commit 983780a
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 15 deletions.
34 changes: 19 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var whichBoxedPrimitive = require('which-boxed-primitive');
var callBound = require('es-abstract/helpers/callBound');
var whichCollection = require('which-collection');
var getIterator = require('es-get-iterator');
var getSideChannel = require('side-channel');

var $getTime = callBound('Date.prototype.getTime');
var gPO = Object.getPrototypeOf;
Expand All @@ -18,7 +19,7 @@ var $mapHas = callBound('Map.prototype.has', true);
var $mapGet = callBound('Map.prototype.get', true);
var $setHas = callBound('Set.prototype.has', true);

function internalDeepEqual(actual, expected, options, memos) {
function internalDeepEqual(actual, expected, options, channel) {
var opts = options || {};

// 7.1. All identical values are equivalent, as determined by ===.
Expand Down Expand Up @@ -46,20 +47,23 @@ function internalDeepEqual(actual, expected, options, memos) {
* corresponding key, and an identical 'prototype' property. Note: this
* accounts for both named and indexed properties on Arrays.
*/
// see https://github.com/nodejs/node/commit/d3aafd02efd3a403d646a3044adcf14e63a88d32 for memos inspiration
// see https://github.com/nodejs/node/commit/d3aafd02efd3a403d646a3044adcf14e63a88d32 for memos/channel inspiration

var actualIndex = memos.actual.indexOf(actual);
if (actualIndex !== -1) {
if (actualIndex === memos.expected.indexOf(expected)) {
var hasActual = channel.has(actual);
var hasExpected = channel.has(expected);
var sentinel;
if (hasActual && hasExpected) {
if (channel.get(actual) === channel.get(expected)) {
return true;
}
} else {
sentinel = {};
}

memos.actual.push(actual);
memos.expected.push(expected);
if (!hasActual) { channel.set(actual, sentinel); }
if (!hasExpected) { channel.set(expected, sentinel); }

// eslint-disable-next-line no-use-before-define
return objEquiv(actual, expected, opts, memos);
return objEquiv(actual, expected, opts, channel);
}

function isUndefinedOrNull(value) {
Expand All @@ -79,7 +83,7 @@ function isBuffer(x) {
return true;
}

function objEquiv(a, b, opts, memos) {
function objEquiv(a, b, opts, channel) {
/* eslint max-statements: [2, 100], max-lines-per-function: [2, 120], max-depth: [2, 5] */
var i, key;

Expand Down Expand Up @@ -153,7 +157,7 @@ function objEquiv(a, b, opts, memos) {
// equivalent values for every corresponding key, and ~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!internalDeepEqual(a[key], b[key], opts, memos)) { return false; }
if (!internalDeepEqual(a[key], b[key], opts, channel)) { return false; }
}

var aCollection = whichCollection(a);
Expand All @@ -172,13 +176,13 @@ function objEquiv(a, b, opts, memos) {
while ((resultA = iA.next()) && (resultB = iB.next()) && !resultA.done && !resultB.done) {
if (!$mapHas(a, resultB.value[0]) || !$mapHas(b, resultA.value[0])) { return false; }
if (resultA.value[0] === resultB.value[0]) { // optimization: keys are the same, no need to look up values
if (!internalDeepEqual(resultA.value[1], resultB.value[1], opts, memos)) { return false; }
if (!internalDeepEqual(resultA.value[1], resultB.value[1], opts, channel)) { return false; }
} else {
aWithBKey = $mapGet(a, resultB.value[0]);
bWithAKey = $mapGet(b, resultA.value[0]);
if (
!internalDeepEqual(resultA.value[1], bWithAKey, opts, memos)
|| !internalDeepEqual(resultB.value[1], aWithBKey, opts, memos)
!internalDeepEqual(resultA.value[1], bWithAKey, opts, channel)
|| !internalDeepEqual(resultB.value[1], aWithBKey, opts, channel)
) {
return false;
}
Expand All @@ -198,5 +202,5 @@ function objEquiv(a, b, opts, memos) {
}

module.exports = function deepEqual(a, b, opts) {
return internalDeepEqual(a, b, opts, { actual: [], expected: [] });
return internalDeepEqual(a, b, opts, getSideChannel());
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"object-is": "^1.0.1",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.2.0",
"side-channel": "^1.0.1",
"which-boxed-primitive": "^1.0.1",
"which-collection": "^1.0.0"
},
Expand Down

0 comments on commit 983780a

Please sign in to comment.