Sunday, February 19, 2012

JavaScript Object-Oriented Programming


It may be shocking news, but JavaScript is a very powerful object-based (or prototype-based, whatever you wish to call it) language. Yes, JavaScript is a powerful language, not just something that's handy for image rollovers and other corny, flashy effects. However, very few people who have used JavaScript realize its capabilities. If you're one of these people, this tutorial is aimed at you. 

First of all, JavaScript is not a full-blown OOP (Object-Oriented Programming) language, such as Java, but it is an object-based language. So, why should you use objects? Not only do they help you better understand how JavaScript works, but in large scripts, you can create self-contained JavaScript objects, rather than the procedural code you may be using now. This also allows you to reuse code more often.
I hope that this article will turn an intermediate JavaScripter who's itching to learn objects, into an expert, keen on the exciting object-oriented JavaScript world!
In this tutorial, you'll learn:
  • JavaScript's primitive data types
  • What an object is in JavaScript
  • How to create custom objects
  • What a constructor is
  • What an object's prototype property is

JavaScript's Primitive Data Types
JavaScript has five primitive data types:
  • Undefined,
  • Null,
  • Boolean,
  • Number, and
  • String. Throughout this tutorial, we'll use the latter three extensively.
  • A Boolean is a logical entity that consists of either a true or a false value. An example of one is:
    var BooleanValue = true;
    A Number is a set of numerical digits that represent a number. Through this tutorial, we'll only use base-10 numbers. An example:
    var NumericalValue = 354;
    A String is a set of zero or more characters. An example:
    var StringValue = "This is a String";


    Typeof
    A less-known operator in JavaScript is the typeof operator. It tells you what type of data you're dealing with. Makes sense, huh? Let's look at some examples:
    var BooleanValue = true;
    var NumericalValue = 354;
    var StringValue = "This is a String";
    alert(typeof BooleanValue) // displays "boolean"
    alert(typeof NumericalValue) // displays "number"
    alert(typeof StringValue) // displays "string"


    An Object
    An object is a collection of properties. These properties can either be primitive data types, other objects, or functions (which in this case are called methods, but more on this later). A constructor function (or simply, constructor) is a function used to create an object - this too we'll discuss in detail later. JavaScript comes with many built-in objects, such as the Array, Image, and Date objects. Many of you are familiar with Image objects from creating those ever-so-cute rollover effects. Well, when you use the code
    var Image1 = new Image();
    Image1.src = "myDog.gif";

    you have in fact created a new Image object, and assigned a property of your new Image object: the src property. Image1 is a new Image object; in other words, it is an instance of the Image object. Using JavaScript's dot-structure ( . ), the code above then accesses and sets the src property of your new Image object. Now, let's learn how to create our own objects.

    function myFunc(){
    }

    var myObject = new myFunc();
    alert(typeof myObject);  // displays "object"

    We've just created our own object. In fact we've created a myFunc object. myFunc() is a constructor function; it lays out the blueprint from which objects that are created from it will follow (although, in this case, it doesn't lay out much of a blueprint). So, how does JavaScript know to create an instance of the myFunc object, rather than to return its results? Let's compare the example above with the following, more conventional use of a function:
    function myFunc(){
     return 5;
    }

    var myObject = myFunc();
    alert(typeof myObject); // displays "number"

    In this case, we've assigned 5 to myObject. So, what's the difference between these two scripts? Answer: the new keyword. It tells JavaScript to create an object following the blueprint set forth in the myFunc() constructor function. In fact, when we create an Image object, we do the same thing, except that instead of using our own constructor function, we use one of JavaScript's built-in constructor functions, the Image() constructor function.
    So far, we've learned how to create a constructor function, and how to create an object from that constructor function. In our example, we've created a myFunc() constructor and created an instance of the myFunc object, which we assigned to the variable myObject.
    This is all fine and dandy, but what's the point? Well, just like our Image object, myObject can be assigned properties:
    function myFunc(){
    }

    var myObject = new myFunc();
    myObject.StringValue = "This is a String";
    alert(myObject.StringValue); // displays "This is a String"

    And voila, we've now created a property for our object. However, if we create another instance of the myFunc object (using the myFunc() constructor function), we also have to assign the StringValue property to this new instance. For example:
    function myFunc(){
    }

    var myObject = new myFunc();
    myObject.StringValue = "This is a String";
    var myObject2 = new myFunc();
    alert(myObject2.StringValue); // displays "undefined"

    So, how can we create properties that exist for all myFunc objects? Within the myFunc() constructor function, we can do just that. The this keyword inside a constructor function refers to the object that's being created. Example:
    function myFunc(){
     this.StringValue = "This is a String";
    }

    var myObject = new myFunc();
    var myObject2 = new myFunc();
    alert(myObject2.StringValue); // displays "This is a String"

    Now, all myFunc objects will have a StringValue property, assigned with the initial value of "This is a String", but every object can have its own distinctive value for StringValue. In other words, we can change the StringValue property for one myFunc object, without affecting the others:
    function myFunc(){
     this.StringValue = "This is a String";
    }

    var myObject = new myFunc();
    myObject.StringValue = "This is myObject's string";
    var myObject2 = new myFunc();
    alert(myObject.StringValue); // displays "This is myObject's string"
    alert(myObject2.StringValue); // displays "This is a String"

    We can also achieve similar results if we pass arguments to our constructor function:
    function myFunc(StringValue){
     this.StringValue = StringValue;
    }

    var myObject = new myFunc("This is myObject's string");
    var myObject2 = new myFunc("This is a String");
    alert(myObject.StringValue); // displays "This is myObject's string"
    alert(myObject2.StringValue); // displays "This is a String"

    In the myFunc() constructor, this.StringValue refers to the property being assigned to the newly created object, while StringValue refers to the function's local variable that was passed as an argument. So, now that we've assigned properties to objects, what about methods?


    Object Methods
    In addition to properties, objects can have methods. An object's method is a function it can perform. Let's take a look at this example. For this one, let's create a Circle object. First, we're going to have to define our functions, and then make them methods of our Circle object. Let's define our Circle() constructor and a Circle object or two:
    function Circle(radius){
     this.radius = radius;
    }

    var bigCircle = new Circle(100);
    var smallCircle = new Circle(2);

    Now, let's define some functions that we might use:
    function getArea(){
     return (this.radius*this.radius*3.14);
    }

    function getCircumference(){
     var diameter = this.radius*2;
     var circumference = diameter*3.14;
     return circumference;
    }

    Note that if you were going for accuracy, you could use Math.PI instead of 3.14, but we'll use this simplified representation of pi to keep the numbers in our examples nice and round.
    These functions are easy, except for one thing: what does this.radius refer to? this always refers to the current object, in this case, the Circle object. So this.radius refers to the radius property of the Circle object. So, how do we attach these functions to our object? It's not as hard as you might think. Let's change our Circle() constructor:
    function Circle(radius){
     this.radius = radius;
     this.getArea = getArea;
     this.getCircumference = getCircumference;
    }

    The above assigns the functions getArea and getCircumference to our Circle object, making them methods—functions belonging to our Circle object. We can use methods just like any normal function, but we must first access the object in which the method is encapsulated:
    alert(bigCircle.getArea()); // displays 31400
    alert(bigCircle.getCircumference()); // displays 618
    alert(smallCircle.getArea()); // displays 12.56
    alert(smallCircle.getCircumference()); // displays 12.56

    Let's say we want to keep all our properties and methods in the same place - in the Circle() constructor function. There are many ways to do this. Let's first examine inner functions. An inner function is a function within a function (say that sentence quickly ten times!). Here's what they let us do:
    function Circle(radius){  
     function getArea(){  
       return (this.radius*this.radius*3.14);  
     }  
     function getCircumference(){  
       var diameter = this.radius*2;  
       var circumference = diameter*3.14;  
       return circumference;  
     }  
    this.radius = radius;  
    this.getArea = getArea;  
    this.getCircumference = getCircumference;  
    }

    It's the same code, except that we've moved the functions. Now, inside our two functions, instead of this.radius, we could use just plain old radius because inner functions can access local variables within outer functions. Thus, it would be able to access the radius local variable passed as an argument to the Circle() constructor. Therefore, we could have just as easily used:
    function Circle(radius){  
     function getArea(){  
       return (radius*radius*3.14);  
     }  
     function getCircumference(){  
       var diameter = radius*2;  
       var circumference = diameter*3.14;  
       return circumference;  
     }  
     this.radius = radius;  
     this.getArea = getArea;  
     this.getCircumference = getCircumference;  
    }

    Ok, now let's change the radius of an object and get its area:
    bigCircle.radius=50;  
    alert(bigCircle.getArea()); // displays 31400

    But wait! It returns 31400, rather than the expected 7850. What's wrong? Well, radius refers to the value we passed to the Circle() constructor function, not the value of the object. So when we change the object's radius, the methods getArea() and geCircumference(), keep on using the old radius. So, we really shouldn't use just plain old radius. Instead, we need to use this.radius, as it refers to the current object's radius, whether this property changes after the object is created or not.

    Ok, so now we've created a self-contained object constructor - the function that defines an object. Let's look at another way we can create functions inside our Circle() constructor:
    function Circle(radius){  
     this.radius = radius;  
     this.getArea = function(){  
       return (this.radius*this.radius*3.14);  
     }  
     this.getCircumference = function(){  
       var diameter = this.radius*2;  
       var circumference = diameter*3.14;  
       return circumference;  
     }  
    }  
     
    var bigCircle = new Circle(100);  
    var smallCircle = new Circle(2);  
     
    alert(bigCircle.getArea()); // displays 31400  
    alert(smallCircle.getCircumference()); // displays 12.56

    Here, we've encountered another way to define a function. We can use:
    functionName = function([parameters]){  
     // function body  
    }

    In this way, we can create parameters:
    functionName = function(parameter1,parameter2,parameter3){  
     //function body  
    }

    While functions aren't created this way very often, when we're creating objects, they can be useful shortcuts. These processes also help avoid conflicts with function names. For instance, another object can have a different function with the same name, for example getArea(), without causing a conflict. This is possible because these functions are encapsulated inside an object constructor.


    Object Categories
    There are three object categories in JavaScript: Native Objects, Host Objects, and User-Defined Objects.

    Native objects are those objects supplied by JavaScript. Examples of these are String, Number, Array, Image, Date, Math, etc.
    Host objects are objects that are supplied to JavaScript by the browser environment. Examples of these are window, document, forms, etc. And, user-defined objects are those that are defined by you, the programmer.
    A fundamental concept in JavaScript is that every element that can hold properties and methods is an object, except for the primitive data types. We can use JavaScript's built-in constructor functions (just like the ones we've created) to create objects:
    var Image1 = new Image(50,100);  
    Image1.src = "myDog.gif";

    Here we've created a new Image object using the native Image() constructor function with the following properties:
  • width = 50
  • height = 100
  • src = "myDog.gif"
JavaScript also includes an Object() constructor function that can be used to define a new Object object:
var myObj = new Object();
To that "base Object", we can add properties/methods. Every object in JavaScript derives from JavaScript's native Object object.
Let's review a String primitive data type:
var myString = "This is my string";  
alert(myString); // displays "This is my string"  
alert(typeof myString); // displays "string"
However, we can even make a String an object, by using its constructor function:
var myString = new String("This is my string");  
alert(myString); // displays "This is my string"  
alert(typeof myString); // displays "object"
Now we've created a String object. We can also do the same with Number and Boolean. But why would we want to? Well, once we've done that, we can add distinctive properties and methods to that object. A primitive data type contains the properties and methods laid out in its object constructor, but it cannot, itself, hold any distinctive properties/methods. For example, a String primitive data type contains the length property as well as the many methods defined in the native String() object constructor, such as substring(). However, a String object contains the properties and methods defined in the String() object constructor as well as any unique values assigned to that particular object. Now, let's create a Number object and add a method to it:
var myNumber = new Number(2);  
myNumber.doubleIt = new Function("return this*2");  
alert(myNumber.doubleIt()); // displays 4  
alert(typeof myNumber); // displays "object"
So, we just created a new Number object, and then we defined a method for it, doubleIt(). Note that typeof myNumber is "object". This is because objects are able to contain unique properties and methods. Primitive data types, such as String, Boolean, Number, Undefined, and Null, cannot, and this is what differentiates the two.
Also, in the example above, we've in fact created another object - a Function object. However, the Function object is different. When we create an object, we first enter the new keyword, then follow it with the object constructor function, and this returns a new instance of that particular object. Then, using the returned value (which we usually assign to a variable), we can add properties and methods to that object. However, because a Function object is also a callable block of code, JavaScript makes the distinction and tells us that it's not only an object (which it is, as we can add properties and methods to it), but is also a callable block of code. So, when we enter:
alert(typeof myNumber.doubleIt) // displays "function"
it displays "function", rather than "object" as you might have expected. The Function() constructor function can take more arguments. The last argument passed to the Function() constructor becomes the body of the function, while the others become parameters:
var myFunc = new Function("parameter1","parameter2",  
 "parameter3"," // function body");
Now we can call that function and specify three arguments:
myFunc("argument1","argument2","argument3");

Function Objects
JavaScript's Function object is unususal for a number of reasons. Firstly, it's a callable block of code. And a function is always an object - it always has the ability to hold unique properties and methods. The creation of a function automatically creates a Function object:
function myFuncObj(){}  
myFuncObj.someVariable = "x";  
alert(myFuncObj.someVariable) // displays "x"  
Even without the new keyword, Function() creates an object, capable of containing properties and methods. Note that the Function() constructor is a special case – all other constructors must be called with the new keyword, or they simply return a value, instead of a new object.
Let's look at a String primitive data type vs. a String object:
var pimitiveString1 = "This is a primitive string";  
var pimitiveString2 = String("This is a primitive string");  
var stringObject = new String("This is a String object");  
 
primitiveString1.prop = "This is a property";  
primitiveString2.prop = "This is a property";  
stringObject.prop = "This is a property";  
 
alert(primitiveString1.prop) // displays "undefined"  
alert(primitiveString2.prop) // displays "undefined"  
alert(stringObject.prop) // displays "This is a property"  
 
alert(typeof primitiveString1); // displays "string"  
alert(typeof primitiveString2); // displays "string"  
alert(typeof stringObject) // displays "object"
Here you can see that, without the new keyword, we don't create and assign an object to a variable, but instead, we assign the returned value (which is a primitive data type, String) to a variable. You can also see that primitiveString1 and primitiveString2 are not objects, as we cannot assign them properties. Note that primitiveString1/primitiveString2 and stringObject return different results when used with the typeof operator. This is even true for Date, Image, Option, and other objects. For example:
var x = Date();  
alert(typeof x); // displays "string"
No matter how you create a function (there are numerous ways), you'll automatically create an object:
var myFuncObj = new Function();  
var myFuncObj = Function();  
var myFuncObj = function(){}  
function myFuncObj(){}
Here, we've examined the different ways to create a Function object that's capable of holding a callable block of code, as well as any disctinct properties or methods.

In Summary
Before we move on, let's review some key points:
  • In JavaScript, there are five primitive data types: Undefined, Null, Boolean, Number, and String.
  • In JavaScript, everything is an object, except for the primitive data types.
  • An object is an unordered collection of properties. Properties may represent primitive data types, objects, or Function objects, in which case they are called "methods".
  • There are three main object categories in JavaScript: native objects, host objects, and user-defined objects.
  • There are many built-in, native objects, such as Object, Image, Date, Option, etc. However, we can also create our own user-defined objects, such as the Circle object.
  • An object constructor/object constructor function is a function that's used to define a new object. In this function, we declare the initial properties/methods of the new object, and usually assign them a pre-defined value.
  • To create an instance of an object, we use the keyword "new", followed by an object constructor. We can either use JavaScript's built-in constructors to create a native object, or we can build our own constructors to create a user-defined object.
  • Every object method has a variable - this - which refers to the current instance of that object from which the method is called. Example:
    • myObj.myFunc()
    • yourObj.myFunc()

    In the first example, in myFunc(), the variable this would refer to myObj. However, in the second example, in myFunc(), the variable this would refer to yourObj.

No comments:

Post a Comment