INTRODUCTION TO
JavaScript Functions |
FUNCTIONS AS A DATA TYPE AND FUNCTION CONSTRUCTOR
|
This page is a continuation of Part 1, available
here.
Let's look at another special feature of a function that makes it unique
among other object types. A function can be used as a blueprint for a data
type. This feature is commonly used in object oriented programming
to simulate user defined data type. Objects created with user
defined data type is usually referred to as user defined objects.
-
By declaring a function, we have also created a new data type. The data
type can be used to create a new object. For example,
below I created a new data type named Ball:
Example DT1
function Ball()
{
}
var ball0=new Ball(); // ball0 now points to a new object
alert(ball0); // prints "Object" because ball0 is now an Object
|
Run example.
So what does "ball0=new Ball()" do? The
"new" keyword creates a new object (named ball0) of type Object.
It then executes: Ball(), passing the reference to ball0
as the calling object. Below, you will see the message: "creating
new Ball" if indeed Ball() is executed.
Example DT2
function Ball(message)
{
alert(message);
}
var ball0=new Ball("creating new Ball"); // creates object &
// prints the message
ball0.name="ball-0"; // ball0 now has a "name" property
alert(ball0.name); // prints "ball-0"
|
Run example.
We may consider the red portion of the above code as a shortcut for
doing the same thing as below:
Example DT2A
function Ball(message)
{
alert(message);
}
var ball0=new Object();
ball0.construct=Ball;
ball0.construct("creating new ball"); // executes ball0.Ball("creating..");
ball0.name="ball-0";
alert(ball0.name);
|
Run example.
The line ball0.construct=Ball has the same syntax as ptr=myFunction
in Example 4.
Review Example 4 also if you're not sure
what the line after that means. Note: You might be thinking
of doing ball0.Ball("...") directly, but that won't
work, because ball0 have no property named Ball("...")
and it won't know what you're trying to do.
- When we create an object using the new keyword like above, a new
Object
is created. We can add properties to the object after the creation
(such as when I added "name" property above. The problem
with that approach is that if we create another instance of the object, we need to add the property again to the new object like below.
Example DT3 (creates 3 ball objects)
function Ball()
{
}
var ball0=new Ball(); // ball0 now points to a new instance of type Ball
ball0.name="ball-0"; // ball0 now has a "name" property
var ball1=new Ball();
ball1.name="ball-1";
var ball2=new Ball();
alert(ball0.name); // prints "ball-0"
alert(ball1.name); // prints "ball-1"
alert(ball2.name); // oops, I forgot to add "name" to ball2!
|
I forgot to add name to ball2 there, which may cause problem on a real
program. Would it be nice if there's a way to add properties automatically? Well
there is: using the "this" keyword. The word "this" has a special meaning within a
function. It refers to the object calling that function. Let's see
another example below, this time, we add the properties on the constructor
function: Example DT4
function Ball(message, specifiedName)
{
alert(message);
this.name=specifiedName;
}
var ball0=new Ball("creating new Ball", "Soccer Ball");
alert(ball0.name); // prints "Soccer Ball"
|
Run example.
Remember that the "new" keyword eventually causes the
constructor
function to be executed. In this case, it will executel Ball("creating new Ball", "Soccer Ball");
and the keyword this will refer
to ball0. Therefore, the line: this.name=specifiedName becomes
ball0.name="Soccer
Ball". It basically said: add "name" property to
ball0,
with the value of "Soccer Ball."
So now, we've just added a name property to ball0, much like what
is being done in previous examples, but in much more elegant and
extensible way. Now, we can have as many balls and as many
properties as we want without having to add them manually. Also,
people wanting to create a Ball object can easily see the constructor
function and easily find out all the properties of a Ball.
Let's add more properties to Ball. Example DT5
function Ball(color, specifiedName, owner, weight)
{
this.name=specifiedName;
this.color=color;
this.owner=owner;
this.weight=weigth;
}
var ball0=new Ball("black/white", "Soccer Ball", "John", 20);
var ball1=new Ball("gray", "Bowling Ball", "John", 30);
var ball2=new Ball("yellow", "Golf Ball", "John", 55);
var balloon=new Ball("red", "Balloon", "Pete", 10);
alert(ball0.name); // prints "Soccer Ball"
alert(balloon.name); // prints "Balloon"
alert(ball2.weight); // prints "55"
|
Run example.
Whew! In object oriented term, you can say that Ball
is an object type which has the following properties: name, color,
owner, weight.
- We are not limited to adding simple data types such as Strings or numbers as
properties. We can also assign objects as properties. Below, supervisor
is a property of Employee. Example DT6
function Employee(name, salary, mySupervisor)
{
this.name=name;
this.salary=salary;
this.supervisor=mySupervisor;
}
var boss=new Employee("John", 200);
var manager=new Employee("Joan", 50, boss);
var teamLeader=new Employee("Rose", 50, boss);
alert(manager.supervisor.name+" is the supervisor of "+manager.name);
alert(manager.name+"\'s supervisor is "+manager.supervisor.name);
|
What will this print?
Run example.
As you see in the above example, manager and teamLeader both has
a property supervisor, which is an object of type Employee.
- Any kind of object can be a property, in fact, recall from Example
4 (not Example DT4) above, that a function is also an
object. So you can make a function a property of an object.
Below, I added 2 functions getSalary and addSalary
Example DT7
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss=new Employee("John", 200000);
boss.addSalary(10000); // boss gets 10K raise
alert(boss.getSalary()); // print 210K |
Run example.
addSalary and getSalary demonstrates several
different ways of assigning functions as properties. If you remember our
discussion at the very beginning; I talked about three different ways
of declaring functions. All of them are applicable here, but the
two ways shown above are the most common.
Let's see what the differences are. Below, take a look at the red
portion of the same example from above. When this
piece of code is executed, getSalary function is declared. As have been
mentioned several times, a function declaration causes an object to be
created. So by the time boss is created (after the green
line below), boss has a property called getSalary
somewhere.
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
var boss3=new Employee("Kim", 200000); |
When you create more instances of the objects
(boss2 and boss3), each of those instances will have a
separate copy of the getSalary code; whereas addSalary
points to only one location (which is the addSalaryFunction).
See
example below to see the effect described above. Example DT8
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss1=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
// add properties to getSalary function object.
boss1.getSalary.owner="boss1";
boss2.getSalary.owner="boss2";
alert(boss1.getSalary.owner); // prints "boss1"
alert(boss2.getSalary.owner); // prints "boss2"
// if both objects are pointing to the same function object, then
// both output above should have printed "boss2".
// add properties to addSalary function object.
boss1.addSalary.owner="boss1";
boss1.addSalary.owner="boss2";
alert(boss1.addSalary.owner); // prints "boss2"
alert(boss2.addSalary.owner); // prints "boss2"
// since both objects are not pointing to the same function,
// then changes in one, affects all instances (so, both prints "boss2").
|
Run example.
This might not seem to be a big deal, but there are several
consequences of doing nested function like getSalary above: 1)
more storage to store the object (because each instance of the object
will have its own copy of the getSalary code; and 2) more time
required for JavaScript to construct the object. Let's
redo the example to make it more efficient. Example DT9
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=getSalaryFunction;
}
function getSalaryFunction()
{
return this.salary;
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
} |
Here, both functions are pointing to one location, which saves space
and construction time (especially if you have a lot of nested
functions in the constructor function). There is another feature of a function that may used to improve this design. It is called "prototype," and we'll talk about it on the next section.
| FUNCTION
PROTOTYPE |
Every constructor function has a property named prototype.
This property is very useful to declare variables or functions that are
common to a particular class.
-
You do not need to explicitly declare a prototype
property, because it exists on every constructor function. You can see
this below:
Example PT1
function Test()
{
}
alert(Test.prototype); // prints "Object"
|
Run example.
- As you've seen above, prototype is an object, therefore, you can add properties to
it. The properties you added to prototype will
become common properties of all objects created using the constructor
function.
For example, below I have a Fish data type. I want
all fishes have these properties: livesIn="water"
and price=20; to accomplish that, I can add those properties to prototype
of the constructor function: Fish. Example PT2
function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20; |
So let's create some fishes:
var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white"); |
And see what the properties of the fishes are:
for (int i=1; i<=3; i++)
{
var fish=eval("fish"+i); // i'm just getting a pointer to the fish
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
} |
Run example.
The output should be: "mackarel, gray, water, 20" "goldfish, orange, water, 20" "salmon, white water, 20"
You see that all fish has the livesIn and price
property, even though they are not specifically declared on each
individual fish. This is because when an object is created,
the constructor function assigns its prototype property to the
internal __proto__ property of the new object. The __proto__
property is used by the object to look for properties.
-
You can use prototype to assign functions that are common on all
objects, too. This has the benefit of not having to create
and initialize the property every time you construct an
object. To illustrate this, let's revisit Example DT9 and
rewrite it using prototype like below: Example PT3
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
}
Employee.prototype.getSalary=function getSalaryFunction()
{
return this.salary;
}
Employee.prototype.addSalary=function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
} |
We can create the objects as usual
var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000); |
And test it:
alert(boss1.getSalary()); // prints 200000
alert(boss2.getSalary()); // prints 100000
alert(boss3.getSalary()); // prints 150000 |
Run example.
Here's an illustration of how prototype works. Each instance
of the object (boss1, boss2, boss3 has an internal
property named __proto__, which points to the prototype
property of its constructor (Employee). When you execute
either getSalary or addSalary, the object finds it on it's
__proto__, and executes the code. Note that here, there's
no code duplication (compare with diagram at Example DT8).
|
(C)
2002 F. Permadi
<<INDEX>>
Terms
of Use
| |