Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why assign Something to Something.prototype.constructor?

I was reading about how the Javascript prototype property works along with inheritance and then began to look through the Angular.js code and came up with some questions.

First off, I read that the prototype property points to an object that has a "constructor" property which points back to the original function that is used to create the object. So for example:

// This is the constructor
function Shape() {
    this.position = 1;
}

// The constructor points back to the original function we defined
Shape.protoype.constructor == Shape;

The prototype also contains any other methods or properties that have been defined on it by us or by the Javascript language itself, and these are shared by all instances of the object. If you want an object called Square to inherit from Shape you need to set Square's prototype equal to a new instance of Shape because the internal [[prototype]] property of Square.prototype gets set to the public object value of the Shape.prototype property.

function Square() {}
Square.prototype = new Shape();
var square = new Square();
square.position; // This will output 1

This all makes sense to me.

However, the Angular.js code that I have a question about seems to be related to all of this but does something that I do not understand. It doesn't appear to be dealing with inheritance so I can understand why there would be differences, but I'm just curious why they wrote it the way they did.

Within Angular.js, there is a HashMap object and a Lexer object, but they are defined differently yet appear to be instantiated and used in the same exact way. The Lexer constructor is first defined, and then they set the prototype to an object literal containing methods that should be shared by all instances of the Lexer. This all makes sense. What I don't understand is why they specify the "constructor" property and set it to just "Lexer" when they don't for HashMap below.

var Lexer = function(options) {
    this.options = options;
};

// Notice they specify Lexer as the constructor even though they don't for HashMap below
Lexer.prototype = {
    constructor: Lexer,
    lex: function(text) { ... },
    is: function(ch, chars) { ... },
    peek: function(i) { ... },
    isNumber: function(ch) { ... },
    isWhitespace: function(ch) { ... },
    isIdent: function(ch) { ... },
    isExpOperator: function(ch) { ... },
    throwError: function(error, start, end) { ... },
    readNumber: function() { ... },
    readIdent: function() { ... },
    readString: function(quote) { ... }
};

Then if you look at the HashMap code, they do the same thing except that they do not specify the constructor property. Why is this? It appears to work exactly the same, and I've tested that the constructor still gets called.

// The HashMap Constructor
function HashMap(array, isolatedUid) {
    if (isolatedUid) {
        var uid = 0;
        this.nextUid = function() {
            return ++uid;
        };
    }
    forEach(array, this.put, this);
}

HashMap.prototype = {
    put: function(key, value) { ... },
    get: function(key) { ... },
    remove: function(key) { ... }
};

So is the constructor property optional when there is no inheritance, so maybe one person wrote the Lexer and another the HashMap and one decided to specify the constructor?

like image 809
Jordan Avatar asked Feb 24 '15 18:02

Jordan


1 Answers

By default, every prototype has a constructor property that refers to the function that it "belongs" to.

function A() {
}

console.log(A.prototype.constructor === A); // true

If you overwrite a prototype in its entirety with an object literal or with some other constructed prototype, this constructor value will get wiped away and be left undefined or with some other value.

function A() {
}
A.prototype = {
    greeting: "Hello!"
};

console.log(A.prototype.constructor === A); // false

The prototype constructor property is not needed in order for the constructor to operate correctly, but people will often reassign prototype.constructor back to its initial value in order to maintain consistency, and that's what you are seeing for Lexer.

Consider:

function A() {
}

function B() {
}

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

var b = new B();

console.log(b.constructor === A); // true
console.log(b.constructor === B); // false

B.prototype.constructor = B;

console.log(b.constructor === A); // false
console.log(b.constructor === B); // true

One can only speculate as to why this was left out for HashMap. I doubt it was done for any good reason and may have just been an oversight.

like image 95
JLRishe Avatar answered Sep 18 '22 01:09

JLRishe