In my previous blog post “JavaScript “Code Reuse” — Part 1–2 : Functional Classes,” I finished cleaning up the functional shared code above. We’re now ready to make a change that would improve performance.

Prototype chains allow many child objects to reflect properties of a parent object. We could use a prototype object to store all the shared methods and make all our instance objects delegate to that property object (rather than using extend to copy all the methods references over).

[library code] 
var Car = function(location) {
var obj = { location : location };
extend(obj, Car.methods);
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

We could use the Car.methods object as our prototype, since it has all the shared methods on it already. We would make all the car instances delegate to it.

[library code] 
var Car = function(location) {
var obj = { location : location };
extend(obj, Car.methods);
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

When creating each instance, we named the instance as “obj” within the constructor. So, talking about Car’s obj variable is the same as talking about “every instance.” The problem is that we used an object literal to create the obj object and there’s no way to specify what a new object will delegate to.

[library code] 
var Car = function(location) {
var obj = ____________________
// [Note] code once included a .location property
extend(obj, Car.methods);
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

So, let’s take the object literal code out. Keep in mind that we removed the definition of a .location property. In that place, we’ll use Object.create to generate an object with a delegation relationship.

COPYRIGHT © 2018 CodeStates
[library code] 
var Car = function(location) {
var obj = Object.create( );
// [Note] code once included a .location property
extend(obj, Car.methods);
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Whatever we pass to Object.create will be the prototype where obj delegates any failed property lookups that can’t be found directly on obj.

COPYRIGHT © 2018 CodeStates
[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
// [Note] code once included a .location property
extend(obj, Car.methods);
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

If we use Car.methods as the prototype for obj, then failed property lookups on instances will fall through it. Thus, all instances will appear to have the properties stored there, even if we don’t copy them all over manually.

COPYRIGHT © 2018 CodeStates
[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
// [Note] code once included a .location property
extend(obj, ___________);
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

In that case, we wouldn’t even need to copy the methods over.

[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
// [Note] code once included a .location property

return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Also, there’s no need to call extend at all.

COPYRIGHT © 2018 CodeStates
[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
obj.location = location;
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

All we have left to do is making sure that our new object gets .location property.

COPYRIGHT © 2018 CodeStates

With this change, we’ve arrived at the completed prototypal pattern (not to be confused with the pseudo-classical pattern, which will be covered in the next blog post). If Netscape hadn’t pushed to add Java-esque keywords and features to the language way back in 1995, we might have written code very much like this for all of our classes in JavaScript.

var Car = function(location) {
// 1) When making instances, you need a function,
var obj = Object.create( Car.methods );
// 2) a line in that function, where you create a new instance object
// 3) a delegation from the new object to some prototype
obj.location = location;
// 4) some logic for augmenting the new object with the properties that make it unique from other objects of the same class
return obj;
};

Since this pattern is so common, the language designer decided to add official conventions to support it.

[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
obj.location = location;
return obj;
};
Car.methods = {
move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Because building a holder object for methods and attaching it as a property of the class function is so common, the language does it for you automatically. Whenever any function gets created, it will have an object attached to it to use as a container for methods just in case you plan on using that function to build instances of a class.

COPYRIGHT © 2018 CodeStates

PLOT TWIST! This (occasionally) handy property of function objects isn’t stored at the key “.methods.”

[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
obj.location = location;
return obj;
};
Car.prototype = {
Car.prototype.move : function() { this.location++; },
};
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

We called the object “methods” in our example. But, the default object that comes with every function is stored at the key “.prototype.”

[library code] 
var Car = function(location) {
var obj = Object.create( Car.methods );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Since the prototype object is going to be available for us automatically, we don’t even need to build the object ourselves. We can just add to it by using a simple assignment operation.

COPYRIGHT © 2018 CodeStates
[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Car.methods doesn’t exist anymore. So, we need to refactor the rest of our code to reflect use of the provided container object.

WARNING : People tend to get tripped up at this point, because they start to imagine that the .prototype property has special rules about it that will influence how this code should work. In truth, nothing interesting has been changed. Using the key “prototype” here instead of “methods” is purely cosmetic.

For example, like any newly created function, Car doesn’t delegate failed lookups to the object stored at its .prototype key. We didn’t imagine Car would delegate to the object at .methods. This is not different. Brand new objects created with object literals don’t delegate to one of their properties either, and neither do brand new functions. Delegation relationships are only created via use of the Object.create function. Thus, Car.move in this example results in undefined, just as it would have when using “methods” as the key. Furthermore, result objects from the Car function delegate to Car.prototype because of our own call to Object.create, not because we’re using the key “prototype” to store the object we’ll use as a prototype.

The .prototype property as a freely-provided object for storing things with no additional special characteristics (.prototype = .methods).

[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
[example.js]
var Example = function() {
return Object.create( Car.prototype );
};

To illustrate, this Example function has the same relationship with Car.prototype. Both of them are simply functions that create objects, and those objects happen to delegate to the same prototype object. The fact that the prototype object is stored as a property on one of those two functions is not important. Both functions behave the same as they would if the prototype had been stored in a global variable.

Historically, this naming choice has been extremely confusing for people learning JavaScript. To describe the methods container, the language introduces an ambiguous second meaning for “prototype.” If someone says “object1’s prototype is object2,” a reasonable interpretation would be to think that failed lookups on object1 would fall through to object2. So, you might say that amy’s prototype is Car.prototype. But, this is not the relationship that Car has with Car.prototype. In this case, Car is a function object. So, failed lookups on it will fail through to Function.prototype, where all function objects delegate their failed lookups. Car’s relationship with Car.prototype is very different from the one that amy has with Car.prototype. This other relationship reflects the second interpretation of the statement “object1’s prototype is object2.” The relationship is that when Car runs, it will create objects that delegate failed lookups to Car.prototype. In this sense, you might say “Car’s prototype is Car.prototype.” So, saying “amy’s prototype is Car.prototype” means something very different from saying “Car’s prototype is Car.prototype,” even though the sentences look so similar.

[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
console.log(Car.prototype.constructor); // logs the Car function

Every .prototype object has .constructor property pointing back to the function that it came attached to. Therefore, there is a mutual linking between any new function and it’s companion .prototype object. So, Car.prototype.constructor is Car itself. The main use of this feature is figuring out which constructor function built a certain object. All instances of a class delegate failed lookups to their prototype, so, they have the same constructor.

[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
console.log(Car.prototype.constructor); // logs the Car function
console.log(amy.constructor); // logs the Car function

In this case, amy delegates the failed lookup for a .constructor property to Car.prototype, which does have that property. So, amy’s .constructor is reported as Car.

[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
console.log(Car.prototype.constructor); // logs the Car function
console.log(amy.constructor); // logs the Car function
console.log(amy instanceof Car); // logs "true"

The instanceof operator checks to see if the right operand’s .prototype object can be found anywhere in the left operand’s prototype chain.

With that in mind, let’s peek at a functional class code again.

[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
console.log(Car.prototype.constructor); // logs the Car function
console.log(amy.constructor); // logs the Car function
console.log(amy instanceof Car); // logs "true"
[dogs.js]
var Dog = function() {
return { legs: 4, bark : alert };
};
var fido = Dog();
console.log(fido instanceof Dog); // logs "false"

In the functional class style, the instanceof operator won’t work. In this case, fido is a simple object (created with an object literal). So, it just delegates to Object.prototype.

COPYRIGHT © 2018 CodeStates
[library code] 
var Car = function(location) {
var obj = Object.create( Car.prototype );
obj.location = location;
return obj;
};
Car.prototype.move : function() { this.location++; };
[run.js]
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
console.log(Car.prototype.constructor); // logs the Car function
console.log(amy.constructor); // logs the Car function
console.log(amy instanceof Car); // logs "true"

With decorators and with the functional class pattern, we examined the technique of using shared method objects as well as one that duplicates methods. But, the prototypal pattern doesn’t lend itself to the same examination. Function sharing via prototype delegation is the very goal of the prototypal pattern. If methods were defined in the constructor, there’d be no reason for instances to delegate to a prototype.

[Note] This blog post is written based on a lecture from CodeStates.

Thanks for reading! 💕 If you like this blog post, please clap👏

--

--