Master binding rules and write advanced code
Photo by Tamarcus Brown on Unsplash
In programming, everything has a structure and a set of rules. There is a certain way to declare variables and an entirely different way to run loops.
The more you know about these syntactical rules, the better programmer you are.
There are such rules to the infamous ‘this’ keyword and binding in JavaScript as well.
If you have been working with JS for a while, you must have noticed the use of the ‘this’ keyword.
It is one of the hardest aspects of the language, mainly because to understand it completely you need to grasp many low-level details.
To get started with ‘this’, you need to first learn what is Lexical Environment and Execution Context.
I won’t blurt out jargon that, although technically right, end up confusing readers.
Lexical Environment
Every time a piece of your code is executed(run) by the JavaScript engine, it stores the variables that you have declared and used in that piece of code in a special store called Lexical Environment.
This environment is created whether you run a function or the entire. But what if you are running a function that uses a global variable like shown below?
var age=10;
function printAge(){
console.log(age);
}
printAge()
Here ‘age’ is a global variable and we are printing it in the function printAge()
. So what if we want to execute only the function and not the rest of the code? How will Lexical Environment know the value of variable ‘age’?
Lexical Environment has two components.
Environment Record- Place where the variables and functions declarations are stored.
Reference to Outer(Parent) Environment- This is where the references of the global variables are stored.
Execution Context
As the name suggests, the Execution Context is an environment created when the code is executed. It has references to everything that facilitates the running of code, including allocating memory.
It even has a reference to the lexical environment that has all the variable values.
In other words, it contains the current evaluation state of the code.
Execution contexts are managed in a stack, that is, there can be many such environments operating independently.
You can find a more elaborate explanation here but the knowledge stated above is sufficient for us to understand ‘this’.
So, what is ‘this’?
The keyword ‘this’ refers to the object the function belongs to. I know it can be confusing, thus we will be again using examples to understand.
var age = 12;
var bodyProps = {
age: 10,
logAge: function () {
console.log('Inside nested function:', age);
console.log('Inside nested function using this:', this.age);
},
statement: console.log('Inside statement function:', this.age),
};
bodyProps.logAge();
console.log('Outside function:', age);
Output:
Inside statement function:12
Inside nested function:12
Inside nested function using this:10
Outside function:12
As you can see, the definition of ‘this’ will become much more clear with this code snippet above.
Whenever we have used ‘this’, we are getting a reference to the object the function belongs to.
In our case, the object is the global scope which has an ‘age’ variable with value 12.
The lexical environment of the variable bodyProps
contains the ‘age’ variable value as ‘10’ however when we write this.age
it refers to the object(in our case) to which the function belongs and uses the value of that ‘age’ variable( that is, value 10).
Simply, the keyword ‘this’ refers to the object the function belongs to.
This is a very simple example of using ‘this’. However, mastering this concept can allow you to:
Reuse code(variables,objects,functions etc)
Focus on the right methods
We can apply the same principle of ‘this’ to reuse complex functions. This allows us to follow the D.R.Y rule( Don’t Repeat Yourself) to write cleaner, organized code.
To achieve this, we use something called Binding. Binding helps us associate an object(which has ‘this’ keyword) to a function to get different results. This will become more clear when I explain the 5 binding rules.
Binding is an advanced topic and it is made possible with the correct usage of ‘this’.
1. Implicit Binding
Implicit binding covers the most common use-case of the ‘this’ keyword and is also fairly straightforward.
In fact, the code snippet which I have used earlier perfectly encapsulates the implicit binding.
However, for the sake of simplicity, I will explain implicit binding using a different code snippet.
function someFunc(obj) {
obj.printName = function () {
console.log(this.name)
}
}
var user = {
name: 'foo',
getName: function () {
console.log(this.name);
}
};
var user1 = {
name: 'bar',
getName: function () {
console.log(this.name);
}
};
user.getName(); //=> foo
user1.getName();//=> bar
someFunc(user);
someFunc(user1);
user.printName();//=> foo
user1.printName();//=> bar
The ‘this’ here is bind to the user object. Thus it provides a reference to variables declared inside the ‘user’ variable.
But what’s interesting is that we have attached a function printName
to our ‘user’.
The same applies to our ‘user1’ variable as well.
Therefore, we can safely say that when we execute user.printName()
the function printName()
becomes bound to the ‘user’ variable. The same applies to the ‘user1’.
In other words, ‘this’ binds to what’s to the left of the dot(.) operator adjacent to a function at execution time.
2. Explicit Binding
Explicit binding is useful to call functions that are outside the scope of the object.
To understand this concept, we need to know the Execution Context which I have explained above. Each Execution Context operates independently and each program can have several of these contexts behaving like a stack.
But what if we want to access something from a different execution context? How will ‘this’ know this?
This is where Explicit Binding comes into action.
In implicit binding, we have access to the function inside the execution context but in explicit binding, we have access to the function outside the execution context.
For the explicit binding, JavaScript offers us three functions: call()
, apply()
and bind()
.
The ‘call()’ method:
In the call()
method, the context with which the function has to be passed as an argument.
let getBrand = function() {
console.log(this.brand); //=> Samsung
}
let phone = {
name: 'Galaxy S20',
brand: 'Samsung'
};
*
*getBrand.call(phone);
Here the this
catches the reference to whatever is passed as a parameter in the call()
method.
Hence, we have a reference to the ‘name’ and ‘brand’ properties of the ‘phone’ variable and we have managed to associate the getBrand()
function by using the call()
method.
The ‘apply()’ method:
The apply()
method covers the shortcomings of the call()
method.
As shown above, the call() method takes arguments but if we try to pass an array as an argument, we need to manually pass each index of the array as a parameter as shown below:
let getBrand = function(name1,name2) {
console.log(this.brand); //=> Samsung
console.log(name1); //=> S10
console.log(name2); //=> Note 9
}
let phone = {
name: 'Galaxy S20',
brand: 'Samsung'
};
var nameList=['S10','Note 9'];
getBrand.call(phone,nameList[0],nameList[1]);
The apply()
method behaves entirely the same as the call()
method except for the fact that it can take an array as a parameter.
let getBrand = function(name1,name2) {
console.log(this.brand); //=> Samsung
console.log(name1); //=> S10
console.log(name2); //=> Note 9
}
let phone = {
name: 'Galaxy S20',
brand: 'Samsung'
};
var nameList=['S10','Note 9'];
getBrand.apply(phone,nameList);
The ‘bind()’ method:
Like the functions above, bind()
also behaves the same, however, with one key difference — it returns a new function that we can invoke.
let getBrand = function(name1,name2) {
console.log(this.brand); //=> Samsung
console.log(name1); //=> S10
console.log(name2); //=> Note 9
}
let phone = {
name: 'Galaxy S20',
brand: 'Samsung'
};
var nameList=['S10','Note 9'];
var myFun = getBrand.bind(phone,nameList[0],nameList[1]);
myFun();
As you can see, it returned a function that we stored in the ‘myFun’ variable and invoked later.
3. Default Global Window Binding
By default, if ‘this’ is not bound to any of the binding techniques, it defaults to the global object binding.
This will become more clear with the example below:
let getArticle = function(title) {
console.log(this.title);
};
var title = 'JavaScript tutorial';
getArticle();
Here we have declared the global variable ‘title’ before invoking the function getArticle()
which is critical, otherwise you will get ‘undefined’ as output.
Since no binding is applied, the ‘this’ keyword is referencing the global scope, hence it will log the value of the global variable ‘title’ in the function.
4. The ‘new’ keyword
We have discussed the explicit and implicit ways of binding but there is another way of binding.
The ‘new’ keyword can also be used to bind. This is a fairly common one and is used in most languages.
The keyword can be used to create objects as shown below:
let phone= function(price, brand) {
this.price = price;
this.brand = brand;
this.log = function() {
console.log(this.price + ' is price of ' + this.brand);
}
};
let iphone = new phone('Apple', '$1000');
let galaxy = new phone('Samsung', '$800');
iphone.log(); // Apple is price of $1000
galaxy.log(); //Samsung is price of $800
As you can see, using the new
keyword provides a new instance of the same object.
This forms the fabric of object-oriented programming.
Here, the function phone()
is created twice using the ‘new’ keyword and is bound to two different variables ‘iphone’ and ‘galaxy’, which have different properties.
In layman's terms, the function phone()
is invoked with the new keyword and will become bound to the new object created which is ‘iphone’, and then the same would happen with ‘galaxy’.
5. HTML Event Binding
If you have experience with building websites using vanilla JavaScript, then you probably saw this one coming.
Using ‘this’, we can bind HTML events that have some type of event listener such as a click event.
<div onmouseover="this.style.color='teal'">Change color!</div>
In the code above, we have an HTML element with an event listener.
When the event listener catches or listens to the expected event(mouse pointer moved on the element, in our case) then it will execute the function which requires a ‘this’ keyword to get a reference to the HTML element.
This entire click event function is made possible by the binding of the HTML element to the code via the ‘this’ keyword.
Conclusion
JavaScript applications are becoming more widespread than ever before, thus it is wise to know the advanced concepts of this language.
You can find detailed documentation here where you can find usage of ‘this’ with regards to super()
and arrow functions.
Mastering the concept of ‘this’ can be beneficial in any project involving JavaScript, whether it’s server-sided or client-sided. Additionally, it can be vital in coding interviews as well.
Reusing complex functions and focusing on particular methods which are invoked can provide a whole new dimension to your code.