Object-Oriented Code in JavaScript

ipivanov No Comments JavaScript 55

Humans are good a categorizing. Objects are a way to categorize behavior and data, making large amounts of code easier to design and think about. In fact, object-oriented programming can be found in many languages used to build all kinds of software and is considered a very good way to write code.
We’ve already looked at objects in JavaScript – they’re collections of named properties and methods, and they can be created on the fly using the object literal syntax:


var user = {
    name: "tom",
    say: function (words) { alert(words); }
};

One reason using objects to hold data is useful is that, as mentioned, human brains are very good at categorizing things: a boat; a chair; a moose. Creating objects in code helps you to think about how the different parts of your code fit and (hopefully) work together.

In a large application you may have a great number of objects interacting with each other. Remember that objects are a grouping together of properties (like name) and methods (like say)? This grouping of data and behavior into one entity is called encapsulation.

A powerful tool available to you when building applications using objects is called inheritance. This is where an objectinherits properties and methods from another object. An object can alter the properties and methods it inherits and add additional properties and methods too.

So, for example, you could create a moose object that inherits behavior from a mammal object. The moose could alter themammal’s furriness, for example. You could then create a pangolin object that also inherits from a mammal. But, of course, pangolins aren’t furry, and they might have a different scaliness property.

How do objects inherit?

JavaScript uses prototypal inheritance. This means that, when an object inherits from another, the parent object is known as the child’s prototype.

There are a couple of other things to know: every object maintains a reference to its prototype and every object inherits from the global object object.

When you ask for a property of an object, JavaScript looks for the property on that object. If it doesn’t find it there, it follows the link to the object’s prototype and looks for the property there. It continues this, up the prototype chain until it finds it, or it returns undefined.

In other words, an object inherits properties and methods by maintaining a link to another object from which it wants to inherit. Multiple objects can inherit from one single object, but one single object cannot inherit from multiple other objects, although it can inherit from an object that inherits from another.

In practice

There are many ways to achieve inheritance in JavaScript. The most commonly used is the constructor pattern. In this pattern, a function called a constructor is used to create new objects. Constructors are used in combination with the new keyword to create objects.

Here’s an example constructor. Note the capital letter on Person – this is an important convention. Generally, in JavaScript, if it’s got a capital first letter, it’s a constructor and should be used with the new keyword.


var Person = function (name) {
    this.name = name;
};

Now you can use the new keyword to create Person objects:


var tom = new Person('tom');

This produces an object like this:


{
    name: "Tom"
}

In the constructor pattern you manually create the object from which the new objects will inherit by adding properties and methods to the prototype property of theconstructor function. Like this:


Person.prototype.say = function (words) {
    alert(this.name + ' says "' + words + '"');
};

Now, objects created from the constructor will have the say method.


var tom = new Person("tom");
tom.say("Hello");

// Produces an alert: tom says "Hello"

This is just the start

Inheritance and object-oriented programming are big, complex areas that whole books are written about. If you’d like to know more there are a few great resources out there.

If you’d like to know more about the new keyword, check out the thread on Stack Overflow. For a slightly more detailed article about constructors, check out Constructors considered mildly confusing.

Object Usage and Properties

Everything in JavaScript acts like an object, with the only two exceptions being null and undefined.

false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

A common misconception is that number literals cannot be used as objects. That is because a flaw in JavaScript’s parser tries to parse the dot notation on a number as a floating point literal.

2.toString(); // raises SyntaxError

There are a couple of workarounds that can be used to make number literals act as objects too.

2..toString(); // the second point is correctly recognized
2 .toString(); // note the space left to the dot
(2).toString(); // 2 is evaluated first

Objects as a Data Type

Objects in JavaScript can also be used as Hashmaps; they mainly consist of named properties mapping to values.

Using an object literal – {} notation – it is possible to create a plain object. This new object inherits from Object.prototype and does not have own properties defined.

var foo = {}; // a new empty object

// a new object with a 'test' property with value 12
var bar = {test: 12}; 

Accessing Properties

The properties of an object can be accessed in two ways, via either the dot notation or the square bracket notation.

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // works

The notations work almost identically, with the only difference being that the square bracket notation allows for dynamic setting of properties and the use of property names that would otherwise lead to a syntax error.

Deleting Properties

The only way to remove a property from an object is to use the deleteoperator; setting the property to undefined or null only removes the valueassociated with the property, but not the key.

var obj = {
    bar: 1,
    foo: 2,
    baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;

for(var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i, '' + obj[i]);
    }
}

The above outputs both bar undefined and foo null – only baz was removed and is therefore missing from the output.

Notation of Keys

var test = {
    'case': 'I am a keyword, so I must be notated as a string',
    delete: 'I am a keyword, so me too' // raises SyntaxError
};

Object properties can be both notated as plain characters and as strings. Due to another mis-design in JavaScript’s parser, the above will throw aSyntaxError prior to ECMAScript 5.

This error arises from the fact that delete is a keyword; therefore, it must be notated as a string literal to ensure that it will be correctly interpreted by older JavaScript engines.

The Prototype

JavaScript does not feature a classical inheritance model; instead, it uses aprototypal one.

While this is often considered to be one of JavaScript’s weaknesses, the prototypal inheritance model is in fact more powerful than the classic model. It is, for example, fairly trivial to build a classic model on top of a prototypal model, while the other way around is a far more difficult task.

JavaScript is the only widely used language that features prototypal inheritance, so it can take time to adjust to the differences between the two models.

The first major difference is that inheritance in JavaScript uses prototype chains.

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// Set Bar's prototype to a new instance of Foo
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// Make sure to list Bar as the actual constructor
Bar.prototype.constructor = Bar;

var test = new Bar(); // create a new bar instance

// The resulting prototype chain
test [instance of Bar]
    Bar.prototype [instance of Foo]
        { foo: 'Hello World' }
        Foo.prototype
            { method: ... }
            Object.prototype
                { toString: ... /* etc. */ }

In the code above, the object test will inherit from both Bar.prototype andFoo.prototype; hence, it will have access to the function method that was defined on Foo. It will also have access to the property value of the one Fooinstance that is its prototype. It is important to note that new Bar() does notcreate a new Foo instance, but reuses the one assigned to its prototype; thus, all Bar instances will share the same value property.

Property Lookup

When accessing the properties of an object, JavaScript will traverse the prototype chain upwards until it finds a property with the requested name.

If it reaches the top of the chain – namely Object.prototype – and still hasn’t found the specified property, it will return the value undefined instead.

The Prototype Property

While the prototype property is used by the language to build the prototype chains, it is still possible to assign any given value to it. However, primitives will simply get ignored when assigned as a prototype.

function Foo() {}
Foo.prototype = 1; // no effect

Assigning objects, as shown in the example above, will work, and allows for dynamic creation of prototype chains.

Performance

The lookup time for properties that are high up on the prototype chain can have a negative impact on performance, and this may be significant in code where performance is critical. Additionally, trying to access non-existent properties will always traverse the full prototype chain.

Also, when iterating over the properties of an object every property that is on the prototype chain will be enumerated.

Extension of Native Prototypes

One mis-feature that is often used is to extend Object.prototype or one of the other built in prototypes.

This technique is called monkey patching and breaks encapsulation. While used by popular frameworks such as Prototype, there is still no good reason for cluttering built-in types with additional non-standard functionality.

The only good reason for extending a built-in prototype is to backport the features of newer JavaScript engines; for example, Array.forEach.

In Conclusion

It is essential to understand the prototypal inheritance model before writing complex code that makes use of it. Also, be aware of the length of the prototype chains in your code and break them up if necessary to avoid possible performance problems. Further, the native prototypes should neverbe extended unless it is for the sake of compatibility with newer JavaScript features.

Leave a Reply Text

Leave a Reply