Instance properties versus prototype properties

Instance properties are the properties that are part of the object instance itself, as shown in the following example:

function Player() {
  this.isAvailable = function() {
    return "Instance method says - he is hired";
  };
}
Player.prototype.isAvailable = function() {
  return "Prototype method says - he is Not hired";
};
var crazyBob = new Player();
console.log(crazyBob.isAvailable());

When you run this example, you will see that Instance method says - he is hired is printed. The isAvailable() function defined in the Player() function is called an instance of Player. This means that apart from attaching properties via the prototype, you can use the this keyword to initialize properties in a constructor. When we have the same functions defined as an instance property and also as a prototype, the instance property takes precedence. The rules governing the precedence of the initialization are as follows:

  • Properties are tied to the object instance from the prototype
  • Properties are tied to the object instance in the constructor function

This example brings us to the use of the this keyword. It is easy to get confused by the this keyword because it behaves differently in JavaScript. In other OO languages such as Java, the this keyword refers to the current instance of the class. In JavaScript, the value of this is determined by the invocation context of a function and where it is called. Let's see how this behavior needs to be carefully understood:

  • When this is used in a global context: When this is called in a global context, it is bound to the global context. For example, in the case of a browser, the global context is usually window. This is true for functions also. If you use this in a function that is defined in the global context, it is still bound to the global context because the function is part of the global context:
    function globalAlias(){
      return this;
    }
    console.log(globalAlias()); //[object Window]
  • When this is used in an object method: In this case, this is assigned or bound to the enclosing object. Note that the enclosing object is the immediate parent if you are nesting the objects:
    var f = {
      name: "f",
      func: function () {
        return this; 
      }
    };
    console.log(f.func());  
    //prints - 
    //[object Object] {
    //  func: function () {
    //    return this; 
    //  },
    //  name: "f"
    //}
  • When there is no context: A function, when invoked without any object, does not get any context. By default, it is bound to the global context. When you use this in such a function, it is also bound to the global context.
  • When this is used in a constructor function: As we saw earlier, when a function is called with a new keyword, it acts as a constructor. In the case of a constructor, this points to the object being constructed. In the following example, f() is used as a constructor (because it's invoked with a new keyword) and hence, this is pointing to the new object being created. So when we say this.member = "f", the new member is added to the object being created, in this case, that object happens to be o:
    var member = "global";
    function f()
    {
      this.member = "f";
    }
    var o= new f(); 
    console.log(o.member); // f

We saw that instance properties take precedence when the same property is defined both as an instance property and prototype property. It is easy to visualize that when a new object is created, the properties of the constructor's prototype are copied over. However, this is not a correct assumption. What actually happens is that the prototype is attached to the object and referred when any property of this object is referred. Essentially, when a property is referenced on an object, either of the following occur:

  • The object is checked for the existence of the property. If it's found, the property is returned.
  • The associated prototype is checked. If the property is found, it is returned; otherwise, an undefined error is returned.

This is an important understanding because, in JavaScript, the following code actually works perfectly:

function Player() {
  isAvailable=false;
}
var crazyBob = new Player();
Player.prototype.isAvailable = function() {
  return isAvailable;
};
console.log(crazyBob.isAvailable()); //false

This code is a slight variation of the earlier example. We are creating the object first and then attaching the function to its prototype. When you eventually call the isAvailable() method on the object, JavaScript goes to its prototype to search for it if it's not found in the particular object (crazyBob, in this case). Think of this as hot code loading—when used properly, this ability can give you incredible power to extend the basic object framework even after the object is created.

If you are familiar with OOP already, you must be wondering whether we can control the visibility and access of the members of an object. As we discussed earlier, JavaScript does not have classes. In programming languages such as Java, you have access modifiers such as private and public that let you control the visibility of the class members. In JavaScript, we can achieve something similar using the function scope as follows:

  • You can declare private variables using the var keyword in a function. They can be accessed by private functions or privileged methods.
  • Private functions may be declared in an object's constructor and can be called by privileged methods.
  • Privileged methods can be declared with this.method=function() {}.
  • Public methods are declared with Class.prototype.method=function(){}.
  • Public properties can be declared with this.property and accessed from outside the object.

The following example shows several ways of doing this:

function Player(name,sport,age,country){ 

  this.constructor.noOfPlayers++;

  // Private Properties and Functions
  // Can only be viewed, edited or invoked by privileged members
  var retirementAge = 40;
  var available=true;
  var playerAge = age?age:18;
  function isAvailable(){ return available && (playerAge<retirementAge); } 
  var playerName=name ? name :"Unknown";
  var playerSport = sport ? sport : "Unknown";

  // Privileged Methods
  // Can be invoked from outside and can access private members
  // Can be replaced with public counterparts
  this.book=function(){ 
    if (!isAvailable()){ 
      this.available=false;
    } else {
      console.log("Player is unavailable");
    } 
  };
  this.getSport=function(){ return playerSport; }; 
  // Public properties, modifiable from anywhere
  this.batPreference="Lefty";
  this.hasCelebGirlfriend=false;
  this.endorses="Super Brand";
} 

// Public methods - can be read or written by anyone
// Can only access public and prototype properties
Player.prototype.switchHands = function(){ this.batPreference="righty"; }; 
Player.prototype.dateCeleb = function(){ this.hasCelebGirlfriend=true; } ;
Player.prototype.fixEyes = function(){ this.wearGlasses=false; };

// Prototype Properties - can be read or written by anyone (or overridden)
Player.prototype.wearsGlasses=true;

// Static Properties - anyone can read or write
Player.noOfPlayers = 0;


(function PlayerTest(){ 
  //New instance of the Player object created.
  var cricketer=new Player("Vivian","Cricket",23,"England"); 
  var golfer =new Player("Pete","Golf",32,"USA"); 
  console.log("So far there are " + Player.noOfPlayers + " in the guild");
  
  //Both these functions share the common 'Player.prototype.wearsGlasses' variable
  cricketer.fixEyes(); 
  golfer.fixEyes(); 


  cricketer.endorses="Other Brand";//public variable can be updated 

  //Both Player's public method is now changed via their prototype 
  Player.prototype.fixEyes=function(){ 
    this.wearGlasses=true;
  };
  //Only Cricketer's function is changed
  cricketer.switchHands=function(){
    this.batPreference="undecided";
  };

})();

Let's understand a few important concepts from this example:

  • The retirementAge variable is a private variable that has no privileged method to get or set its value.
  • The country variable is a private variable created as a constructor argument. Constructor arguments are available as private variables to the object.
  • When we called cricketer.switchHands(), it was only applied to the cricketer and not to both the players, although it's a prototype function of the Player itself.
  • Private functions and privileged methods are instantiated with each new object created. In our example, new copies of isAvailable() and book() would be created for each new player instance that we create. On the other hand, only one copy of public methods is created and shared between all instances. This can mean a bit of performance gain. If you don't really need to make something private, think about keeping it public.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.83.150