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:
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:
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]
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" //}
this
in such a function, it is also bound to the global context.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:
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:
var
keyword in a function. They can be accessed by private functions or privileged methods.this.method=function() {}
.Class.prototype.method=function(){}
.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:
retirementAge
variable is a private variable that has no privileged method to get or set its value.country
variable is a private variable created as a constructor argument. Constructor arguments are available as private variables to the object.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.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.