Description
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:
IE11, Edge, and Node Chakra fail:
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 valuefalse
- 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