Skip to content

instanceof returns incorrect result within function after prototype chain has been extended with Object.setPrototypeOf() #5915

Open
@nateps

Description

@nateps

Hey there! I authored / maintain a web framework (https://derbyjs.com/). We use Object.setPrototypeOf() in a manner similar to how Node.js util.inherits() is implemented. This pattern is also demonstrated in the MDN docs for Object.setPrototypeOf(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf.

We discovered an issue where an instanceof check returns the incorrect result in IE 11, Edge, and Node Chakra only. In our code, this leads to an error being thrown in a case where the instanceof check is supposed to guard against the prototype chain being appended to twice. Here is a simplified repro case that demonstrates the issue:

// class Component {}
function Component() {}

function isBasePrototype(object) {
  return (object === Function.prototype) ||
    (object === Object.prototype) ||
    (object === null);
}
function getRootPrototype(object) {
  while (true) {
    var prototype = Object.getPrototypeOf(object);
    if (isBasePrototype(prototype)) return object;
    object = prototype;
  }
}
function extendComponent(klass) {
  console.log('extendComponent: klass.prototype instanceof Component', klass.prototype instanceof Component);
  console.log('extendComponent: Box.prototype instanceof Component', Box.prototype instanceof Component);
  // Don't do anything if the constructor already extends Component
  if (klass.prototype instanceof Component) return;
  // Find the end of the prototype chain
  var rootPrototype = getRootPrototype(klass.prototype);
  // Establish inheritance with the pattern that Node's util.inherits() uses
  // if Object.setPrototypeOf() is available (all modern browsers & IE11).
  // This inhertance pattern is not equivalent to class extends, but it does
  // work to make instances of the constructor inherit the desired prototype
  // https://github.com/nodejs/node/issues/4179
  Object.setPrototypeOf(rootPrototype, Component.prototype);
}

// class Shape {}
// class Box extends Shape {}
function Shape() {}
function Box() {}
Box.prototype = Object.create(Shape.prototype);
Box.prototype.constructor = Box;

console.log('before extend: Box.prototype instanceof Component', Box.prototype instanceof Component);
extendComponent(Box);
console.log('after extend 1: Box.prototype instanceof Component', Box.prototype instanceof Component);
extendComponent(Box);
console.log('after extend 2: Box.prototype instanceof Component', Box.prototype instanceof Component);

In Chrome, Firefox, and Safari, we get the correct output:
screen shot 2019-01-18 at 10 06 31 am

IE11, Edge, and Node Chakra fail:
screen shot 2019-01-18 at 10 06 06 am

Some observations:

  • The same problem occurs both with ES5 class-like function constructors as well as ES6 classes.
  • The instanceof outside of the function returns the correct value true and then instanceof within the function returns the incorrect value false
  • This occurs when setting the prototype of the prototype of a class, but if Box doesn't inherit from any other classes, this issue does not occur.

For now, we're going to work around this issue with some extra checks in the code, but I'm pretty sure this is a bug in Chakra. Please let me know if you determine otherwise.

Best,
Nate

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions