Search code examples
javascriptprototypejavascript-objectsprotojavascript-inheritance

Understanding the __extends function generated by typescript?


I am playing with Typescript and trying to understand the compiled Javascript code generated by the compiler

Typescript code:

class A { }
class B extends A { }

Generated Javascript code:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var A = /** @class */ (function () {
    function A() {
    }
    return A;
}());
var B = /** @class */ (function (_super) {
    __extends(B, _super);
    function B() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return B;
}(A));

The Javascript inheritance as per Mozilla docs is this:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

The parts that I do not understand in the Typescript's generated code are this

1. What is the purpose of this line? Looks like it is copying all the keys of A into B? Is this some sort of a hack for static properties?

var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };

2. What is this doing?

function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

I don't understand this part: (__.prototype = b.prototype, new __())

Why does function B() return this?

return _super !== null && _super.apply(this, arguments) || this;

If someone can explain this to me line by line I would be grateful.


Solution

  • I was curious about this myself and couldn't find a quick answer so here is my breakdown:

    What it does

    __extends is a function that simulates single class inheritance in object oriented languages and returns a new constructor function for a derived function that can create objects that inherit from a base object.

    Note 1:

    I wasn't actually aware of this myself but if you do something like the following where all of the values are truthy the variable is set to the value of last item being tested unless one is falsy in which case the variable is set to false:

    // value1 is a function with the definition function() {}
    var value1 = true && true && function() {};
    
    // value2 is false
    var value2 = true  && false && function() {};
    
    // value3 is true
    var value3 = true && function() {} && true;
    

    I mention this because this is the thing that confused me the most when I saw this javascript and it is used a couple of times in the __extends function defintion.

    Note 2: Parameter d (probably stands for derived) and b (probably stands for base) are both constructor functions and not instance objects.

    Note 3:

    prototype is a property of a function and it is a prototype object used by 'constructor' functions (i.e. objects created by using new <function name>()).

    When you use the new operator to construct a new object, the new object's internal [[PROTOTYPE]] aka __proto__ is set to be the function's prototype property.

    function Person() {  
    }
    
    // Construct new object 
    var p = new Person();
    
    // true
    console.log(p.__proto__ === Person.prototype);
    
    // true
    console.log(Person.prototype.__proto__ === Object.prototype);
    

    It isn't a copy. It IS the object.

    When you create a literal object like

    var o = {};
    
    // true    
    console.log(o.__proto__ === Object.prototype);
    

    the new object's __proto__ is set to Object.prototype (the built-in Object constructor function).

    You can set an object's __prototype__ to another object however using Object.create.

    When a property or method isn't found on the current object the object's [[PROTOTYPE]] is checked. If it isn't found then THAT object's prototype is checked. And so it goes checking prototypes until it reaches the final prototype object, Object.prototype. Keep in mind nothing is a copy.

    Note 4 When simulating inheritance in Javascript the 'constructor' functions' prototypes are set.

    function Girl() {  
    }
    
    Girl.prototype = Object.create(Person.prototype);
    
    // true
    console.log(Girl.prototype.__proto__ === Person.prototype);
    
    // true
    console.log(Girl.constructor === Function);
    
    // Best practices say reset the constructor to be itself
    Girl.constructor = Girl;
    
    // points to Girl function
    console.log(Girl.constructor);
    

    Notice how we point the constructor to Girl because it Person's constructor points to the built-in Function.

    You can see the code above in action at: http://jsbin.com/dutojo/1/edit?js,console

    Original:

    var __extends = (this && this.__extends) || (function () {
        var extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return function (d, b) {
            extendStatics(d, b);
            function __() { this.constructor = d; }
            d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
        };
    })();
    

    The break down:

    var __extends = (this && this.__extends) || (function () {
       // gobbledygook
    })();
    

    Keeping my Note 1 above in mind, this first part (and end) is creating a variable called __extends that has the intention of holding a function to set the prototype of the derived class.

    (this && this.__extends) 
    

    is doing what my Note 1 explains. If this is truthy and this.__extends is truthy then the variable __extends is already exists and so set to the existing instance of itself. If not it is set to what comes after the || which is an iife (immediately invoked function expression).

    Now for the gobbledygook which is the actual definition of __extends:

    var extendStatics = Object.setPrototypeOf ||
    

    A variable named extendStatics is set to either the built in Object.setPrototypeOf function of the environment that the script is running in (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)

    OR

    it creates its own version

    ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    

    In Note 3 I discussed __proto__ aka [[PROTOTYPE]] and how it could be set. The code

    { __proto__: [] } instanceof Array
    

    is a test to determine whether the current environment allows setting this property by comparing a literal object's __proto__ set to a literal array with the Array built-in function.

    Referring back to my Note 1 from above and keeping in mind that the javascript instanceof operator returns true or false (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) if the environment evaluates an an object with its prototype property set to the built in Array then extendsStatics is set to

    function (d, b) { d.__proto__ = b; })
    

    If the environment doesn't evaluate it that way then extendStatics is set to this:

    function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }
    

    It does this because __proto__ was never part of the official ECMAScript standard until ECMAScript 2015 (and according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto) is there only for backwards compatibility). If it is supported the __proto__ function is used or else it uses the 'roll your own' version' which does a copy from object b to d for user defined properties.

    Now that the extendStatics function variable defined, a function that calls whatever is inside extendStatics (as well as some other stuff) is returned. Note that parameter 'd' is the sub-class (the one inheriting) and 'b' is the super-class (the one being inherited from):

    return function (d, b) {
            extendStatics(d, b);
            function __() { this.constructor = d; }
            d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
        };
    

    Breaking it down extendStatics is called and the first parameter object (d) has its prototype set to be (b) (recall Note 3 above):

    extendStatics(d, b);
    

    In the next line a constructor function named '__' is declared that assigns its constructor to be the derived (d) constructor function:

    function __() { this.constructor = d; }
    

    if the base (b) constructor function happens to be null this will make sure that the derived will keep its own prototype.

    From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor, the Object.prototype.constructor (all objects are Objects in javascript):

    Returns a reference to the Object constructor function that created the instance object. Note that the value of this property is a reference to the function itself, not a string containing the function's name.

    And

    All objects will have a constructor property. Objects created without the explicit use of a constructor function (i.e. the object and array literals) will have a constructor property that points to the Fundamental Object constructor type for that object.

    So if constructor function '__' was new'd up as is it would create a derived object.

    Lastly there is this line:

    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    

    which sets the prototype of the derived (d) to be a new empty object if the base constructor function happens to be null

    //  b is null here so creates {}
    Object.create(b)
    

    OR

    sets the __ constructor function's prototype to be the base class prototype and then calls __() which has the effect of setting the derived functions constructor to be the derived function.

    (__.prototype = b.prototype, new __()
    

    So basically the final function returned creates a derived constructor function that prototypically inherits from the base constructor function.

    Why does function B() return this?

    return _super !== null && _super.apply(this, arguments) || this;
    

    According to: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

    The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

    Remember B is a constructor function and that is what is being returned in B's definition.

    If you had a Person class (constructor function) that accepted a name parameter in the constructor function you could then call a derived Girl class (constructor function) with the girls name as a parameter.

    // Base constructor function
    function Person(n) {
      // Set parameter n to the name property of a Person
      this.name = n;
    }
    
    function Girl() {
       // Call the Person function with same arguments passed to new Girl
       Person.apply(this, arguments);
       // Set it so all Girl objects created inherit properties and methods from Person
       Girl.prototype = Object.create(Person.prototype);  
       // Make sure the constructor is not set to Person
       Girl.prototype.constructor =  Girl;
    }
    
    var p = new Person("Sally");
    var g = new Girl("Trudy");
    console.log(p.name);
    console.log(g.name);