Constructor function introduction
You can use a function as a constructor to create objects, if the constructor function is named Person then the object(s) created with that constructor are instances of Person.
The member
Even though bob, ben and all other created Person instances share walk the function will behave differently per instance because in the walk function it uses
If ben was waiting for a red light and and bob was at a green light; then you'll invoke walk() on both ben and bob obviously something different would happen to ben and bob.
Shadowing members happens when we do something like
When asking for bob.walk you'll get the Person.prototype.walk function because
So assignment of a member would cause JavaScript to not look it up in the prototype chain and assign value to that. Instead it would assign the value to an already existing member of the object instance or create it and then assign the value to it.
The next part (More about prototype) will explain this with sample code and demonstrate how to inherit.
More about prototype
Prototype is used to define a property that is shared for all instances (functions or default immutable values). For properties that are unique per instance you use
In the previous example we see that when RussionMini "inherits" from Hamster by setting RussionMini.prototype to an instance of Hamster actually creates instance variables (this.food) on RussionMini's prototype. The food member is shadowed by invoking Hamster.apply(this,arguments) but it's still undesirable to have an instance variable on the prototype.
And what if we would like to set Hamser as RussionMini's prototype without creating a Hamster instance? Consider the following code:
Sometimes
You want the 'child' (=RussionMini) to do something extra. When RussionMini can call the Hamster code to do something and then do something extra you don't need to copy and paste Hamster code to RussionMini.
In the following example we assume that a Hamster can run 3km an hour but a Russion mini can only run half as fast. We can hard code 3/2 in RussionMini but if this value were to change we have multiple places in code where it needs changing. Here is how we use Hamster.prototype to get the parent (Hamster) speed.
this.constructor
The constructor property is included in the prototype by JavaScript, you can change it but it should point to the constructor function. So
Inherits sets the constructor to point to the right constructor (RussionMini). An alternative to the helper function would be
In Java when a Child overrides a Parent function you can call it's parent function with super.someMethod(). When re factoring you may have to re name Parent many times and would like to cut back on the times you hard code the Parent constructor name.
A wrong way would be to set the Parent as a value of the prototype or even worse; as a value of
Some things are better not to be inherited, if a Hamster can run and can speak than a Hamster should not inherit from run or speak (an object can only "inherit" from one other object). A hamster is not a run or speak but rather a Hamster can run and speak.
For running and speaking we have instance specific members (like
In all the example code you'll see
The this variable actually refers to the invoking object, it refers to the object that came before the function.
To clarify see the following code:
When Child calls a Parent (
Python has keyword arguments with default values:
With named parameters you could add currency to
Private instance specific members
Private members are methods or properties of an object that be accessed by the same object (for example private variable hungry in Person can only be accessed by a Person object). Let's say we have a Hamster and we would like to have a food property but when accessing this property we would like to force people to use the get and set methods. The code could look something like this:
The disadvantage of using
The advantage is that Hamster now has private variables (although many libraries use
There is a pattern that can be used to reduce a lot of publicly accessible members from showing up by creating 2 closures per instance. It can be found here: http://stackoverflow.com/a/19879651/1641941
Privates through Object.defineProperty
To protect your private variables using Object.create or Object.defineProperty() isn't full proof. When assigning new values you can stimulate use of getters and setters. But the value that the getter and setter function would manipulate would still be publicly accessible (or you have to use closures again). When invoking mutator methods on object properties will change their value.
For privates that don't need to be instance specific you can do the following:
We now have functions on the prototype that can only be accessed by other function within the
You can use a function as a constructor to create objects, if the constructor function is named Person then the object(s) created with that constructor are instances of Person.
var Person = function(name){
this.name = name;
};
Person.prototype.walk=function(){
this.step().step().step();
};
var bob = new Person("Bob");
Person is the constructor function. When you create an instance using Person you have to use the new keyword:var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(ben.name);//=Ben
The property/member name
is instance specific, it's different for bob and benThe member
walk
is shared for all instances bob and ben are instances of Person so they share the walk member (bob.walk===ben.walk).bob.walk();ben.walk();
Because walk() could not be found on bob direcly JavaScript will look for it in the Person.prototype as this is the constructor of bob. If it can't be found there it'll look on Object.prototype. This is called the prototype chain.Even though bob, ben and all other created Person instances share walk the function will behave differently per instance because in the walk function it uses
this
. The value of this
will be the invoking object; for now let's say it's the current instance so for bob.walk()
"this" will be bob. (more on "this" and the invoking object later).If ben was waiting for a red light and and bob was at a green light; then you'll invoke walk() on both ben and bob obviously something different would happen to ben and bob.
Shadowing members happens when we do something like
ben.walk=22
, even though bob and ben share walk
the assignment of 22 to ben.walk will not affect bob.walk. This is because that statement will create a member called walk
on ben directly and assign it a value of 22. There will be 2 different walk members: ben.walk and Person.prototype.walk.When asking for bob.walk you'll get the Person.prototype.walk function because
walk
could not be found on bob. Asking for ben.walk however will get you the value 22 because the member walk has been created on ben and since JavaScript found walk on ben it will not look in the Person.prototype.So assignment of a member would cause JavaScript to not look it up in the prototype chain and assign value to that. Instead it would assign the value to an already existing member of the object instance or create it and then assign the value to it.
The next part (More about prototype) will explain this with sample code and demonstrate how to inherit.
More about prototype
Prototype is used to define a property that is shared for all instances (functions or default immutable values). For properties that are unique per instance you use
this
. function Hamster(){
this.food=[];
}
//primitive values are string, boolean and number
// they are immutable (=can't be changed with someVar.changeMe())
Hamster.prototype.primitiveVal=22;
Hamster.prototype.objectVal=[];
var lex=new Hamster();
var bob=new Hamster();
// no extra steps needed for food
// each hamster instance will have it's own
lex.food.push("lexfood");
//re assigning an object property does not
//affect other instances
//this because it adds objectVal to the lex instance
//this behavior on assignment is called shadowing
//this objectVal is found before the prototype
//version of objectVal in Hamster
lex.objectVal=[22,33];
lex.objectVal.push(44);
console.log("bob.objectVal",bob.objectVal);//=[]
//going to delete the objectVal property assigned to lex instance
delete lex.objectVal;
// lex is now using objectVal from Hamster.prototype
console.log("lex.objectVal",lex.objectVal);//=[]
//the following push is done on the objectVal
//of Hamster.prototype, not the lex instance
lex.objectVal.push(1);
console.log("bob.objectVal",bob.objectVal);//=[1]<== the value pushed by lex
//here it looks like primitive vals aren't shared
//but in reality they are, you can't change a primitive
//value other than assigning another value to it
//like val=,val++,val+=... primitives do not have
//val.changeMe() (=immutable) so changing lex.primitiveVal by
//assigning a new value actually adds a primitiveVal property to
//the lex instance and assigns a value of 'lex val' to it
//when asking for lex.primitiveVal it gives the value of
//lex.primitiveVal (not Hamster.prototype.primitiveVal)
lex.primitiveVal="lex val";
console.log("bob.primitiveVal",bob.primitiveVal);//=22
//when deleting lex.primitiveVal then lex.primitiveVal
//will revert to Hamster.prototype.primitiveVal
delete lex.primitiveVal;
console.log("lex.primitiveVal",lex.primitiveVal);//=22
// Now lets create a new type definition (constructor function)
// that is a type of Hamster
// a "subclass" of Hamster, in JS you'd say has Hamster as it's prototype
// or "is a" Hamster
function RussionMini(){
//If you don't execute the following line of code
//then mini and betty would share the same food
//because the prototype of RussionMini will be an(one) instance of Hamster
Hamster.apply(this,arguments);
//Hamster.apply(this,arguments) executes every line of code
//in the Hamster body where the value of "this" is
//the to be created RussionMini (once for mini and once for betty)
}
//setting RussionMini's prototype (later we'll see better ways of doing this)
RussionMini.prototype=new Hamster();
mini=new RussionMini();
betty = new RussionMini();
mini.food.push("mini's food");
console.log(betty.food);//=[]
//I can delete mini.food and mini would still have it's food property
//from the Hamster instance that is RussionMini.prototype
delete mini.food;
console.log("mini.food",mini.food);//=[]
// if the RussionMini function executes Hamster.apply(this,arguments)
// then why set the prototype? because instenceof
console.log(mini instanceof Hamster);//true
// and added features to Hamster.prototype
Hamster.prototype.runWheel=function(){console.log("I'm running")};
mini.runWheel();//=I'm running
//And you'll use less resources (CPU and memory) to create instances
//because what's on the prototype is shared
Setting prototype without calling the constructorIn the previous example we see that when RussionMini "inherits" from Hamster by setting RussionMini.prototype to an instance of Hamster actually creates instance variables (this.food) on RussionMini's prototype. The food member is shadowed by invoking Hamster.apply(this,arguments) but it's still undesirable to have an instance variable on the prototype.
And what if we would like to set Hamser as RussionMini's prototype without creating a Hamster instance? Consider the following code:
function Hamster(name){
if(name===undefined){
throw new Error("Name cannot be undefined");
}
this.name=name;
}
function RussionMini(name){
Hamster.apply(this,arguments);
}
RussionMini.prototype=new Hamster(); //Error: Name cannot be undefined
We can pass in a dummy name that will be shadowed with the real name when we create a RussionMini but maybe instead of name we need to pass something that isn't available when we declare the RussionMini constructor (DOM element) and passing a dummy would complicate the code too much. We can add a helper function that will set "inheritance" without calling the Hamster() constructor:// based on goog.inherits in closure library
var inherits = function(childCtor, parentCtor) {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
var Hamster = function(name){
if(name===undefined){
throw new Error("Name cannot be undefined");
}
this.name=name;
}
var RussionMini=function(name){
//we are not creating a Hamster in the following code
//we are invoking the method with the "this" context of
//the to be created RussionMini, it's like copying
//the lines in Hamster and pasting it here only better
//it will initialize RussionMini instances with a member named "name"
//having the value of the argument passed to RussionMini's constructor
Hamster.apply(this,arguments);
}
inherits(RussionMini,Hamster);//no error
var betty=new RussionMini("Betty");
console.log(betty.name);//=Betty
Overriding functionsSometimes
children
need to extend parent
functions. You want the 'child' (=RussionMini) to do something extra. When RussionMini can call the Hamster code to do something and then do something extra you don't need to copy and paste Hamster code to RussionMini.
In the following example we assume that a Hamster can run 3km an hour but a Russion mini can only run half as fast. We can hard code 3/2 in RussionMini but if this value were to change we have multiple places in code where it needs changing. Here is how we use Hamster.prototype to get the parent (Hamster) speed.
// from goog.inherits in closure library
var inherits = function(childCtor, parentCtor) {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
var Hamster = function(name){
if(name===undefined){
throw new Error("Name cannot be undefined");
}
this.name=name;
}
Hamster.prototype.getSpeed=function(){
return 3;
}
Hamster.prototype.run=function(){
//Russionmini does not need to implement this function as
//it will do exactly the same as it does for Hamster
//But Russionmini does need to implement getSpeed as it
//won't return the same as Hamster (see later in the code)
return "I am running at " +
this.getSpeed() + "km an hour.";
}
var RussionMini=function(name){
Hamster.apply(this,arguments);
}
//call this before setting RussionMini prototypes
inherits(RussionMini,Hamster);
RussionMini.prototype.getSpeed=function(){
return Hamster.prototype
.getSpeed.call(this)/2;
}
var betty=new RussionMini("Betty");
console.log(betty.run());//=I am running at 1.5km an hour.
The disadvantage is that you hard code Hamster.prototype ... There may be patterns that will give you the advantage of super
as in Java.this.constructor
The constructor property is included in the prototype by JavaScript, you can change it but it should point to the constructor function. So
Hamster.prototype.constructor
should point to Hamster.Inherits sets the constructor to point to the right constructor (RussionMini). An alternative to the helper function would be
RussionMini.prototype=Object.create(Hamster.prtotype);
but you'll have to repair the RussionMini.prototype.constructor to point to RussionMini. You may not use prototype.constructor but someone building on your code might want to and will be surprised if it points to the wrong constructor:var Hamster = function(){};
var RussionMinni=function(){};
RussionMinni.prototype=Object.create(Hamster.prototype);
console.log(RussionMinni.prototype.constructor===Hamster);//=true
RussionMinni.prototype.haveBaby=function(){
return new this.constructor();
};
var betty=new RussionMinni();
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//false
console.log(littleBetty instanceof Hamster);//true
//fix the constructor
RussionMinni.prototype.constructor=RussionMinni;
//now make a baby again
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//true
console.log(littleBetty instanceof Hamster);//true
Helper function that simulates super.In Java when a Child overrides a Parent function you can call it's parent function with super.someMethod(). When re factoring you may have to re name Parent many times and would like to cut back on the times you hard code the Parent constructor name.
A wrong way would be to set the Parent as a value of the prototype or even worse; as a value of
this
. Here is an example of why://following is needed to break a never ending loop
var i = 0;
function GrandDad(){
console.log("I'm GrandDad");
}
function Dad(){
i++;
if(i>10){
console.log("Breaking never ending loop");
return;
}
this.Parent.call(this);
console.log("I'm Dad");
}
Dad.prototype=Object.create(GrandDad.prototype);
Dad.prototype.constructor=Dad;
//this is dangerous when inheritance is 3 levels deep
Dad.prototype.Parent=GrandDad;
function Child(){
this.Parent.call(this);
console.log("I'm Child");
}
Child.prototype=Object.create(Dad.prototype);
Child.prototype.constructor=Child;
//this is dangerous when inheritance is 3 levels deep
Child.prototype.Parent=Dad;
new Child();
A better way (although you still have to hard code the "own" function name) would be:function GrandDad(){
console.log("I'm GrandDad");
}
function Dad(){
Dad.Parent.call(this);
console.log("I'm Dad");
}
Dad.Parent=GrandDad;
Dad.prototype=Object.create(GrandDad.prototype);
Dad.prototype.constructor=Dad;
function Child(){
Child.Parent.call(this);
console.log("I'm Child");
}
Child.Parent=Dad;
Child.prototype=Object.create(Dad.prototype);
Child.prototype.constructor=Child;
new Child();
Multiple inheritance with mix insSome things are better not to be inherited, if a Hamster can run and can speak than a Hamster should not inherit from run or speak (an object can only "inherit" from one other object). A hamster is not a run or speak but rather a Hamster can run and speak.
For running and speaking we have instance specific members (like
message
and runSpeed
). And we have members that are not instance specific (like the function run() and speak()). Instance specific members will be set by calling mixin_init (added by mix_in helper function) when creating an instance and prototype members will be copied one by one on Hamster.prototype from canSpeak.prototype using the mix_in helper function.// funciton to shallow copy source members to target
var mix_in = function(source,target){
for(thing in source){
if(source.hasOwnProperty(thing)){
target[thing]=source[thing];
}
}
if(!target.mixin_init){
target.mixin_inits=[];
target.mixin_init = function(){
var i,len;
for(i=0,len=this.mixin_inits.length;i<len;i++){
this.mixin_inits[i].apply(this,arguments);
}
return this;
};
}
target.mixin_inits.push(source.constructor);
};
var canTalk = function(){
this.message = this.name;
};
canTalk.prototype.speak = function(){
console.log("Hi, my name is:",this.message);
};
var canRun = function(arg){
this.runSpeed = (arg&&arg.speed)?arg.speed:2;
};
canRun.prototype.run = function(){
//will shadow prototype.speed, speed
//will be instance specific after this
this.speed = this.speed * this.runSpeed;
return this;
};
var Hamster = function(name,mixin_arg){
this.name=name;
if(typeof mixin_arg !==undefined){
console.log("and mixinarg is:",mixin_arg);
this.mixin_init(mixin_arg);
}
};
Hamster.prototype.speed=2;//defaults to 2
mix_in(canTalk.prototype,Hamster.prototype);
mix_in(canRun.prototype,Hamster.prototype);
var betty = new Hamster("Betty").mixin_init({speed:3});
var ben = new Hamster("Ben",{speed:2});
betty.run();
console.log(ben.speed);//=2
console.log(betty.speed);//=6
ben.run().run().run();
console.log(ben.speed);//=16
ben.speak();//=Hi, my name is: Ben
betty.speak();//=Hi, my name is: Betty
The this variableIn all the example code you'll see
this
referring to the current instance.The this variable actually refers to the invoking object, it refers to the object that came before the function.
To clarify see the following code:
theInvokingObject.thefunction();
The instances where this would refer to the wrong object are usually when attaching event listeners, callbacks or timeouts and intervals. In the next 2 lines of code we pass
the function, we don't invoke it. Passing the function is: someObject.aFunction
and invoking it is: someObject.aFunction()
. The this
value does not refer to the object the function was declared on but on the object that invokes
it.setTimeout(someObject.aFuncton,100);//this in aFunction is window
somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton
To make this
in the above cases refer to someObject you can pass a closure instead of the function directly:setTimeout(function(){someObject.aFuncton();},100);
somebutton.onclick = function(){someObject.aFunction();};
I like to define functions that return a function for closures on the prototype to have fine control over the variables that are included in the closure scope.var Hamster = function(name){
var largeVariable = new Array(100000).join("Hello World");
// if I do
// setInterval(function(){this.checkSleep();},100);
// then largeVariable will be in the closure scope as well
this.name=name
setInterval(this.closures.checkSleep(this),1000);
};
Hamster.prototype.closures={
checkSleep:function(hamsterInstance){
return function(){
console.log(typeof largeVariable);//undefined
console.log(hamsterInstance);//instance of Hamster named Betty
hamsterInstance.checkSleep();
};
}
};
Hamster.prototype.checkSleep=function(){
//do stuff assuming this is the Hamster instance
};
var betty = new Hamster("Betty");
Passing (constructor) argumentsWhen Child calls a Parent (
Hamster.apply(this,arguments);
) we assume that Hamster uses the same arguments as RussionMini in the same order. For functions that call other functions I usually use another way to pass arguments.Python has keyword arguments with default values:
def printinfo( name, age = 35 ):...printinfo( age=50, name="miki" );
to achieve the same in JavaScript I usually pass one object to a function and have that function mutate whatever it needs (set defaults), then pass it to another function that will do the same and so on and so on. Here is an example://helper funciton to throw error
function thowError(message){
throw new Error(message)
};
var Hamster = function(args){
//default value for type:
this.type = args.type || "default type";
//name is not optional, very simple truthy check f
this.name = args.name || thowError("Name is not optional");
};
var RussionMini = function(args){
args.type = "Russion Mini";
Hamster.call(this,args);
};
var ben = new RussionMini({name:"Ben"});
console.log(ben);// Object { type="Russion Mini", name="Ben"}
var betty = new RussionMini({});//Error: Name is not optional
This way of passing arguments in a function chain is useful in many cases. When you're working on code that would calculate a total of something and later you'd like to re factor the total of that something to be in a certain currency you may have to change a lot of functions to pass the value for currency. You could up scope a currency value (even to global like window.currency='USD'
) but that's a bad way to solve it.With named parameters you could add currency to
args
whenever it's available in the function chain and mutate/use it whenever you need it without changing the other functions.Private instance specific members
Private members are methods or properties of an object that be accessed by the same object (for example private variable hungry in Person can only be accessed by a Person object). Let's say we have a Hamster and we would like to have a food property but when accessing this property we would like to force people to use the get and set methods. The code could look something like this:
var Hamster = function(){
var food=[];
this.getFood=function(){
return food;
}
this.setFood=function(newFood){
food=newFood;
if(food.length>10){
//I'm full, store the food
}
}
}
Hamster.prototype.addFood=function(foodItem){
// since we're outside the Hamster function body
// we cannot access food so the addFood method
// cannot be declared on the prototype.
}
var lex = new Hamster();
console.log(lex.food);//undefined
In the above code the food property isn't available outside the Hamster function body thus food is private.The disadvantage of using
var
is that the functions declared in the Hamster body with this.myfunction=function...
are initialized for every single Hamster instance instead of all Hamster instances sharing one. So when creating a Hamster instance it'll consume more memory to store it and it'll use more cpu to create it as every this.somefunction=function...
is initialized as a closurewhen a Hamster instance is created instead of only once when defined on the prototype. It still isn't really private because an instance of Hamster cannot access the food property of another Hamster (in hamster1 instance hamster2.food will be undefined)The advantage is that Hamster now has private variables (although many libraries use
_myprivatevar
to indicate that a variable is private). There is a pattern that can be used to reduce a lot of publicly accessible members from showing up by creating 2 closures per instance. It can be found here: http://stackoverflow.com/a/19879651/1641941
Privates through Object.defineProperty
To protect your private variables using Object.create or Object.defineProperty() isn't full proof. When assigning new values you can stimulate use of getters and setters. But the value that the getter and setter function would manipulate would still be publicly accessible (or you have to use closures again). When invoking mutator methods on object properties will change their value.
//only showing that mutator methods can change "private" methods
// for an example of implementing getters and setters see here:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
function Test(){
//trying to create private instance
Object.defineProperties(this,
{
_privateInstanceVal: {value:22},//immutable
_privateInstanceObject: {value:[]}//mutable
}
);
this._privateInstanceVal=99;//<=doesn't do anything
}
var t = new Test();
console.log(t._privateInstanceVal);//22 so we can see it
t._privateInstanceVal=77;//can we write it? (there is no error)
console.log(t._privateInstanceVal);//nope, it's still 22
t._privateInstanceObject.push(88);//this one isn't immutable
console.log(t._privateInstanceObject);//=[88] so we can manipulate it
t._privateInstanceObject=0;//but we can't re assign it
console.log(t._privateInstanceObject);//still [88]
console.log(t.hasOwnProperty("_privateInstanceObject"));//true
for(thing in t){
console.log(thing);//logs nothing
}
Private shared membersFor privates that don't need to be instance specific you can do the following:
var Hamster = function(name){this.name=name};
Hamster.prototype=function(){//<-nameless function returning the prototype
//private shared members
//good for functions and items that
//do not need to be instance specific
var someThingPrivate = function(hamster){
//can access the instance of hamster with the passed
//parameter
console.log(this);//is Window
console.log(hamster.name);
}
//privileged methods (these methods can access private members)
return{
publicMember:function(){
//calling private member:
someThingPrivate(this);
}
}
}();
Hamster.prototype.constructor=Hamster;
var ben = new Hamster("Ben");
ben.publicMember();//=Ben
console.log(ben.someThingPrivate);//undefined
You can't override privileged methods in "subclasses" and expect them to be able to access the private members.We now have functions on the prototype that can only be accessed by other function within the
nameless function
(see code above) body. Remember that functions are good on the prototype because they will do exactly the same thing for every instance so they usually don't need to be instance specific.
0 comments:
Post a Comment