Javascript is based… Object-Based
I had an interview question asking me to explain what call()
, apply()
and bind()
functions do, and what this
means in Javascript. It was a good refresher of what kind of language Javascript is and the crazy things you can do with it. So I’m going to try to answer the question in great detail.
A good place to start is by understanding that Javascript is an Object-Based language which happens to NOT be an Object-Oriented Language. Naturally, when you are a newbie at programming you look at the names Java and Javascript and you are to believe they are related in some way. They are not at all. In fact, Javascript was named as such to ride off the popularity of Java. Not because they are similar.
In Javascript, everything that is not a primitive type is an Object. Therefore, even functions in Javascript are Objects. They are a special type of Object and you can test this in the browser’s console by opening the developer tools [F12].
So why is this important to know? Long story short, inheritance doesn’t work the same in Javascript as it does in traditional OOP languages such as Java or C#. The thing is, Javascript doesn’t have “classes”, only objects.
Since Javascript has no classes, you could define a random object like this:
var Rectangle = {
length: 20,
width: 20,
computePerimeter: function(){
return 2*this.length + 2*this.width;
}
The challenge becomes when we want to create multiple objects which look like this. In OOP, you would use a constructor. In Javascript, you use a function contructor. Which looks like this:
var Rectangle = function(length, width){
this.length = length;
this.width = width;
this.computePerimeter = function(){
return 2*this.length + 2*this.width;
}
And to create multiple Rectangles
, we use the new
keyword:
var babyRectangle = new Rectangle(5, 5);
var longAssMotherFuckingRectangle = new Rectangle(200000, 5);
Turns out, whenever we use this function constructor we end up creating a new function computePerimeter()
for every object made using the Shape
function constructor. This could be a problem if we made thousands of Rectangle
objects. Even worse if we have multiple, BIGGER methods in our function constructor.
Without getting into the whole “protoype” can of worms, functions call()
, apply()
, and bind()
come to the rescue.
Beginning with call()
we can now separate our getPerimeter()
function from our Rectangle
function constructor and use it like such:
var Rectangle = function(length, width){
this.length = length;
this.width = width;
}
var computePerimeter = function(){
return 2*this.length + 2*this.width;
}
var lilRectangle = new Rectangle(2, 3);
var p = getPerimeter.call(lilRectangle);
console.log(p); // 10
apply()
works in a similar way, except it takes an array of arguments if the function requires parameters.
bind()
is a little different, and requires a lil more of an understanding of what this
means in Javascript and the execution context. So I’ll explain both.
Execution Context
Is the scope of which functions are called. If you create a function like this:
var lmao = function(){ console.log("AHAHAHAHA😂😂😂") }
You can then invoked it like this:
lmao();
You are doing 2 things:
- The function you wrote belongs to the global execution context
- You are calling it in the global context
So, when we write a function which belongs to an object like this:
var magicNumber = {
num: 42,
printNumber: function() {
return this.num;
},
};
And invoke it like this:
magicNumber.printNumber();
You are invoking the function printNumber()
in the magicNumber execution context.
‘This’
So things are about to get fucked up (like always when it comes to Javascript). For starters, this
references the execution context. Lets try some shit out using the command-line so we can see the gears working.
Lets see what happens when we call this
inside a function:
What about a function defined inside of an object?
This is all expected behavior. Remember: Javascript is an Object-Based language and we can pass basically ANY object as a function parameter, including function objects. We can do funny business such as this:
var arithmetic = function(x, y, operator){
return operator(x, y);
}
var add = function(x, y){
return x + y;
}
var multiply = function(x, y){
return x * y;
}
arithmetic(2, 3, add); // 5
arithmetic(2, 3, multiply); // 6
Hilarious, in’it? So, what will happen if we pass a function defined in a different object?
var arithmetic = function(x, y, operator){
return operator(x, y);
}
var magicNumber = {
num: 42,
addToMagic: function(x, y){
return this.num + x + y
}
};
arithmetic( 8, 0, magicNumber.addToMagic ); //NaN
NaN
(Not A Number)? This happened because we invoked arithemtic()
in the global execution context, which means that the this
keyword will refer to the global execution context. Unfortunately, num
is undefined
in our global object, which means this function is trying to do: window.num + 8 + 0
. And undefined + Number
equals to: NaN
If num
exists in the global execution context, then it will use that:
One way to fix this issue is to define a function constructor, and store the value of this
inside a private variable within the object so it can later be used:
var MagicNumber = function() {
var that = this;
that.num = 42;
that.addToMagic = function(x, y){
return that.num + x + y;
}
}
Then create an object of type MagicNumber
. See a working example of this technique:
Bind()
Now that we understand this
keyword refers to the execution context, we can use that knowledge to understand what bind()
does.
Here’s what is happening:
- We create a function in the global context
- we bind that function to the
magic
object we created - we execute it in the global execution context, but the value of
this
is referencingmagic
object
The result is a function which, no matter where its invoked will reference the magic
object when calling this
in the function definition.
I’ll be writing more about Javascript explaining other aspects of it such as closures and “prototypal inheritance”.