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.
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);