Borrowing methods from a function in Javascript

One of the ideals that we as coders are always striving toward is one of making our code DRY. The ‘Don’t Repeat Yourself’ idea is one where if you find yourself writing the same lines of code over and over then there is probably a better way to do what you are trying to accomplish.

During a revisit to Colt Steele’s, ‘The Web Developer Bootcamp’ class on Udemy I noticed that that there was some new (new to me, anyway) material that was focused on some of the stranger parts of Javascript. One of those parts was how you can use methods like .call(), .apply(), and .bind() to ‘share’ code between functions.

This is a pretty complex topic involving the ‘this’ keyword context and multiple functions. Colt’s class does a great job explaining it but I thought I would take a crack at it too. The more you see something, the more it makes sense, right?

Let’s look at the following two functions as an example

function Dog(name, age){
this.name = name;
this.age = age;
this.numWalksPerDay = 2;
}
function Cat(name,age){
this.name = name;
this.age = age;
this.numWalksPerDay = 0;
}

Nothing out-of-the-ordinary here. Just two rather simple constructor functions that can be used to create Dog and Cat objects.

Non-DRY constructor functions.

Creating objects with these is done with the ‘new’ keyword.

Objects created from the previous functions.

The only issue is that we have to repeat the ‘name’ and ‘age’ attributes for each object. Not very DRY. It would be nice if we could somehow use the attributes defined in the Dog function and apply them to the Cat function. That way, they are only defined once but can be used in as many places as necessary. Luckily, this is where methods like .call() come in.

The unique thing about the .call(), .apply(), and .bind() functions is that they are methods on the prototype attribute of an object. If you look at the prototype of an object and dig far enough into its prototype chain, then you will see these methods defined.

The prototype methods on all objects.

These methods also allow you to define the context of the ‘this’ keyword and that is what makes it possible to ‘borrow’ attributes from one function and use them in another.

If we were to just add the Dog function as part of the Cat function, this would not work because ‘this’ would have the wrong context.

The new Cat object doesn’t have the Dog attributes.

However , we can do this if we redefine the context of ‘this’ using one of the prototype methods. This will then change ‘this’ to be the context of the object being created from the constructor. But how do we redefine ‘this’? With ‘this’. Wat?

No, it’s true, in this case in order to define the context of ‘this’ we need to pass the ‘call’ method the keyword of ‘this’ so that the context is set to the object that is being created.

Let’s take this step-by-step. You already know that this example won’t work:

function Cat(name, age){
Dog(name, age);
this.numWalksPerDay = 0;
}

It won’t work because the object being created from the Cat constructor has no idea what Dog is and thus can’t use any of its attributes. Okay, what about using .call() to define the context?

function Cat(name, age){
Dog.call(Dog, name, age);
this.numWalksPerDay = 0;
}

Nope, the result is the same. The .call() method is still just setting the ‘this’ context to be that of the Dog constructor and the new Cat object being created won’t be aware of that.

What needs to be done is to explicitly set the context of ‘this’ to the object that is being created.

function Cat(name, age){
Dog.call(this, name, age);
this.numWalksPerDay = 0;
}

I think of this as being a substitute for the ‘this.name = name; this.age = age’ lines in the original constructor. But rather than writing those same lines again in the Cat constructor, you are looking to the Dog constructor for them.

With the correct ‘this’ context, the Dog attributes are now passed on to new Cat objects.

So, now our two constructor functions look like this:

function Dog(name, age){
this.name = name;
this.age = age;
this.numWalksPerDay = 2;
}
function Cat(name,age){
Dog.call(this, name, age);
this.numWalksPerDay = 0;
}

Since the name and age attributes are shared between these two constructors you can make them DRY by using a prototype method like .call() to reuse code from one constructor to another.