Part - 1 | Part - 2 | Part - 3 |
Where Are We With Object Oriented JavaScript?
With our brief diversion into JSON complete, let's return to the topic of object oriented JavaScript. So far we've learned the following:
- Every JavaScript object is a dictionary.
- Every JavaScript function is an object.
JavaScript Functions
A JavaScript function is a chunk of executable code, but it's also a first class object. This is fundamentally different from methods in C# and Visual Basic. We can invoke methods in C# and VB, but we can't treat those methods as datatypes (although delegates and lamda expressions in C# make this area a little bit fuzzy). In JavaScript, we can manipulate functions using other JavaScript code, assign functions to variables, store functions inside arrays, nest functions inside other functions, and pass functions as a parameter to other functions. This might sound strange, so let's walk through a simple example.
function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}
The above code defines a function named "add". The function expects two parameters, and expects that these two parameters will both have x and y properties that it can add together. It returns the result as a new object (created in object notation) with x and y properties. We could use invoke this function as in the following sample:var p1 = { x: 1, y: 1 };
var p2 = { x: 1, y: 1 };
// use our add function
var p3 = add(p1, p2);
alert(p3.x + "," + p3.y);
The resulting dialog box will display "2,2".
Technically, what we've done with the add function is create a new function object, and assigned the function object to a variable named add. We could take the same function object and assign it to different variables and invoke the function through those variables.
function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}
var foo = add
var bar = add
var p1 = { x: 1, y:1 };
var p2 = { x: 1, y:1 };
// invoke add through foo variable
// p3 should be 2,2
var p3 = foo(p1, p2);
// invoke add again through bar variable
// 2,2 + 1,1 = 3,3
p3 = foo(p3, p1);
alert(p3.x + "," + p3.y);
The resulting dialog box should now display "3,3".
Functions as Methods
We can also assign a function object to an object property. As we noted before, this promotes the function to the status of "method".
var point1 =
{
x: 3,
y: 5,
add: function(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
};
var point2 =
{
x: 1,
y: 1
};
// add 3,5 to 1,1
point1.add(point2);
// shows 4,6
alert(point1.x + "," + point1.y);
The first part of the code uses object notation to create an object with x, y, and add properties. The add property is a function object, and inside we've introduced the "this" keyword. Just as every instance method in C# has an implicit "this" parameter (and every instance method in VB has "Me" parameter), every JavaScript method has an implicit "this" parameter that represents the object through which the method was invoked. "this.x" will reference the x property of point1, because we invoke the add method using point1.
What is a problem is that we have two "point" objects, but one has an add method and one does not. Remember, we are not defining classes like we would in C# or VB, we are simply creating objects and adding properties and methods on the fly. If we wanted to same add method in both point1 and point2, we could write the following code.
function addPoints(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
var point1 =
{
x: 3,
y: 5,
add: addPoints
};
var point2 =
{
x: 1,
y: 1,
add: addPoints
};
// add 3,5 to 1,1
point1.add(point2);
// shows 4,6
alert(point1.x + "," + point1.y);
Now we've defined a function object and assigned the object to a variable named addPoints. We use addPoints to create new add methods in both the point1 and point2 objects. Does the "this" reference still work in addPoints? Yes it does, because "this" will still reference the object through which the method was invoked. "this" is a bit ephemeral in JavaScript, as we see later on, but we can can now invoke the add method on either the point1 or point2 object.
This syntax is feeling uncomfortable, however. It looks as if we are trying to create a Point class that will define the properties and methods for all Point objects. But JavaScript doesn't have classes, so that would be a dream, right? We'll forever need to include all this object literal code every time we need a point object, right? Let's hope we never move into 3 dimensions where the definition of our point objects will change.
Fortunately, there is a better solution.
Constructor Functions
In JavaScript, a constructor function works in conjunction with the new operator to initialize objects. A constructor function can improve our previous code, because we can use the function to initialize every object we want to use as a Point.
function Point(x,y)
{
this.x = x;
this.y = y;
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
// shows 3,5
alert(p1.x, p1.y);
Constructor functions are just regular functions. It's just we've designed the function to be used with the new operator. By convention, we generally capitalize constructor functions to make other programmers aware of their significance.
When we use the new operator with the Point function, the new operator will first create a new object. The new operator then invokes the Point function and passes a reference to the newly created object in the implicit "this" parameter. Inside the Point function we are creating new name/value pairs using the parameter values passed to the function.
Constructor Functions and Object Methods
We can also create methods on an object inside a constructor function.
function Point(x,y)
{
this.x = x;
this.y = y;
this.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
p1.add(p2);
// shows 7,11
alert(p1.x + ',' + p1.y);
This approach works well, but there is an alternate approach we can use which is more in favor today. To understand this approach, we'll need to introduce a new piece of knowledge. Let's review the first two:
- Every JavaScript object is a dictionary.
- Every JavaScript function is an object.
- Every JavaScript object references a prototype object.
Object Prototypes
Prototypes are a distinguishing feature of the JavaScript language. C#, Visual Basic, C++, and Java are all examples of class-based programming languages. To create objects, we must first write a class that defines fields, properties, methods, and events. When we create a new object, we are creating an instance of that class.
In JavaScript, there are no classes. JavaScript is a prototype-based programming language. Every object has a prototype property that references its prototype object. Any properties and methods that are a part of an object's prototype will appear as properties and methods of the object itself.
Remember that every function is an object, and every object references a prototype object. That means every constructor function references a prototype object. This is extremely useful when used in conjunction with the "new" operator, because of steps taken by the new operator:
- Create an empty object.
- Assign the value of the constructor function's prototype property to the new object's prototype property.
- Invoke the constructor function, passing the new object as the "this" reference.
Fortunately, there is an easy syntax we can use to add new properties and methods into a prototype object.
Prototype Programming
Remember, all objects in JavaScript are dictionaries, and a prototype object is no exception. We can modify an object' s prototype simply by referencing it's prototype property, and we can add properties and methods to that prototype object. Let's rewrite our Point "class" one more time.
function Point(x,y)
{
this.x = x;
this.y = y;
}
Point.prototype.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
p1.add(p2);
// shows 7,11
alert(p1.x + ',' + p1.y);
The methods and properties we add to Point.prototype will be shared by all objects that are constructed from the Point constructor function. When we add methods to an object using the constructor function – each object gets a new property referencing a function object, so the prototype approach is more efficient (shared function objects) as well as being a little easier to read. This prototype approach is used by many of today's JavaScript frameworks.
Putting It All Together
The topics we've discussed so far put us close to "simulating" classes in JavaScript. There are just a couple more topics we need to introduce before wrapping up.
One topic is encapsulation. In JavaScript, every name/value pair we add to an object becomes a public property. There are no keywords in JavaScript to restrict accessibility (like the internal, protected, and private keywords in C#). Nevertheless, we can simulate private members.
Private Members
Douglas Crockford published an article "Private Members In JavaScript" that demonstrates how to add private members to a JavaScript object. Information hiding is an important technique in object oriented programming, and many JavaScript toolkits use Crockford's approach to private members.
Private members have to be made in an object's constructor function. Both local vars and parameters are eligible to become private members using a closure. A closure in JavaScript is an inner function that references a local var or parameter in its outer function. Those local variables and parameters, which typically go out of scope when the outer function finishes execution are now "enclosed" by the inner function, which can continue to reference and use those variables.
Let's re-write our sample once more, this time providing public "get" and "set" accesors for our points.
function Point(x, y)
{
this.get_x = function() { return x; }
this.set_x = function(value) { x = value; }
this.get_y = function() { return y; }
this.set_y = function(value) { y = value; }
}
Point.prototype.print = function()
{
return this.get_x() + ',' + this.get_y();
}
var p = new Point(2,2);
p.set_x(4);
alert(p.print());
Client code can no longer access the x and y values of a point object directly. Instead, the code has to go through the set_ and get_ methods.
Namespaces
Namespaces are crucial for avoiding type name collisions, which can be a bad thing in JavaScript. Unlike a compiled language like C# or VB, where a type name collision will result in a compiler error and an un-shippable product, in JavaScript you can still ship the code and might not find out about the collision until it's too late. JavaScript will happily overwrite one value with another. Since we are now including JavaScript code from all over the place, the practice of using namespace is important.
There is just one problem.
JavaScript doesn't support namespaces.
This is ok, because we can "simulate" namespace using objects. Let's put our "Point class" into a Geometry namespace.
var Geometry = {}
Geometry.Point = function(x,y)
{
this.x = x;
this.y = y;
}
Geometry.Point.prototype =
{
print: function()
{
return this.x + ',' + this.y;
}
}
var p1 = new Geometry.Point(5,2);
alert(p1.print());
Essentially, we are adding our constructor function to the Geometry object. By adding other constructor functions (Rectangle, Square, etc), we could keep all of our types inside Geometry and not pollute the global namespace. Most JavaScript frameworks use a similar technique.
In Conclusion
This article presented three key pieces of knowledge:
- Every JavaScript object is a dictionary.
- Every JavaScript function is an object.
- Every JavaScript object references a prototype object.
This article is by K. Scott Allen. Questions? Comments? Bring them to my blog.
Part - 1 | Part - 2 | Part - 3 |
1 comments:
Hello. This post is likeable, and your blog is very interesting, congratulations :-). I will add in my blogroll =). If possible gives a last there on my blog, it is about the Wireless, I hope you enjoy. The address is http://wireless-brasil.blogspot.com. A hug.
Post a Comment