OO Javascript: Creating Objects
For a few posts we’ll talk a bit about programming javascript in such a way as to take advantage of OO principles of reuse, including encapsulation, inheritance, and polymorphism. Javascript of course has no concept of such things as classes; therefore, we use common javascript principles in order to take advantage of such things–even for how we define a javascript “object”. This post in particular will focus on javascript object creation.
There really is no way to define a class in javascript. ECMA-262 defines an object as an “unordered collection of properties each of which contains a primitive value, object, or function.” As such, a javascript object is really nothing more than an array of values (which could be any combination of primitives, other objects, or functions) that are named. This means that an object in javascript is more closely aligned to what I as a traditional OO-language developer think of as a hash table or a map–a grouping of name-value pairs.
Creating objects
Here’s how you’d typically create an object in javascript:
1 2 3 4 5 6 7 8 9 |
|
Looks good. The problem with this traditional approach is that if you were to create multiple guitar objects, you’d be duplicating a lot of code. Dang! This is where programming patterns can help.
The factory pattern
Let’s set up a bit of code that acts like a factory—in other
words, it is responsible for creating a guitar whenever we call it.
We’ll call it createGuitar
:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
…And then this factory function could be used to build multiple objects with little code duplication:
1 2 |
|
The problem with the factory pattern is that we can’t identify what type of an object guitar1 or guitar2 is. Change the names of the variables, and it’s even more potentially confusing. That’s where the constructor pattern helps.
The constructor pattern
Let’s create a function that—like the factory pattern—is responsible
for creating our objects. However, let’s take advantage of some built-in
features of javascript itself (specifically the new
operator and the
this
keyword) to be able to identify the object type:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Note that this constructor is just a function named Guitar (with an uppercase “G”, as javascript convention dictates). No object being created, the properties and methods are assigned to the “this” object, and no return statement. Calling a constructor with the “new” operator causes four steps:
1. Create a new object.
2. Assign the scope of the constructor to the new object so this
points to the new object.
3. Execute the code inside the constructor which adds properties to the new object.
4. Return the new object.
Furthermore, each of these objects has a “constructor” property that points back to the “Guitar” constructor/function. This property was intended for use in identifying the object type:
1 2 |
|
This is an advantage over the Factory pattern. The “instanceof” operator is considered to be a safer way of determining type.
The only difference between javascript “constructors” and other javascript functions is the way they are called (with the new
operator).
Any function that is called with the new
operator acts as a constructor. So, what if I call it without the new
operator? The properties
get added to the window
object. this
then points to the “Global” object when a function is called in the global scope. Using new
assigns the scope of the function to the new object represented by the variable (var
).
So, any possible problems here?
Try this:
1
|
|
What do you think the result is?
If you said “false”, give yourself a brownie. For every object created with a constructor pattern, a new method/function instance is created for each object instance–they are not the same instance of “Function”. The constructor is really doing the following:
1 2 3 4 5 6 7 8 |
|
For the function/method getBrand
, a new Function object
is created every time a new Guitar is created.
We really don’t need two instances of Function that do the same thing. One workaround is to move the function definition outside of the constructor:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This works, but clutters up the global scope. Think if Guitar
had more methods, like getModel
, getYear
, play
, etc.!
The prototype pattern
The prototype pattern solves this problem. Every function is created with a prototype
property, which is an object containing properties and methods that
should be available to instances of a particular reference type.
Let’s rewrite the previous example to use the prototype pattern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Note that we still call the (now empty) Guitar
function as a constructor
to create new guitars and we have the properties and methods present, which
here are shared among instances.
guitar1
has a property that points to the prototype object. So does guitar2
.
(This is known internally as __proto__
, which can be examined via the isPrototypeOf()
method).
The Guitar
constructor function has a member called prototype
, which also points to the
prototype object. So,
guitar1.__proto__
points toGuitar
’s prototype objectguitar2.__proto__
points toGuitar
’s prototype objectGuitar.prototype
points toGuitar
’s prototype object
This protoype object has a constructor
property. It also has other
properties and methods as defined by the user. This constructor property points back to the constructor
function (Guitar
).
Here’s alternate prototype syntax:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
It is important to note that it is not possible to overwrite prototype’s values. In other words, if you add a property to an instance that is the same name as a property on the object’s prototype, you create the property on the instance, which shadows the property on the prototype. For example:
1 2 |
|
To find a property, the javascript compiler performs a two-Step Search order: First it checks the instance for the property/method. If it’s not found there, it then checks the prototype.
There are some problems with this approach. You can’t pass initialization arguments into the constructor–all instances of the object get the same property values by default.
This kinda blows because typically we don’t want all of our object’s properties to have the same values–we want a guitar that’s a Fender brand, we want one that’s a PRS, etc. And yet we still want to take advantage of having methods shared among the various objects. What we need is a combination of approaches.
The Constructor/Prototype combination pattern
In the Constructor/Prototype Combination pattern, the constructor defines instance properties, and the prototype defines methods and shared properties. Best of both worlds, right? To do it, we take a clue from the alternate prototype syntax described above, and explicitly set the constructor property on the prototype object to point to our constructor function. However, the only other property we define is the method we want shared. Every other property goes in the constructor function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Now we have our cake and eat it, too: We can create guitars passing
in different initialization parameters for each one, and yet all guitars
share the same getBrand
method. Great, huh?