JavaScript has a complicated history. The name itself seems to indicate a relationship to Java, when in fact, the two languages share little in common except for a common syntax relationship by way of C. Add to this the myriad of browser incompatibilities and numerous examples of bad usage, and you have what is perhaps the world’s least understood popular programming language. (I had to add the ‘popular’ qualifier, since I am sure there are some esoteric languages out there that only a handful of people know)
This is why inheritance in JavaScript is probably one of the least understood concepts. While languages like Java have well-defined constructs for inheritance, the topic is of less importance with JavaScript. In many situations, you’ll never have to deal with these aspects when programming in JavaScript, simply because it isn’t required for a lot of client side scripts. However, for larger-scale web applications, applying object-oriented principles to your code may make it easier to design, improve and maintain.
There have been many articles written about inheritance in JavaScript and how it can and should be done. You really don’t need to read mine to gain an understanding. Rather, I’m just going to write about what I’ve learned, the process I went through and the experience I’ve gained along the way.
It’s all new(s) to me
If you come from a Java background, you’ll be no stranger to the new
keyword. Using it allows you to instantiate a new instance of an object from its class by calling its constructor. The keyword also makes a lot of sense, since you’re creating a ‘new’ object.
JavaScript also has a new
keyword, but it doesn’t do quite the same thing as its Java counterpart, though at first the effects might seem similar. This is one of the main reasons why JavaScript inheritance tends to be poorly understood.
Consider the following example.
function Foo(name)
{
this.name = name;
}
var fooObject = new Foo('I am foo');
If you’re familiar with Java or JavaScript, this example is fairly straightforward. In the prototype-based JavaScript, functions can serve as object constructors in somewhat the same manner as a constructor in Java. However, Foo
in the above example is not a class, since classes do not exist in JavaScript. So, if Foo
is not a class, what exactly happens when the statement var fooObject = new Foo('I am foo')
is executed?
Taking a step back
Before we dive into the details, I’ll explain the concepts of a prototype-based language as it applies to JavaScript. I mentioned above that JavaScript does not use classes like Java does. This basically means that new objects are not created from instantiation of classes, but rather inherit from existing objects. Thus, there is no “class hierarchy” but rather just an object hierarchy.
In JavaScript, all objects are descended from the built-in Object
type. This is similar to how all Java classes implicitly have the Object class as a superclass. In JavaScript, instead of extending classes, you can inherit by manipulating the prototype
property on a type; that is, the prototype
property on the function you are using to create an object. Let’s see some code to straighten things out.
function Foo(name)
{
this.name = name;
}
Foo.prototype.getName = function()
{
alert(this.name);
}
var fooObject = new Foo('I am foo');
fooObject.getName(); // Will alert with 'I am foo'
This example is identical to the first one, except for the lines that are emphasized. Here, I’ve set the getName
property on the prototype
property of Foo
equal to a reference to a Function object. This is effectively the same as defining a method for a class, since any object created from that type can call the method getName()
on itself to return the proper value. But what exactly is happening, and what’s the deal with the prototype
property?
More new(s)
In JavaScript, the new
keyword actually accomplishes a few things. When you execute an expression such as var fooObject = new Foo('I am foo')
, here’s exactly what happens.
-
First, a new generic object is created. This object is the same as if you created the object using the statement
new Object()
, using JavaScript’s built in Object type. -
The constructor function, that is, the function
Foo()
, is then called with thethis
keyword set to reference the newly-created object. The parameters supplied are also passed to the constructor function, in this case, the string “I am foo”. If you’re familiar with context, this is equivalent to:var fooObject = new Object(); Foo.call(fooObject, 'I am foo');
Thus, the code in
Foo()
sets a property calledname
equal to the supplied string. Up to this point, things are the same as if you had created a new object and then manually set thename
property yourself. The next step is where things change. -
The last step is where inheritance takes effect. The constructor function, in addition to executing the specified code above, also sets an internal property called
__proto__
equal to the value ofFoo.prototype
. This is how the new object inherits from the old one.When you call a property on
fooObject
now, JavaScript will first check to see if the object has a local value for it. In this case, the only local property isname
. (Besides the internal and implicitly set__proto__
property) However, if it does not exist locally, it will check to see if the property exists under the object referenced by the__proto__
property. In this case, thegetName()
method exists under here.
Where things get confusing is because of how the new
keyword is used. Perhaps the designers of JavaScript wanted to use a keyword familiar to Java programmers so as to ease the transition to a new language that sounded so similar. While it accomplishes a similar goal and for the most part you can consider the action to be almost the same, the use of the new
keyword conceals the true meaning of prototype-based inheritance in JavaScript. This ends up causing more confusion when you start digging into code.
We’re all the same
Suppose you created multiple objects from the Foo()
constructor, called fooObject1
, fooObject2
and fooObject3
, In one fell swoop, you could add a new method to all of these objects without having to recreate them. This is because of the prototype
property. Because each object contains a reference to Foo.prototype
, adding a new method here will allow all objects access to it. For example:
Foo.prototype.yellName = function()
{
alert(this.name.toUpperCase());
}
var fooObject3 = new Foo('I am Foo 3!');
fooObject3.yellName(); // alerts 'I AM FOO 3!'
This adds another method to all Foo
-constructed objects allowing them to “yell” their names.
Extending Types
I mentioned inheritance above but with just one type definition, the principles weren’t too clear. Suppose we decided to extend our Foo
“class” – how exactly would this work? First, you need to define the child type and then set its prototype
value to a new object of the parent type. An example:
function FooChild(childName)
{
Foo.call(this, 'I am a child of Foo and my name is ' + childName);
}
FooChild.prototype = new Foo('');
var fooChildObject = new FooChild('Barbar');
fooChildObject.getName(); // Will alert with 'I am a child of Foo and my name is Barbar'
Let’s break down what’s going on here.
-
First, we define a new constructor called
FooChild
. In it, wecall
the parent constructor function with the context set to the current object. This allows thename
property to be set on the object during creation since that’s what the parent does. -
Then, we set the
prototype
property equal to a new object that’s an instance ofFoo
. This is what allows all of the child objects to have access to the parent’s properties, such as thegetName()
method. Coming from the Java world, this would be roughly equivalent to theextends
keyword, when defining a class that inherits from another.
A few more details
JavaScript provides two special operators for dealing with types and objects. One is called instanceof
and the other is typeof
. The latter, typeof
, is less useful since it only deals with built-in JavaScript types.
The typeof
operator returns the current variable’s type, but is limited to predefined types. For example, typeof fooChildObject
or typeof fooObject
both return a string with the contents of “object”. Thus, differentiating between the two is impossible. More information can be found at the Mozilla Developer Center.
The instanceof
operator is more interesting. It takes two arguments and tests whether the first is an instance of the second. (I guess I didn’t need to explain that) For example:
function Foo(name)
{
this.name = name;
}
Foo.prototype.getName = function()
{
alert(this.name);
}
function FooChild(childName)
{
Foo.call(this, 'I am a child of Foo and my name is ' + childName);
}
FooChild.prototype = new Foo('');
var fooObject = new Foo('I am foo');
var fooChildObject = new FooChild('Barbar');
alert(fooChildObject instanceof Foo); // true
alert(fooObject instanceof Foo); // true
alert(fooChildObject instanceof FooChild); // true
alert(fooObject instanceof FooChild); // false;
In the above code, it is clear that fooChildObject
is an instance of FooChild
, and fooObject
is an instance of Foo
. However, fooChildObject
is also an instance of Foo
, since its type was inherited from it through the prototype
property. However, fooObject
is not an instance of FooChild
since that type is a child of Foo
.
I hope I haven’t said “foo” one too many times for you.
More information about instanceof
is again available from the excellent Mozilla Developer Center.
Concluding remarks
I hope this has helped you to understand the nature of JavaScript’s built-in inheritance features. I know I found it very convoluted at first, coming from a class-inheritance background. I read everything I could find on the subject, and I believe it’s made me a better JavaScript programmer by understanding some of the “inner workings” of the language. I encourage you to check out some of the references I’ve provided below.
References
- Prototypal Inheritance in JavaScript – Douglas Crockford
- The Philosophy of JavaScript – Jesse of 20bits
- Prototype Inheritance Revisited – Mozilla Developer Center
- The Employee Example – Mozilla Developer Center
Hi Peter,
very nice summary, very clearly explained. Thanks a lot.
Tom