Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deepEqual fails to work with descendants of something with a null prototype #851

Closed
stefanpenner opened this issue Aug 13, 2015 · 4 comments

Comments

@stefanpenner
Copy link
Contributor

the following works as expected.

var a = Object.create(null);
a.foo = 1;
var b = { foo: 1 }

deepEqual(a, b);

Unfortunately, in practice Object.create(null) is fairly costly. To mitigate this cost a specialized NullObject can be used. This Object has similar safe characteristics, but with dramatically reduced allocation costs.

Reference implementation:

function NullObject() { }
NullObject.prototype = Object.create(null, {
  constructor: {
    value: undefined,
    enumerable: false,
    writable: true
  }
});

This leads us to the problem:

var a = new EmptyObject();
a.foo = 1;
var b = { foo: 1 }

deepEqual(a, b); // fails

This fails because, a.constructor !== b.constructor and a.prototype !== null

relevant code in QUnit:

if ( a.constructor !== b.constructor ) {

  // Allow objects with no prototype to be equivalent to
  // objects with Object as their constructor.
  if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
    ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
    return false;
  }
}

As QUnit already special cases null prototyped object with projo deepEquality, could we explore another special case to handle this scenario? Is there an alternative I am missing?

One solution,

would be to also detect a null constructor.

change to the NullObject constructor would be as follows:

function NullObject() { }
NullObject.prototype = Object.create(null, {
  constructor: {
    value: null, // <-- small change
    enumerable: false,
    writable: true
  }
});

Change to QUnit would require also checking for a null constructor.

if ( a.constructor !== b.constructor ) {

  // Allow objects with no prototype to be equivalent to
  // objects with Object as their constructor.
  if ( !( ( ( getProto( a ) === null || getConstructor( a ) === null) && getProto( b ) === Object.prototype ) ||
    ( ( getProto( b ) === null || getConstructor( b ) === null) && getProto( a ) === Object.prototype ) ) ) {
    return false;
  }
}

note: this protocol does appear funky, but would be quite handy in practice

@stefanpenner
Copy link
Contributor Author

Anyone have thoughts on this? Would love to act on feedback.

@stefanpenner
Copy link
Contributor Author

I suspect, we can walk the chain and detect a deeper inheritance of null

@leobalter
Copy link
Member

I'm take the holiday tomorrow to work on this, I want to see if I also bring a test to avoid any regressions.

@stefanpenner
Copy link
Contributor Author

note, i took a quick stab at something: stefanpenner/data@6a53d42 it may or may not be of help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants