Voir cette page en français

Aug 03 2011

Everything you ever wanted to know about prototype without ever having sought it

Category: JavaScript,Node.js

Without explicit notice, all code examples given later are interpreted by V8 JavaScript Engine via NodeJS.

The property prototype

All Javascript objects have methods and properties so functions, as objects in their own, have their own properties and methods :
> function foo(parameter){return !!parameter;}
> foo.length
1
> foo.constructor
[Function: Function]
> foo.toString()
function foo(parameter){return !!parameter;}
  
The functions, and only the functions, have the important native property prototype ; we will detail in this article how it works and good practices to follow when handling it.

When the property prototype is used ?

Affecting any value to the property prototype function foo will have no effect at all when calling the function by foo(), it is not true calling it by new foo()
> function foo(parameter){return !!parameter;}
> foo.prototype.bar = "I'm foo.prototype.bar"
'I\'m foo.prototype.bar'
> foo().bar
> new foo().bar
'I\'m foo.prototype.bar'
  
Let us then study the differences between calling new foo(), said procedural call and new foo() said as a constructor call.

differences between calling foo() and new foo()

  • Case of procedural call to a function (e.g. foo())
    • the returned value is this returned by the return statement in the body of the function itself (undefined if the function returns nothing ;
    • In the body of the function, the keyword this represents the scope in which the code runs ; the global scope is window in a navigator, global in NodeJS.
      Into Firebug 
      >>> function foo(){return this === window;}
      undefined
      >>> foo()
      true


      Into NodeJS 
      > function foo(){return this === global;}
      > foo()
      true


      More generally :
      > var scope = {
      ... ini: function() {
      ... return this === scope;
      ... }
      ... };
      >
      > scope.ini();
      true
    • As we have already seen, the property prototype is not used in the case of a procedural call.
  • Case of calling a function like a constructor (e.g. new foo())
    • the returned value is always an object whatever the value returned by the function itself ;
      > function foo(){return 1;}
      > new foo()
      {}
                  
    • in the body of the function, the keyword this is the object that will actually be returned :
      > function foo(){this.bar = 1;}
      > a = new foo()
      { bar: 1 }
      > typeof bar
      'undefined'
      > a.bar
      1
                  
    • any object returned by a constructor, via the operator new, keep a hidden reference to the property prototype of the constructor. This reference provides access to the hidden constructor's prototype attributes from the object itself.
      1. > function foo(){}
      2. > foo.prototype.aProperty = "I'm foo.prototype.aProperty"
      3. 'I\'m foo.prototype.aProperty'
      4. > foo.prototype.aMethod = function(){return "I'm foo.prototype.aMethod";}
      5. [Function]
      6. > a = new foo()
      7. {}
      8. > a.aProperty
      9. 'I\'m foo.prototype.aProperty'
      10. > a.aMethod()
      11. 'I\'m foo.prototype.aMethod'
      12. > foo.prototype.aMethod = function(){return "I have been modified";}
      13. [Function]
      14. > a.aMethod()
      15. 'I have been modified'
This command sequence requires several comments.
  • we see, line 6 and 7, that « a » is an empty object, however, line 8, we can access to the attribute a.aProperty defined in the prototype of the constructor foo. This is because, internally, Javascript looks in the object itself then, if it can not find what is requested, it looks in the constructor's prototype . Proof by example (a nod to logicians) :
    > function foo(){this.name = "I'm this.name";}
    > a = new foo()
    { name: 'I\'m this.name' }
    > foo.prototype.name = "I'm foo.prototype.name"
    'I\'m foo.prototype.name'
    > a.name
    'I\'m this.name'
    > delete(a.name)
    true
    > a.name
    'I\'m foo.prototype.name'
    > a.name = "I'm a.name"
    'I\'m a.name'
    > a.name
    'I\'m a.name'
    > delete(a.name)
    true
    > a.name
    'I\'m foo.prototype.name'
    > delete(a.name)
    true
    > a.name
    'I\'m foo.prototype.name'
    > delete(a.constructor.prototype.name)
    true
    > a.name
    >
            
    When the property, name in this case, belongs to the object itself is said to be a own property of the object. The method hasOwnProperty of all object allows to differentiate own properties between prototype properties  :
    > function foo(){this.name = "I'm this.name"}
    > foo.prototype.pName = "I'm foo.prototype.pName"
    'I\'m foo.prototype.pName'
    > a = new foo()
    { name: 'I\'m this.name' }
    > a.name
    'I\'m this.name'
    > a.pName
    'I\'m foo.prototype.pName'
    > a.hasOwnProperty('name')
    true
    > a.hasOwnProperty('pName')
    false
            
  • line 12, the modification of the constructor's prototype method aMethod (foo.prototype.aMethod) is reflected when calling a.aMethod() whereas the object is already created ; one might hastily conclude that a.aMethod points to (is a reference to) a.consructor.prototype.aMethod, but this is not the case !
    > function foo(){}
    > foo.prototype.bar = 'bar'
    'bar'
    > a = new foo()
    {}
    > a.bar
    'bar'
    > a.constructor.prototype
    { bar: 'bar' }
    > a.constructor.prototype = null
    null
    > a.bar
    'bar'
            
    The link between an obect and the constructor's prototype is not objet.constructor.prototype, it is a hidden reference named __proto__ :
    a.__proto__
    { bar: 'bar' }
    > a.__proto__ = {}
    {}
    > a.bar
    >
            
    Not fully convinced ?
    > var a = {
    ...   sum: function () {
    ...     return this.x + this.y;
    ...   },
    ...   z: "Hello, I'm z"
    ... };
    >
    > var b = {
    ...   x: 10,
    ...   y: 20,
    ...   __proto__: a
    ... };
    >
    > b.z;
    'Hello, I\'m z'
    > b.sum();
    30
            
    ¡¡ Attention !!
    • __proto__ is a property of the instance whereas prototype is a property of the constructor ;
    • the property __proto__ does not exist in Internet Explorer and should not be used in your WEB developments if you want your code to be compatible with that navigator thing ;
    • best practices recommend not to use the method __proto__ in yours scripts except for learning purposes.
Note : When defining a function that is intended to be used as a constructor it is a naming conventions rule to begin the name with a uppercase letter. I strongly encourage to respect this kind of recommendation and, personally, I strive to respect the Felix's Node.js Style Guide.

Overwriting the prototype

We must take particular care when overwriting the prototype of a constructor. Indeed, in the following code the value returned by a.constructor === Foo is the expected :
> var Foo = function(){}
> Foo.prototype.sayHello = function(){console.log('Bonjour');}
[Function]
> var a = new Foo()
> a.constructor === Foo
true
    
Whereas in the following code, where the prototype is overwritten by the constructor, the value of a.constructor === Foo is no longer what it is expected :
> var Foo = function(){}
> Foo.prototype = {sayHello: function(){console.log('Bonjour');}}
{ sayHello: [Function] }
> var a = new Foo()
> a.constructor === Foo
false
    
Thus, when we overwrite the prototype of a constructor, we must also correctly reposition the value of the prototype's constructor :
> var Foo = function(){}
> Foo.prototype = {sayHello: function(){console.log('Bonjour');}}
{ sayHello: [Function] }
> var a = new Foo()
> a.constructor === Foo
false
> Foo.prototype.constructor = Foo
[Function]
> a.constructor === Foo
true
    

What to remember

  • only functions have natively a property prototype, which is an empty object by default ;
  • the property prototype of a function is used only when called with the operator new ;
  • all the components, methods and properties, of the property prototype of a constructor can be accessed from any object instantiated with this constructor as his own, even if the component is added after the creation of the object because the constructor's prototype is shared by all objects instantiated by this constructor :
    > function Iam(name){this.name = name;}
    > a = new Iam('foo')
    { name: 'foo' }
    > b = new Iam('bar')
    { name: 'bar' }
    > a.name
    'foo'
    > b.name
    'bar'
    > Iam.prototype.shared = 'Shared with all the instances of Iam'
    'Shared with all the instances of Iam'
    > a.shared
    'Shared with all the instances of Iam'
    > b.shared
    'Shared with all the instances of Iam'
            
  • we can add methods to native constructors :
    > if (!String.reverse) {
    ...   String.prototype.reverse = function() {
    ...     return this.split('').reverse().join('');
    ...   };
    ... }
    > 'abc'.reverse()
    'cba'
            
  • we can differentiate between a own property of an object and a property inherited from the prototype with the method hasOwnProperty of the object ;
  • if we overwrite the property prototype of a constructor, we must reposition the value of prototype.constructor of the constructor :
    > var Dog = function(){}
    > Dog.prototype = {paws: 4, hair: true}
    > Dog.prototype.constructor = Dog
    [Function]
            

Sources and supplements