javascriptdynamic-languagesprototype-oriented

How does JavaScript .prototype work?


I'm not that into dynamic programming languages but I've written my fair share of JavaScript code. I never really got my head around this prototype-based programming, does any one know how this works?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

I remember a lot discussion I had with people a while back (I'm not exactly sure what I'm doing) but as I understand it, there's no concept of a class. It's just an object, and instances of those objects are clones of the original, right?

But what is the exact purpose of this ".prototype" property in JavaScript? How does it relate to instantiating objects?

Update: correct way

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

Also these slides really helped a lot.


Solution

  • Every JavaScript object has an internal "slot"(i.e. hidden property) called [[Prototype]](square brackets are deliberate) whose value is either null or an object. The value is colloquially known as "the prototype of that object."

    These prototypes connect objects into prototype chains for looking up properties. When you try to access a property from an object by either obj.propName or obj['propName'], and the object does not own(obj.hasOwnProperty('propName')) the property, the runtime will try to find the property from the prototype chain of that object by recursively referencing the [[Prototype]] slots until a null is reached.

    An object's [[Prototype]] is initially set during object creation, and modern JavaScript allows read and write access to the [[Prototype]] in the following ways:

    1. The new Ctor() syntax, which sets [[Prototype]] to Func.prototype for the newly created object.
    2. The extends keyword, which configures the prototype chain for the class syntax.
    3. Object.create, which will set the supplied argument as the [[Prototype]] of the resulting object.
    4. Object.getPrototypeOf and Object.setPrototypeOf (get/set the [[Prototype]] after object creation)
    5. The standardized accessor property named __proto__. (but it has unusual behaviours when an object has a prototype of null.)

    Note that the .prototype is a genuine property, not an internal slot. Therefore, all classes, and all functions that can be used with the new operator, have a property named .prototype in addition to their own [[Prototype]] internal slot. This dual use of the word "prototype" is the source of endless confusion amongst newcomers to the language.

    Before JavaScript introduced the class syntax, people used the syntax new Ctor() to simulate classical inheritance by prototypical inheritance:

    For example, here's one way:

    function Child() {}
    function Parent() {}
    Parent.prototype.inheritedMethod = function () { return 'this is inherited' }
    
    function inherit(child, parent) {
      child.prototype = Object.create(parent.prototype)
      child.prototype.constructor = child
      return child;
    }
    
    Child = inherit(Child, Parent)
    const o = new Child
    console.log(o.inheritedMethod()) // 'this is inherited'
    

    ...and here's another way:

    function Child() {}
    function Parent() {}
    Parent.prototype.inheritedMethod = function () { return 'this is inherited' }
    
    function inherit(child, parent) {
        function tmp() {}
        tmp.prototype = parent.prototype
        const proto = new tmp()
        proto.constructor = child
        child.prototype = proto
        return child
    }
    
    Child = inherit(Child, Parent)
    const o = new Child
    console.log(o.inheritedMethod()) // 'this is inherited'
    

    But if you're careful, you might have found that Parent's instance fields are not considered in these two cases. Fortunately, in ES2015 these details are hidden and we can just write a one-liner version thanks to the extends syntax:

    class Parent { inheritedMethod() { return 'this is inherited' } }
    class Child extends Parent {}
    
    const o = new Child
    console.log(o.inheritedMethod()) // 'this is inherited'
    

    ...the resulting object's [[Prototype]] will be set to an instance of Parent, whose [[Prototype]], in turn, is Parent.prototype.

    Finally, if you create a new object via Object.create(foo), the resulting object's [[Prototype]] will be set to foo.