does exactly the same thing as this:
We can also define the lastLoginTime property directly within userObject’s definition like this:
or even like this, using the Function constructor:
This shows that a function is really just an object that supports a function call operation. That last way of defining a function using the Function constructor is not commonly used, but it opens up interesting possibilities because, as you may notice, the body of the function is just a String parameter to the Function constructor. That means you can construct arbitrary functions at run time.
As objects, functions can also be assigned to variables, passed as arguments to other functions, returned as the values of other functions, stored as properties of objects or elements of arrays, and so on. Figure 1 provides an example of this.
With that in mind, adding methods to an object is as easy as choosing a name and assigning a function to that name. So I define three methods in the object by assigning anonymous functions to the respective method names:
The use of the "this" keyword inside the function displayFullName should be familiar to the C++/C# developers among us—it refers to the object through which the method is called ( developers who use Visual Basic should find it familiar, too—it’s called "Me" in Visual Basic). So in the example above, the value of "this" in the displayFullName is the myDog object. The value of "this" is not static, though. Called through a different object, the value of "this" will also change to point to that object as Figure 2demonstrates.
Figure 2 “this” Changes as the Object Changes
So we’ve seen ways to create an object, complete with its properties and methods. But if you notice all the snippets above, the properties and methods are hardcoded within the object definition itself. What if you need more control over the object creation? For example, you may need to calculate the values of the object’s properties based on some parameters. Or you may need to initialize the object’s properties to the values that you’ll only have at run time. Or you may need to create more than one instance of the object, which is a very common requirement.
Constructor Functions but No Classes
OK, so what’s happening here? Ignore the DogConstructor function definition for a moment and examine this line:
What the "new" operator does is simple. First, it creates a new empty object. Then, the function call that immediately follows is executed, with the new empty object set as the value of "this" within that function. In other words, the line above with the "new" operator can be thought of as similar to the two lines below:
In the Dog definition above, I defined an instance variable called name. Every object that is created using Dog as its constructor function will have its own copy of the instance variable name (which, as noted earlier, is just an entry into the object’s dictionary). This is expected; after all, each object does need its own copies of instance variables to carry its state. But if you look at the next line, every instance of Dog also has its own copy of the respondTo method, which is a waste; you only need one instance of respondTo to be shared among Dog instances! You can work around the problem by taking the definition of respondTo outside Dog, like this:
This way, all instances of Dog (that is, all instances created with the constructor function Dog) can share just one instance of the method respondTo. But as the number of methods grow, this becomes harder and harder to maintain. You end up with a lot of global functions in your codebase, and things only get worse as you have more and more "classes," especially if their methods have similar names. There’s a better way to achieve this using the prototype objects, which are the topic of the next section.
the object referenced by buddy inherits properties and methods from its prototype, although it’s probably not obvious from just that one line where the prototype comes from. The prototype of the object buddy comes from a property of the constructor function (which, in this case, is the function Dog).
Figure 3 Every Function’s Prototype Has a Constructor Property
Now, when a function (in the example above, Dog) is used to create an object with the "new" operator, the resulting object will inherit the properties of Dog.prototype. In Figure 3, you can see that the Dog.prototype object has a constructor property that points back to the Dog function. Consequently, every Dog object (that inherits from Dog.prototype) will also appear to have a constructor property that points back to the Dog function. The code in Figure 4 confirms this. This relationship between constructor function, prototype object, and the object created with them is depicted in Figure 5.
Figure 4 Objects Appear to Have Their Prototype’s Properties
Figure 5 Instances Inherit from Their Prototype
In this example, remember that Dog.prototype is an object. It is created with a call to the Object constructor function, although it is not visible:
So just like instances of Dog inherit from Dog.prototype, Dog.prototype inherits from Object.prototype. This makes all instances of Dog inherit Object.prototype’s methods and properties as well.
Figure 6 Resolving toString() Method in the Prototype Chain (Click the image for a larger view)
Figure 7 illustrates these consequences. Figure 7 also shows how to solve the problem of unnecessary method instances as encountered earlier. Instead of having a separate instance of a function object for every object, you can make the objects share the method by putting it inside the prototype. In this example, the getBreed method is shared by rover and spot—until you override the toString method in spot, anyway. After that, spot has its own version of the getBreed method, but the rover object and subsequent objects created with new GreatDane will still share that one instance of the getBreed method defined in the GreatDane.prototype object.
Figure 7 Inheriting from a Prototype
Static Properties and Methods
Let’s say you need to filter a sequence of numbers based on a simple criterion that only numbers bigger than 100 can pass, while the rest are filtered out. You can write a function like the one in Figure 8.
Figure 8 Filtering Elements Based on a Predicate
But now you want to create a different filtering criterion, let’s say this time only numbers bigger than 300. You can do something like this:
And then maybe you need to filter numbers that are bigger than 50, 25, 10, 600, and so on, but then, being the smart person you are, you realize that they’re all the same predicate, "greater than." Only the number is different. So you can factor the number out with a function like this
which lets you do something like this:
Watch the inner anonymous function returned by the function makeGreaterThanPredicate. That anonymous inner function uses lowerBound, which is an argument passed to makeGreaterThanPredicate. By the usual rules of scoping, lowerBound goes out of scope when makeGreaterThanPredicate exits! But in this case, that inner anonymous function still carries lowerBound with it, even long after makeGreaterThanPredicate exits. This is what we call closure—because the inner function closes over the environment (that is, the arguments and local variables of the outer function) in which it is defined.
Simulating Private Properties
The arguments name and age are local to the constructor function Person. The moment Person returns, name and age are supposed to be gone forever. However, they are captured by the four inner functions that are assigned as methods of a Person instance, in effect making name and age live on, but only accessible strictly through these four methods. So you can do this:
Private members that don’t get initialized in the constructor can be local variables of the constructor function, like this:
Inheriting from Classes
Figure 9 Classes
Figure 10 Deriving from the Pet Class
The prototype-replacement trick used sets the prototype chain properly, so instanceof tests work as expected if you were using C#. Also, the privileged methods still work as expected.
One level of namespace may not be unique, so you can create nested namespaces:
As you can imagine, typing those long nested namespaces can get tiresome pretty fast. Fortunately, it’s easy for the users of your library to alias your namespace into something shorter:
If you take a look at the source code of the Microsoft AJAX Library, you’ll see that the library’s authors use a similar technique to implement namespaces (take a look at the implementation of the static method Type.registerNamespace). See the sidebar "OOP and ASP.NET AJAX" for more information.
Putting It into Perspective