Search code examples
javascriptconstructorprototypesubclassprototypal-inheritance

instanceof check works on subclass without setting constructor


I have the following JavaScript code

function Parent() {
}

function Child() {
}

Child.prototype = Object.create(Parent.prototype);

Note the absence of the statement

Child.prototype.constructor = Child;

My understanding is that as the constructor property has not been set the instanceof checks should fail for new instances of Child class.

var child = new Child();
child instanceof Child; //Should give false

I verified that the constructor is incorrectly set enter image description here

But when I run child instanceof Child it gave me true

enter image description here

But it should be false as constructor property is not set on Child's prototype to be Child.

Environment

Google Chrome Version 48.0.2564.109 (64-bit)
Ubuntu 14.04 LTS

Solution

  • My understanding is that as the constructor property has not been set the instanceof checks should fail for new instances of Child class.

    The constructor property isn't used by instanceof at all. In fact, until ES2015 (aka ES6), the constructor property wasn't used for anything in JavaScript itself. It was defined as existing on the default objects the runtime assigns to the prototype property on functions, but not used.

    Let's look at how instanceof works. Consider:

    o instanceof Foo
    

    As of ES2015, instanceof will check to see if Foo implements an internal operation called @@hasInstance and, if so, it will use that operation to ask Foo whether o is an instance.

    In ES5 and earlier (or if Foo doesn't have that internal operation), instanceof will look to see if the object Foo.prototype points to is anywhere on o's prototype chain. (If "prototype chain" is not a familiar term, see the * at the end of the answer and then come back.) If so, it returns true; if not, it returns false.

    E.g., here's a conceptual implementaton of instanceof, hand-waving away some details:

    function isAnInstance(obj, func) {
        // Start: ES2015+ part
        const hasInstance = func[Symbol.hasInstance];
        if (hasInstance) {
            return hasInstance.call(func, obj);
        }
        // End: ES2015+ part
        // Start: The OrdinaryHasInstance specification operation
        for (let p = Object.getPrototypeOf(obj); p; p = Object.getPrototypeOf(p)) {
            if (p === func.prototype) {
                return true;
            }
        }
        return false;
        // End: The OrdinaryHasInstance specification operation
    }
    

    The functions built into JavaScript do have @@hasInstance (they inherit it from Funtion), but all it does is the OrdinaryHasInstance operation shown above. See the specification here, here, and here.

    We can see that constructor isn't involved from your question, and also from this simple demonstration:

    var p = {};
    var o = Object.create(p);
    var Foo = function() {};
    Foo.prototype = p;
    console.log(o instanceof Foo);                // true
    console.log(o.hasOwnProperty("constructor")); // false
    console.log(
        Object.getPrototypeOf(o).constructor === Object
    );                                            // true

    Note that:

    1. o wasn't created via Foo.
    2. In fact, Foo didn't even exist until after o was created.
    3. o doesn't have its own constructor property.
    4. o's prototype's constructor property is Object, not Foo.

    ...and yet instanceof says "Yep, looks like it's a Foo." :-) Purely because the object Foo.prototype points to is also on o's prototype chain.


    * "prototype chain"

    You clearly know that objects in JavaScript have prototypes from which they inherit properties. Those prototypes are objects, and so they have prototypes. So you get a "chain" of prototypes.

    Consider a two-level (arguably three-level) inheritance hierarchy, here in ES5:

    function Base() {
    }
    
    function Derived() {
        Base.call(this);
    }
    Derived.prototype = Object.create(Base.prototype);
    Derived.prototype.constructor = Derived;
    

    ...or in ES2015:

    class Base {
    }
    class Derived extends Base {
    }
    

    Now we use it:

    var d = new Derived();
    

    (Where you see "the d object" and similar in the below, I do of course mean "the object d refers to" — but that's really verbose.)

    Now, the d object's prototype is Derived.prototype. Derived.prototype's prototype is Base.prototype. Base.prototype's prototype is Object.prototype. Object.prototype doesn't have a prototype (its [[Prototype]] internal slot is null).

    Those objects are the prototype chain underlying d, and they mean that d is instanceof Object, instanceof Base, and instanceof Derived.