- JavaScript:Moving to ES2015
- Ved Antani Simon Timms Narayan Prusty
- 1295字
- 2021-07-09 19:07:36
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: Whenthis
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 usuallywindow
. This is true for functions also. If you usethis
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 anew
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 anew
keyword) and hence,this
is pointing to the new object being created. So when we saythis.member = "f"
, the new member is added to the object being created, in this case, that object happens to beo
: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 thecricketer
and not to both the players, although it's a prototype function of thePlayer
itself. - Private functions and privileged methods are instantiated with each new object created. In our example, new copies of
isAvailable()
andbook()
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.