Object Patterns - The Principles of Object-Oriented Javascript (2014)

The Principles of Object-Oriented Javascript (2014)

Chapter 6. Object Patterns

JavaScript has many patterns for creating objects, and there’s usually more than one way to accomplish the same thing. You can define your own custom types or your own generic objects whenever you want. You can use inheritance to share behavior between objects, or you can employ other techniques, such as mixins. You can also take advantage of advanced JavaScript features to prevent an object’s structure from being modified. The patterns discussed in this chapter give you powerful ways of managing and creating objects, all based on your use cases.

Private and Privileged Members

All object properties in JavaScript are public, and there’s no explicit way to indicate that a property shouldn’t be accessed from outside a particular object. At some point, however, you might not want data to be public. For example, when an object uses a value to determine some sort of state, modifying that data without the object’s knowledge throws the state management process into chaos. One way to avoid this is by using naming conventions. For example, it’s quite common to prefix properties with an underscore (such as this._name) when they are not intended to be public. However, there are ways of hiding data that don’t rely on convention and are therefore more “bulletproof” in preventing the modification of private information.

The Module Pattern

The module pattern is an object-creation pattern designed to create singleton objects with private data. The basic approach is to use an immediately invoked function expression (IIFE) that returns an object. An IIFE is a function expression that is defined and then called immediately to produce a result. That function expression can contain any number of local variables that aren’t accessible from outside that function. Because the returned object is defined within that function, the object’s methods have access to the data. (All objects defined within the IIFE have access to the same local variables.) Methods that access private data in this way are called privileged methods. Here’s the basic format for the module pattern:

var yourObject = (function() {

// private data variables

return {

// public methods and properties

};

❶ }());

In this pattern, an anonymous function is created and executed immediately. (Note the extra parentheses at the end of the function ❶. You can execute anonymous functions immediately using this syntax.) That means the function exists for just a moment, is executed, and then is destroyed. IIFEs are a very popular pattern in JavaScript, partially for their use in the module pattern.

The module pattern allows you to use regular variables as de facto object properties that aren’t exposed publicly. You accomplish this by creating closure functions as object methods. Closures are simply functions that access data outside their own scope. For example, whenever you access a global object in a function, such as window in a web browser, that function is accessing a variable outside its own scope. The difference with the module function is that the variables are declared within the IIFE, and a function that is also declared inside the IIFE accesses those variables. For example:

var person = (function() {

var age = 25;

return {

name: "Nicholas",

❷ getAge: function() {

return age;

},

❸ growOlder: function() {

age++;

}

};

}());

console.log(person.name); // "Nicholas"

console.log(person.getAge()); // 25

person.age = 100;

console.log(person.getAge()); // 25

person.growOlder();

console.log(person.getAge()); // 26

This code creates the person object using the module pattern. The age variable ❶ acts like a private property for the object. It can’t be accessed directly from outside the object, but it can be used by the object methods. There are two privileged methods on the object: getAge() ❷, which reads the value of the age variable, and growOlder() ❸, which increments age. Both of these methods can access the variable age directly because it is defined in the outer function in which they are defined.

There is a variation of the module pattern called the revealing module pattern, which arranges all variables and methods at the top of the IIFE and simply assigns them to the returned object. You can write the previous example using the revealing module pattern as follows:

var person = (function() {

var age = 25;

function getAge() {

return age;

}

function growOlder() {

age++;

}

return {

name: "Nicholas",

❶ getAge: getAge,

growOlder: growOlder

};

}());

In the revealing module pattern, age, getAge(), and growOlder() are all defined as local to the IIFE. The getAge() and growOlder() functions are then assigned to the returned object ❶, effectively “revealing” them outside the IIFE. This code is essentially the same as the earlier example using the traditional module pattern; however, some prefer this pattern because it keeps all variable and function declarations together.

Private Members for Constructors

The module pattern is great for defining individual objects that have private properties, but what about custom types that also require their own private properties? You can use a pattern that’s similar to the module pattern inside the constructor to create instance-specific private data. For example:

function Person(name) {

// define a variable only accessible inside of the Person constructor

var age = 25;

this.name = name;

this.getAge = function() {

return age;

};

this.growOlder = function() {

age++;

};

}

var person = new Person("Nicholas");

console.log(person.name); // "Nicholas"

console.log(person.getAge()); // 25

person.age = 100;

console.log(person.getAge()); // 25

person.growOlder();

console.log(person.getAge()); // 26

In this code, the Person constructor has a local variable, age. That variable is used as part of the getAge() ❶ and growOlder() ❷ methods. When you create an instance of Person, that instance receives its own age variable, getAge() method, and growOlder() method. In many ways, this is similar to the module pattern, where the constructor creates a local scope and returns the this object. As discussed in Chapter 4, placing methods on an object instance is less efficient than doing so on the prototype, but this is the only approach possible when you want private, instance-specific data.

If you want private data to be shared across all instances (as if it were on the prototype), you can use a hybrid approach that looks like the module pattern but uses a constructor:

var Person = (function() {

// everyone shares the same age

var age = 25;

function InnerPerson(name) {

this.name = name;

}

InnerPerson.prototype.getAge = function() {

return age;

};

InnerPerson.prototype.growOlder = function() {

age++;

};

return InnerPerson;

}());

var person1 = new Person("Nicholas");

var person2 = new Person("Greg");

console.log(person1.name); // "Nicholas"

console.log(person1.getAge()); // 25

console.log(person2.name); // "Greg"

console.log(person2.getAge()); // 25

person1.growOlder();

console.log(person1.getAge()); // 26

console.log(person2.getAge()); // 26

In this code, the InnerPerson constructor ❷ is defined inside an IIFE. The variable age ❶ is defined outside the constructor but is used for two prototype methods. The InnerPerson constructor is then returned and becomes the Person constructor in the global scope. All instances ofPerson end up sharing the age variable, so changing the value with one instance automatically affects the other instance.

Mixins

Although pseudoclassical inheritance and prototypal inheritance are used frequently in JavaScript, there is also a type of pseudoinheritance accomplished through mixins. Mixins occur when one object acquires the properties of another without modifying the prototype chain. The first object (a receiver) actually receives the properties of the second object (the supplier) by copying those properties directly. Traditionally, you create mixins using a function such as this:

function mixin(receiver, supplier) {

for (var property in supplier) {

if (supplier.hasOwnProperty(property)) {

receiver[property] = supplier[property]

}

}

return receiver;

}

The mixin() function accepts two arguments: the receiver and the supplier. The goal of the function is to copy all enumerable properties from the supplier onto the receiver. You accomplish this using a for-in loop that iterates over the properties in supplier and then assigns the value of that property to a property of the same name on receiver. Keep in mind that this is a shallow copy, so if a property contains an object, then both the supplier and the receiver will be pointing to the same object. This pattern is used frequently for adding new behaviors to JavaScript objects that already exist on other objects.

For example, you can add event support to an object through a mixin rather than inheritance. First, suppose you’ve already defined a custom type for using events:

function EventTarget(){

}

EventTarget.prototype = {

constructor: EventTarget,

❶ addListener: function(type, listener){

// create an array if it doesn't exist

if (!this.hasOwnProperty("_listeners")) {

this._listeners = [];

}

if (typeof this._listeners[type] == "undefined"){

this._listeners[type] = [];

}

this._listeners[type].push(listener);

},

❷ fire: function(event){

if (!event.target){

event.target = this;

}

if (!event.type){ // falsy

throw new Error("Event object missing 'type' property.");

}

if (this._listeners && this._listeners[event.type] instanceof Array){

var listeners = this._listeners[event.type];

for (var i=0, len=listeners.length; i < len; i++){

listeners[i].call(this, event);

}

}

},

❸ removeListener: function(type, listener){

if (this._listeners && this._listeners[type] instanceof Array){

var listeners = this._listeners[type];

for (var i=0, len=listeners.length; i < len; i++){

if (listeners[i] === listener){

listeners.splice(i, 1);

break;

}

}

}

}

};

The EventTarget type provides basic event handling for any object. You can add ❶ and remove ❸ listeners as well as fire events ❷ directly on the object. The event listeners are stored on a _listeners property that is created only when addListener() is called for the first time (this makes it easier to mix in). You can use instances of EventTarget like this:

var target = new EventTarget();

target.addListener("message", function(event) {

console.log("Message is " + event.data);

})

target.fire({

type: "message",

data: "Hello world!"

});

Support for events is useful for objects in JavaScript. If you want to have a different type of object that also supports events, you have a few options. First, you can create a new instance of EventTarget and then add on the properties that you want:

var person = new EventTarget();

person.name = "Nicholas";

person.sayName = function() {

console.log(this.name);

this.fire({ type: "namesaid", name: name });

};

In this code, a new variable called person is created as an instance of EventTarget, and then the person-related properties are added. Unfortunately, this means that person is actually an instance of EventTarget instead of Object or a custom type. You also incur the overhead of needing to add a bunch of new properties by hand. It would be better to have a more organized way of doing this.

A second way to solve this problem is to use pseudoclassical inheritance:

function Person(name) {

this.name = name;

}

❶ Person.prototype = Object.create(EventTarget.prototype);

Person.prototype.constructor = Person;

Person.prototype.sayName = function() {

console.log(this.name);

this.fire({ type: "namesaid", name: name });

};

var person = new Person("Nicholas");

console.log(person instanceof Person); // true

console.log(person instanceof EventTarget); // true

In this case, there is a new Person type that inherits from EventTarget ❶. You can add any further methods you need to Person’s prototype afterward. However, this isn’t as succinct as it could be, and you could argue that the relationship doesn’t make sense: A person is a type of event target? By using a mixin instead, you can reduce the amount of code necessary to assign those new properties to the prototype:

function Person(name) {

this.name = name;

}

❶ mixin(Person.prototype, new EventTarget());

mixin(Person.prototype, {

constructor: Person,

sayName: function() {

console.log(this.name);

this.fire({ type: "namesaid", name: name });

}

});

var person = new Person("Nicholas");

console.log(person instanceof Person); // true

console.log(person instanceof EventTarget); // false

Here, Person.prototype is mixed in with a new instance of EventTarget ❶ to get the event behavior. Then, Person.prototype is mixed in with constructor and sayName() to complete the composition of the prototype. Instances of Person are not instances of EventTarget in this example because there is no inheritance.

Of course, you might decide that while you do want to use an object’s properties, you don’t want a constructor of pseudoclassical inheritance at all. In that case, you can use a mixin directly when you create your new object:

var person = mixin(new EventTarget(), {

name: "Nicholas",

sayName: function() {

console.log(this.name);

this.fire({ type: "namesaid", name: name });

}

});

In this example, a new instance of EventTarget is mixed in with some new properties to create the person object without affecting person’s prototype chain.

One thing to keep in mind about using mixins in this way is that accessor properties on the supplier become data properties on the receiver, which means you can overwrite them if you’re not careful. That’s because the receiver properties are being created by assignment rather than byObject.defineProperty(), meaning the current value of the supplier property is read and then assigned to a property of the same name on the receiver. For example:

var person = mixin(new EventTarget(), {

❶ get name() {

return "Nicholas"

},

sayName: function() {

console.log(this.name);

this.fire({ type: "namesaid", name: name });

}

});

console.log(person.name); // "Nicholas"

❷ person.name = "Greg";

console.log(person.name); // "Greg"

In this code, name is defined as an accessor property with only a getter ❶. That means assigning a value to the property should have no effect. However, because the accessor property becomes a data property on the person object, it’s possible to overwrite name with a new value ❷. During the call to mixin(), the value of name is read from the supplier and assigned to the property called name on the receiver. At no point during this process is a new accessor defined, making the name property on the receiver a data property.

If you want accessor properties to be copied over as accessor properties, you need a different mixin() function, such as:

function mixin(receiver, supplier) {

❶ Object.keys(supplier).forEach(function(property) {

var descriptor = Object.getOwnPropertyDescriptor(supplier, property);

❷ Object.defineProperty(receiver, property, descriptor);

});

return receiver;

}

var person = mixin(new EventTarget(), {

get name() {

return "Nicholas"

},

sayName: function() {

console.log(this.name);

this.fire({ type: "namesaid", name: name });

}

});

console.log(person.name); // "Nicholas"

person.name = "Greg";

console.log(person.name); // "Nicholas"

This version of mixin() uses Object.keys() ❶ to get an array of all enumerable own properties on supplier. The forEach() method is used to iterate over those properties. The property descriptor for each property on supplier is retrieved and then added to receiver viaObject.defineProperty() ❷. This ensures that all of the relevant property information is transferred to receiver, not just the value. That means the person object has an accessor property called name, so it cannot be overwritten.

Of course, this version of mixin() works only in ECMAScript 5 JavaScript engines. If your code needs to work for older engines, you should combine the two mixin() approaches into a single function:

function mixin(receiver, supplier) {

if (Object.getOwnPropertyDescriptor) {

Object.keys(supplier).forEach(function(property) {

var descriptor = Object.getOwnPropertyDescriptor(supplier, property);

Object.defineProperty(receiver, property, descriptor);

});

} else {

for (var property in supplier) {

if (supplier.hasOwnProperty(property)) {

receiver[property] = supplier[property]

}

}

}

return receiver;

}

Here, mixin() checks whether Object.getOwnPropertyDescriptor() ❶ exists to determine whether the JavaScript engine supports ECMAScript 5. If so, it goes on to use the ECMAScript 5 version. Otherwise, the ECMAScript 3 version is used ❷. This function is safe to use in both modern and legacy JavaScript engines, as they will apply the most appropriate mixin strategy.

NOTE

Keep in mind that Object.keys() returns only enumerable properties. If you want to also copy over nonenumerable properties, use Object.getOwnPropertyNames() instead.

Scope-Safe Constructors

Because all constructors are just functions, you can call them without using the new operator and therefore affect the value of this. Doing so can yield unexpected results, as this ends up coerced to the global object in nonstrict mode, or the constructor throws an error in strict mode. InChapter 4, you encountered this example:

function Person(name) {

this.name = name;

}

Person.prototype.sayName = function() {

console.log(this.name);

};

var person1 = Person("Nicholas"); // note: missing "new"

console.log(person1 instanceof Person); // false

console.log(typeof person1); // "undefined"

console.log(name); // "Nicholas"

In this case, name is created as a global variable because the Person constructor is called without new ❶. Keep in mind that this code is running in nonstrict mode, as leaving out new would throw an error in strict mode. The fact that the constructor begins with a capital letter usually indicates that it should be preceded by new, but what if you want to allow this use case and have the function work without new? Many built-in constructors, such as Array and RegExp, also work without new because they are written to be scope safe. A scope-safe constructor can be called with or without new and returns the same type of object in either case.

When new is called with a function, the newly created object represented by this is already an instance of the custom type represented by the constructor. So you can use instanceof to determine whether new was used in the function call:

function Person(name) {

if (this instanceof Person) {

// called with "new"

} else {

// called without "new"

}

}

Using a pattern like this lets you control what a function does based on whether it’s called with new or without. You may want to treat each circumstance differently, but you’ll often want the function to behave the same way (frequently, to protect against accidental omission of new). A scope-safe version of Person looks like this:

function Person(name) {

if (this instanceof Person) {

this.name = name;

} else {

return new Person(name);

}

}

For this constructor, the name property is assigned as always when new is used. If new isn’t used, the constructor is called recursively via new to create a proper instance of the object. In this way, the following are equivalent:

var person1 = new Person("Nicholas");

var person2 = Person("Nicholas");

console.log(person1 instanceof Person); // true

console.log(person2 instanceof Person); // true

Creating new objects without using the new operator is becoming more common as an effort to curb errors caused by omitting new. JavaScript itself has several reference types with scope-safe constructors, such as Object, Array, RegExp, and Error.

Summary

There are many different ways to create and compose objects in JavaScript. While JavaScript does not include the formal concept of private properties, you can create data or functions that are accessible only from within an object. For singleton objects, you can use the module pattern to hide data from the outside world. You can use an immediately invoked function expression (IIFE) to define local variables and functions that are accessible only by the newly created object. Privileged methods are methods on the object that have access to private data. You can also create constructors that have private data by either defining variables in the constructor function or by using an IIFE to create private data that is shared among all instances.

Mixins are a powerful way to add functionality to objects while avoiding inheritance. A mixin copies properties from one object to another so that the receiving object gains functionality without inheriting from the supplying object. Unlike inheritance, mixins do not allow you to identify where the capabilities came from after the object is created. For this reason, mixins are best used with data properties or small pieces of functionality. Inheritance is still preferable when you want to obtain more functionality and know where that functionality came from.

Scope-safe constructors are constructors that you can call with or without new to create a new object instance. This pattern takes advantage of the fact that this is an instance of the custom type as soon as the constructor begins to execute, which lets you alter the constructor’s behavior depending on whether or not you used the new operator.

The Principles of Object-Oriented JavaScript is set in New Baskerville, Futura, TheSansMono Condensed, and Dogma. The book was printed and bound by Lake Book Manufacturing in Melrose Park, Illinois. The paper is 60# Husky Opaque Offset Smooth, which is certified by the Sustainable Forestry Initiative (SFI).