[Coding] JavaScript Patterns (Ch. 6)
Posted by Khatharsis on June 21, 2013
This chapter talked about code reuse including inheritance and other methods like composing objects from other objects, mix-ins, and temporarily borrowing and reusing functionality without permanent inheritance. The book quotes the Gang of Four’s advice on preferring “object composition to class inheritance.”
I have used the word “classical” in previous posts to refer to C++, Java, and C#. To give a little more clarification, I’m not hinting that these languages are old (like classical music) or are the standard taught in school (no longer the case with Python), but rather they rely on the concept of classes – hence why it’s C++ and not C/C++. In JavaScript, classical inheritance is a similar wordplay on “class,” where classes are the blueprints for the creation of objects. Objects cannot exist without a class. As discussed in Chapter 5, JavaScript supports new and in classical languages, this is how instances of objects are created. But, also recall that in JavaScript, something like var thing = new Thing(), Thing is just a function and not a class.
Classical inheritance patterns follow the same train of thought of using classes as in OOP languages. Modern inheritance patterns use techniques other than classes. It is preferable to use modern inheritance patterns to take advantage of JavaScript’s “good parts” (as Crockford would put it), but classical inheritance can be still be emulated.
—
When using classical inheritance patterns, the goal is to have a child constructor get properties from a parent constructor. There are multiple ways of achieving this goal.
The first pattern is the “default pattern” and simply assigns a parent’s constructor (P) to the child’s prototype (C). The example is from the book:
function inherit(C, P) {
C.prototype = new P();
}
Word of caution, the prototype property should point to an object and not a function. The constructor of P() should return a P object and the new keyword is necessary for this pattern to work.
What occurs is when a function is not found in the Child object’s list of properties, it will travel up the prototype chain looking for the property. Since prototype points to Parent, Parent is searched next. Recall that objects are not really empty because they inherit, via prototype, some properties from something built-in like Object. So, Child can be “empty” of its own properties, but inherit Parent and have access to Parent’s functions:
var p = new Parent();
p.say = function() {
return 'Now, now.';
};
p.name = 'Adam';
var c = new Child();
inherit(c, p);
c.say(); // 'Now, now.'
c.name; // 'Adam'
// c is "empty" - let's add something to it
c.name = 'Ann';
c.name; // Ann
// Now let's delete it
delete c.name;
c.name; // 'Adam'
I added a little bit of code at the end to display how a child property with the same name as the parent’s property can “override” the parent’s value, then if that same property were to be deleted, the parent’s value “shines through.”
One drawback of the default pattern is both own properties (those added to this) and prototype properties are inherited when it is often preferable to not want the own properties. I’m not too clear on this point. Another drawback is the inherit() function does not accept any additional parameters to enable the child to pass to the parent when invoking a parent’s method. It becomes inefficient to keep re-creating parent objects every time you create a new child just to pass parameters to the parent it inherits from.
The second classical pattern is called “rent-a-constructor,” solving the problem of passing parameters to the parent by using apply() with the Parent’s constructor:
function Child(a, b, c, d) {
Parent.apply(this, arguments);
}
this points to Child and recall that the arguments variable is a built-in variable containing an array of all of the parameters passed into the function. call() may be used if there are no parameters to be passed. Properties added to this inside the parent constructor are inherited and not members added to the prototype. The inherited members are also copies rather than references, so changes made to the members are local to the child and the parent retains its original value. Multiple inheritance is also possible by using multiple parent constructors in a child object.
The pro of this pattern is copies of the parent’s members are given to the child, but the con is the lack of use of prototype, which is where reusable code goes to avoid recreation in each instance.
The third pattern, “rent-and-set prototype,” combines the best of the previous two patterns – setting prototype of the Child to its Parent and having local copies of parents’ members. The implementation is quite simple:
function Child(a, b, c, d) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
The drawback is the Parent constructor is called twice, meaning own properties are inherited twice.
The fourth pattern, “share the prototype,” attempts to fix this double constructor call by making a slight change to the inherit() function:
function inherit() {
C.prototype = P.prototype;
}
Now, both C’s and P’s prototype point to the same thing. However, if C changes something in the prototype, it gets changed for everyone who’s prototype also points to the same thing, including P itself.
The fifth pattern attempts a different method using a proxy as a “temporary constructor.” inherit() becomes:
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
}
Where F is the proxy. The point to this is to still inherit the prototype, but break the direct link between the Child and the Parent by going through a proxy. A bonus is own properties of the Parent are not inherited by the Child because the child has an instance of the proxy, which is essentially an empty object that conveniently has a prototype pointing to the Parent’s prototype.
A “super” can be implemented by adding the line, C.uber = P.prototype; (super is a reserved word), to emulate the superclass in classical languages. Another addition to the inherit() function is to set the constructor’s name to the actual Child, rather than it’s Parent: C.prototype.constructor = C;.
—
Prototypal inheritance does away with the notion of classes and instead the concept is simply objects inherit from other objects. The code is likewise quite simple:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var parent = {};
var child = object(parent);
Using the object() function, which uses the proxy/temporary constructor pattern, child inherits from parent. One variation is instead of using a literal notation for the Parent, a constructor function can create the Parent instead. This variation will make both own properties and the Parent’s prototype inherited, rather than just the prototype as in the original pattern. Another variation is to simply pass Parent.prototype rather than Parent and this will pass only the prototype properties.
—
Another modern form of inheritance is by copying properties. The concept is to loop through the Parents’ properties and copy them into the Child:
function extend(parent, child) {
var i;
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
}
This implementation is a shallow copy, so array and object properties will not be fully copied unless a deep copy is implemented. A deep copy involves checking if the property is an array or object, then use recursion to copy all of the property’s contents to the Child. I won’t paste in the code here. Another bonus to a deep copy is the Child no longer has a reference to the Parent’s array or object, that is, if a Child modifies a property that is an array or object, the Parent’s property is not affected. One last interesting point about this pattern is does not involve the use of prototype at all, which was heavily discussed throughout most of the chapter.
The copying properties pattern can be built on using mix-ins. The concept of mix-ins is similar to object composition or aggregation. Instead of copying properties from one parent, copy properties from multiple parents to create a new, custom object. Again, I’ll not include the code, but it is an interesting pattern that may come in handy later.
—
The last section is about borrowing and binding methods through call, apply, or assignment. The use of the keyword this is based on the call expression, but sometimes this might want to be “locked” or bound to a specific object. The following is an example from the book, giving a quick demonstration of borrowing a method with the use of this:
var one = {
name: "object",
say: function (greet) {
return greet + ", " + this.name;
}
};
// test
one.say('hi'); // "hi, object"
var two = {
name: "another object"
};
one.say.apply(two, ['hello']); // "hello, another object"
two is borrowing one‘s say() method. By passing itself as this in the apply() method, the this.name points to two‘s name value. There are cases where this is unbound and points to the global object, such as if the function is passed as a callback. Hence, the need to bind this to a specific object and can be done using the following pattern:
function bind(o, m) {
return function () {
return m.apply(o, [].slice.call(arguments));
};
}
—
I skipped over the Klass section, which covered how to use an automatically invoking constructor function similar to OOP languages. I also skipped over a couple of ECMA 5 subsections. I haven’t had a need to use these patterns yet, so I was aiming for a more surface overview rather than a deep dive. I was more amused that the classical patterns, which are not encouraged, took up most of the chapter.