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 out 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?
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.
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:
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");
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.
Before we move on, let's review some key points:
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.
In every function, a private variable – argument – is automatically created, holding an array of the arguments passed to the function. For example:
function testArg(){ for(i=0;i<arguments.length;i++){ alert("Argument "+i+" is "+arguments[i]); } }
As demonstrated in the example above, we can access the set of arguments passed when calling a function with the arguments variable that exists in the function's scope. This example shows that we can access all of the arguments in a function without specifying them as parameters when we define the function. This can be particularly useful when we don't know exactly how many arguments we're going to pass.
Therefore, we can use:
testArg("PageResource","SitePoint","JavaScriptCity", "WebSite Abstraction");
…to get an alert of some of my favorite Web development sites.
Now that we have a foundation in object-based programming in JavaScript, let's build an intricate object-based example, a library. We’ll just keep track of some basic information, such as the book titles, authors, pages, and price. To accomplish this, we're going to have a Person object (that represents each author), a Book object, and a Library object. First, let's create the Person() object constructor:
function Person(lastName, firstName){ this.lastName = lastName; this.firstName = firstName; }
And now, let's create some instances of our Person object:
var DnnyGdmn = new Person("Goodman","Danny"); var DvdFlngn = new Person("Flanagan","David"); var TmMyrs = new Person("Myers","Tom"); var AlxNkmvsky = new Person("Nakhimovsky","Alexander");
Next, let's create our Book object. Its properties will be:
Lastly, we can have multiple authors for the same book, so we need to be able to accept more than one Person object as an author. To do this, we'll create an array to hold each person that wrote the book.
function Book(title, pages, price){ this.title = title; this.pages = pages; this.price = price; this.authors = new Array(arguments.length-3); for(i=0;i<arguments.length-3;i++){ this.authors[i] = arguments[i+3]; } }
The first part of that code should seem straightforward; however, the last part may not. So, let's examine it more closely:
this.authors = new Array(arguments.length-3);
This creates an author property for our Book object. The author property is itself an Array object. When we call our Book() constructor, the first three arguments are title, pages, and price, respectively, so those arguments that are specified after these are our authors. Therefore, if we pass five arguments, we know that two of those must be authors. So we can create an Array object with a length of arguments.length-3.
for(i=0;i<arguments.length-3;i++){ this.authors[i] = arguments[i+3]; }
This code loops through the arguments and assigns them to the Array object. Now, let's see how we can create instances of this Book object:
var JavaNut = new Book("Java Foundation Classes in a Nutshell", 731, 29.95, DvdFlngn); var JSTDR = new Book("Javascript: The Definitive Guide (3rd Edition)", 776, 39.95, DvdFlngn); var JSBible = new Book("Javascript Bible, 4th Edition", 1200, 49.99, DnnyGdmn); var DHTMLTDR = new Book("Dynamic Html: The Definitive Reference", 1073, 44.95, DnnyGdmn); var JSObj = new Book("JavaScript Objects", 450, 39.99, TmMyrs, AlxNkmvsky);
Note that we're passing instances of the Person object as the last arguments to create the authors property of the Book object. A key concept in OOP design (as in Relational Database design) is to avoid repetition within data. Therefore, we create one Person object for each distinct author. So, even though David Flanagan may write more than one book, we always refer to the same Person object. Also, if David ever decides to change his first name to “Bebop”, we can easily change it for all records, by simply altering the one Person object that holds this information. In addition, note that instead of passing primitive data types, we could have passed objects. For instance, for the title, we could have passed a String object, and for the page number, we could have passed a Number object. However, here, they wouldn't serve much use, so we used a primitive data type - this fits our needs just perfectly.
Now, let's move on to perhaps the most difficult object constructor, the Library() constructor. I'm going to break this one up in to parts:
function Library(){ this.books = new Array(arguments.length); for(i=0;i<arguments.length;i++){ this.books[i] = arguments[i]; }
The first thing you may notice about this function is that it has no parameters. This is because it only accepts Book objects, though we have no idea how many. It creates a property of the Library object, books, which stores an Array of Book objects. Let's say we wanted to access a book's first-listed author. We could use:
this.books[bookIndex].authors[0]
We first access the Library’s book property, which is an Array object. Then, we access a specific Book object. From that Book object, we access its authors property, which is an array. Lastly, we access a specific Person object. And from there, we could go on to access that Person object’s firstName or lastName property. Note that bookIndex is the index of the book we want to access.
Now, that's the only property that our Library object will contain. The rest will be methods:
this.totalPrice = function(){ var totalCost = 0; for(i=0;i<this.books.length;i++){ totalCost += this.books[i].price; } return totalCost; }
This method loops through our books property, which is an Array object, takes the price of each Book object and adds it up, and finally returns the value.
this.averagePrice = new Function("return this.totalPrice ()/this.books.length");
This method takes the total price of all of our books and divides it by the number of books we have, to find out the average price of our books. So, once we create a Library object, how do we add more books to it? We're going to have to create another function:
this.addBook = new Function("book", "this.books.push(book)");
This uses the Array's built-in method, push(). The push() method adds the value or object passed as an argument to its Array object, while making sure to change the Array's length property. Lastly, we'll create a method to display the names of the authors in our library. This method is rather long, so I'll split it up:
this.getAuthors = function(){ var toSay = "Your favorite authors are:\n";
This creates a method we'll use to retrieve the list of authors. The toSay variable will hold the string of what this method returns.
for(i=0;i<this.books.length;i++){ for(j=0; j<this.books[i].authors.length; j++){ var authName = this.books[i].authors[j].firstName + " " + this.books[i].authors[j].lastName;
This portion of the code loops through all the books, and then loops through all the authors of that book, placing their names in the authName variable.
if(toSay.indexOf(authName)!=-1) continue; toSay+="\n\t"+authName;
If this author is already in the toSay variable, we don't want to add him again, so we continue to loop through the authors of this book. However, if the author is not yet listed, we can go ahead and add him or her to the toSay variable.
} } return toSay; } }
That closes the two for loops we had open, and returns the toSay variable. It also closes out the method we've been defining, getAuthors(), as well as closing out the Library() constructor. Now, let's put all the code together, and create a new Library object:
// define our Person() constructor function Person(lastName, firstName){ this.lastName = lastName; this.firstName = firstName; } // define our Book() constructor function Book(title, pages, price){ this.title = title; this.pages = pages; this.price = price; this.authors = new Array(arguments.length-3); for(i=0;i<arguments.length-3;i++){ this.authors[i] = arguments[i+3]; } } //define our Library() constructor function Library(){ this.books = new Array(arguments.length); for(i=0;i<arguments.length;i++){ this.books[i] = arguments[i]; } this.totalPrice = function(){ var totalCost = new Number(0); for(i=0;i<this.books.length;i++){ totalCost += this.books[i].price; } return totalCost; } this.averagePrice = new Function("return this.totalPrice()/this.books.length"); this.addBook = new Function("book","this.books.push(book)"); this.getAuthors = function(){ var toSay = "Your favorite authors are:\n"; for i=0;i<this.books.length;i++){ for(j=0;j<this.books[i].authors.length;j++){ var authName = this.books[i].authors[j].firstName + " " + this.books[i].authors[j].lastName; if(toSay.indexOf(authName)!=- 1)continue; toSay+="\n\t"+authName; } } return toSay; } } // create some Person objects DnnyGdmn = new Person("Goodman","Danny"); DvdFlngn = new Person("Flanagan","David"); TmMyrs = new Person("Myers","Tom"); AlxNkmvsky = new Person("Nakhimovsky","Alexander"); // create some Book objects JavaNut = new Book("Java Foundation Classes in a Nutshell",731,29.95,DvdFlngn); JSTDR = new Book("Javascript: The Definitive Guide (3rd Edition)",776,39.95,DvdFlngn); JSBible = new Book("Javascript Bible, 4th Edition",1200,49.99,DnnyGdmn); DHTMLTDR = new Book("Dynamic Html: The Definitive Reference",1073,44.95,DnnyGdmn); JSObj = new Book("JavaScript Objects",450,39.99,TmMyrs,AlxNkmvsky); // create a Library object myLib = new Library(JavaNut,JSTDR,JSBible,DHTMLTDR);
Oops, we left out the JavaScript Objects book. We'd better add it:
myLib.addBook(JSObj);
Now, we can get the information, such as how much our library of books cost, the average price of a book, and the names of the authors that have written the various books we own. And that's it! We've completed a complicated OOP example with JavaScript. You might want to go back over any code that you don't understand, or feel free to post a question in the Client Side Scripting forum at SitePointForums.com.
Every object constructor has a special property, prototype. This property allows you to add properties/methods to all objects created from that object constructor. Sound confusing? It's not. Let's look at some examples:
function Square(){ } var squareObj = new Square(); Square.prototype.side = 5; var squareObj2 = new Square(); alert(squareObj.side); // displays 5 alert(squareObj2.side); // displays 5
What this does is add a side property, with an initial value of 5, to all Square objects, whether they've been created, or have yet to be created. The prototype object (it is, in fact, an object) loads before the object constructor does anything. So, this code:
function Square(){ this.side=5; } var squareObj = new Square(); Square.prototype.side = 4; var squareObj2 = new Square(); alert(squareObj.side); // displays 5 alert(squareObj2.side); // displays 5
returns 5, because everything in the prototype object loads first (before the Square() object constructor even runs), so the properties and methods defined in the constructor will override it. So, with the prototype property, you can't override any properties or methods defined in an object's constructor (the function that creates the object). Using the String's prototype property, we can add new methods to String objects. Consider this example:
function consonantize(){ var consonants =""; for(i=0;i<this.length;i++){ var l = this.charAt(i); if(l!="a" && l!="A" && l!="e" && l!="E" && l!="i" && l!="I" && l!="o" && l!="O" && l!="u" && l!="U" && l!=" "){ consonants+=l; } } return consonants; }
The above function goes through a string and removes all vowels and spaces, returning only consonants. Now, we can use it on any String object, or any String primitive datum:
String.prototype.consonantize = consonantize; var dg = "Danny Goodman"; var df = new String("David Flanagan"); alert(dg.consonantize()); alert(df.consonantize());
Neat, huh? Note how the new method, just like other String methods, can be used by a String object or by a String primitive data type. Therefore, by using an object constructor's prototype method, we can add properties and methods to both native objects and user-defined objects.
Every instance of an object has a constructor property. It returns the Function object that created that instance of the object. For example:
function myConstructor(){ } var str = new String("Some String"); var obj = new Object(); var myObj = new myConstructor(); alert(str.constructor); // the native String() constructor alert(String) // the native String() constructor alert(obj.constructor); // the native Object() constructor alert(Object) // the native Object() constructor alert(myObj.constructor); // the user-defined myConstructor() constructor alert(myConstructor); // the user-defined myConstructor() constructor
I recommend that you run this example to see what it returns. Notice how each alert returns the Function object that created that instance of the object. Also, notice that JavaScript's native objects return “[native code]”. When you retrieve the typeof for a constructor property, you'll find that it's the same as the Function object that created it, “function”:
alert(typeof str.constructor); // "function" alert(typeof String) // "function" alert(typeof obj.constructor); // "function" alert(typeof Object) // "function" alert(typeof myObj.constructor); // "function" alert(typeof myConstructor); // "function"
All of the above return “function”. Because a constructor property returns a reference to the Function object that created it, the constructor is in fact a constructor method:
function myConstructor(){ var x = "y"; this.x = "x"; return x; } var myObj = new myConstructor(); alert(myObj.constructor); // the myConstructor() function object alert(myObj.constructor()); // "y"
Note that in this example, we return the local variable, x, rather than the object's property, this.x. So, if every object has a constructor method, and every method is really a Function object, what's a Function object's constructor?
alert(myConstructor.constructor); alert(myObj.constructor.constructor); alert(myConstructor.constructor.constructor); alert(myObj.constructor.constructor.constructor);
All of those return the native Function() object constructor. Although that's trivial, I personally thought it rather interesting- and thought you might too, and it brings me to another point. Constructors are both “types of objects” as well as objects themselves (more specifically, Function objects). Thus, Date is both an object (a Function object) and a “type of object”, from which you can create Date objects, or instances of the Date object. This is true for all native objects and user-defined objects.
The practical value of all this is that, via an object's constructor method, we can figure out what type of object it is. We can see whether it's a String object, created from the native String constructor function; whether it's an Object object, created from the native Object constructor function; or whether it's one of our user-defined objects, created from a user-defined constructor function.
Besides being a method of an object, constructor() is also a method of a primitive data type. So what does it return? After all, no real constructor function was run to create primitive data types:
var primitiveString1 = "This is a primitive string"; var primitiveString2 = 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) // "undefined" alert(primitiveString2.prop) // "undefined" alert(stringObject.prop) // "This is a property" alert(typeof primitiveString1); // "string" alert(typeof primitiveString2); // "string" alert(typeof stringObject) // "object" alert(primitiveString1.constructor); // "function String(){ [native code] }" alert(primitiveString2.constructor); // "function String(){ [native code] }" alert(stringObject.constructor); // "function String(){ [native code] }"
As we can see, both a String primitive data type and a String object have the same constructor(), the native String() constructor. Note that constructor() is the only property/method that a primitive data type holds, so these data types have access to the properties/methods defined in the native object constructor function. For example, a primitive String data type (as well as a String object) has access to the many properties/methods defined in the native String() constructor, including:
However, a String object may also contain properties/methods that are particular to that object. For example:
var myStringObj = new String("This is a String object"); myStringObj.prop = "This is a property of the object I created"; alert(myStringObj.prop) // "This is a property of the object I created"
As Alex Vincent notes, sometimes you'll want to turn a primitive data type into an object. For example, let's say we have a function like this:
function myFunc(param){ param.property = "I want to add this property"; alert(param.property); // "undefined" }
If we decide to use this function and pass it a primitive data type, we can't also add properties to it, because it's not an object. And anyway, passing an object is rather cumbersome:
myFunc(new String("This is a String object")); myFunc(new Number(5));
One way to overcome this, as Alex points out, is as follows:
function myFunc(param){ param = new param.constructor(param); param.property = "I want to add this property"; alert(param.property); // returns "I want to add this property" }
That new line looks confusing, but let's take a step back. Imagine that we wanted to change a primitive Number into a new Number object. We could use:
var myNum = 5; myNum = new Number(5);
Now let's take that a step further:
var myNum = 5; myNum = new myNum.constructor(5);
You must remember that myNum.constructor() is the same as Number(). Then, instead of using 5, we can use myNum, as that, too, is 5:
var myNum = 5; myNum = new myNum.constructor(myNum);
And the same works for a String primitive data type - as it does for all primitive data types. Therefore, when we pass any primitive data type as an argument to our function, we automatically convert it to an object so that we can add properties/methods to it.
Let's go back and revisit the Function object's prototype property. In Java, a popular, well-known feature is to extend a class; however, in JavaScript, most people are unaware that you can do this - but you can! For instance, let's say we have a Car object. A Corvette and an Ares are two different types of cars, but they are both still cars. In this way, they have similar properties/methods and extend upon the Car object.
Let's create the three objects we're going to use - Car, Corvette, and Ares. Then, we'll discuss the ways for the latter two to inherit the properties/methods of the Car object.
function Car(color){ this.wheels = 4; this.doors = 4; this.color = color; this.speed = 0; this.accelerate = function(){ this.speed+=20; } this.brake = function(){ this.speed-=20; } } function Corvette(color){ // all of Car properties/methods this.doors = 2; this.color = color; this.accelerate = function(){ this.speed+=40; } } function Ares(color){ // all of Car properties/methods this.doors = 2; this.color = color; this.accelerate = function(){ this.speed+=10; } this.brake = function(){ this.speed-=10; } } var myCar = new Car("white"); var myCorvette = new Corvette("black"); var myAres = new Ares("red");
Because a Corvette is an especially fast car, we've upped its acceleration speed from a normal car, and because a Dodge Ares is a rickety, old car, we've made it so the brakes don't work as well, and it doesn't accelerate as fast (no offense to Dodge Ares owners). Now, we could use the Corvette() and Ares() prototype property and add to each the properties/methods from the Car object that we want them to inherit. However, this could be a confusing and tedious task, especially if there are many properties/methods. To overcome this, we need to examine the prototype property again.
The prototype property is an object with no initial properties/methods. When we add properties/methods to this object, we automatically add them to all instances of the object. However, instead of adding properties/methods to the prototype object, we could replace the prototype object with an object that already has the properties/methods we want. For example, instead of using:
Corvette.prototype.wheels = 4; Corvette.prototype.speed = 0; Corvette.prototype.brake = function(){ this.speed-=20; }
we can more easily use:
Corvette.prototype = new Car();
We can do the same for the Ares object:
Ares.prototype = new Car();
Both the Corvette and Ares objects now have all the Car's properties/methods, which can then be overridden by the properties/methods defined in each object constructor. For example, in both the Corvette and Ares objects, the door property is overridden to 2. Altogether, our now code looks like:
function Car(color){ this.wheels = 4; this.doors = 4; this.color = color; this.speed = 0; this.accelerate = function(){ this.speed+=20; } this.brake = function(){ this.speed-=20; } } function Corvette(color){ this.doors = 2; this.color = color; this.accelerate = function(){ this.speed+=40; } } Corvette.prototype = new Car(); function Ares(color){ this.doors = 2; this.color = color; this.accelerate = function(){ this.speed+=10; } this.brake = function(){ this.speed-=10; } } Ares.prototype = new Car(); var myCar = new Car("white"); var myCorvette = new Corvette("black"); var myAres = new Ares("red");
Now, from the Corvette and Ares objects, we can retrieve the appropriate properties and run the accelerate() and brake() methods that correspond to those objects. In this way, in JavaScript, object inheritance is not hard to accomplish. Wrap-up
Through this tutorial, I hope you've learned a general understanding of how JavaScript operates. In addition, I hope you've gained a basic knowledge of OOP and an understanding of the power of JavaScript as an object-based language. I suggest that you post any questions you might have in the SitePoint Forums; however, if you can't seem to find an answer to your JavaScript object question, I'd be more than happy to give it a shot if you email me at arielladog@yahoo.com
There have been many people who have helped me write this tutorial. In particular, though, I'd like to thank Alex Vincent, Jason Davis, and Jared for helping me to understand the finer points of JavaScript's object abilities.