JavaScript Essentials: Often Overlooked Elements of One of The Most Popular Web Development Languages
The first of a 3-part series on JavaScript essentials by SSENSE-TECH
JavaScript is one of the most popular languages for web development nowadays. At SSENSE, we take advantage of the powerful, mature, and stable JavaScript ecosystem. Additionally, JavaScript’s versatility allows us to develop applications that bring great performance and value to the company both in the back-end and front-end. Several of our microservices run on NodeJS and are developed in Typescript.
Despite all the benefits that the language offers, it comes with some quirks and is often criticized for being overly flexible, not strongly typed, and for allowing (and sometimes even enforcing) bad practices. In addition to this, the design of the language itself is awkward for many developers that are used to other languages like Java, where it is more intuitive how to properly implement Object-Oriented Programming (OOP) principles. This, paired with a complexity of specific concepts that I will cover in this series, leads developers to overlook some of the language’s essentials, opting instead for pre-built libraries and modifying as little as they can to make things work. This introduces error-prone workarounds to the parts that they don’t fully understand.
In this JavaScript Essentials series, I will explain some key concepts that are usually misunderstood or ignored. Part 1 focuses on the this
keyword.
WHAT IS this
Just like in other languages, this
refers to the instanced object where any property or method will be searched for when accessed. However, JavaScript has a different set of rules that could be less intuitive. The first thing to keep in mind is: this
is assigned a value at the moment of execution, not at the moment of declaration. This operation is also known as binding.
Another way to picture this, however simplified, is to imagine who is going to call your function at the moment of execution. If no specific object is calling your function, it will be bound to the global object.
This rule seems to be counter-intuitive because developers need to think about the runtime when designing and writing their code. However, there are clear rules that, when understood, make it easier for anyone to use this
instead of finding ways to avoid it.
DEFAULT BINDING
You can think of this as the fallback mechanism when there is no explicit indication of what this
should mean.
Think of this rule whenever a function is going to be invoked as a standalone function; that means, it’s not a method of an object and there is no enforced meaning of this
. For such a scenario, this
will be the global object, unless restrictions apply when running in strict mode
. There are many examples to illustrate the point, a clear example is the callback function passed as argument to the filter
method in an array:
This code will throw an error and it may be hard to figure out the reason, why did the runtime try to find the isPair
method on an undefined
object? First, we need to know that functions are first class citizens, meaning that you can interact with them as if they were any other object, which also means that you can pass them as arguments to other functions. Functions passed as arguments are known as callbacks
, and they are always executed as standalone invocations, which also means that the default binding rule applies and this
will be bound to the global object. The reason why the last line throws is that I ran the code in strict mode
, and access to the global object is restricted.
This is something that goes against the usage of this
in other languages, because we’re using it inside a class, expecting it to refer to the instantiated object.
So, how do we solve this problem?
EXPLICIT BINDING
JavaScript provides three ways of forcing the meaning of this into the function execution: bind
, call
, and apply
. call
and apply
execute the function by forcing the meaning of this
to the object passed as argument:
bind
returns a function that has this
set to the object passed as argument. This binding is one of the possible solutions to the Array.filter
problem stated at the default binding section:
The difference between bind
and call
is that bind
doesn’t execute the function, it builds a function that you can execute later, while call
does execute the function right away.
IMPLICIT BINDING
This type of binding provides a context to the function invocation. It is basically calling an object’s method, and the runtime will understand that the object will be used for any reference to this
inside the method.
new
BINDING
This type of binding can be more intuitive, because it is a way to build objects. In JavaScript, you can build objects from functions called constructor functions and, since this is a function execution, it also needs a this
binding:
In this case, four steps will happen:
- An empty object
{}
is created as soon as the constructor function is called with thenew
operator. - That empty object is bound to that specific function execution, so any reference to
this
inside the function will take effect on that object. - The object is
[[Prototype]]
-linked to the constructor function. - The return value of the function execution is the newly created object.
ARROW FUNCTIONS
This is not just a fancy and shortened way to create functions. Arrow functions have several differences when compared to their regular counterpart, and one of them helps us solve the challenge of inadvertently losing the binding to this
.
The syntax is as follows: a parenthesis that encloses all the arguments passed to the function, a “fat arrow”, and the brackets that will contain the body of the function.
The main difference is that this kind of function doesn’t have its own binding to this
, and it will take it from the context that contains it. This is another way to solve our callback function problem highlighted on the default binding section as we can see below:
In this example, even if the arrow function is still executed as a standalone function, the runtime knows that there is no specific binding to this
and that it has to interpret it as the enclosing context of the function, which in this case is the object that contains it.
Because of these features, this type of function is not suitable for call
, apply
, or bind
.
Arrow functions have other functional and syntactic differences, like the fact that they can’t be used as constructors like regular functions, and that they do not have the prototype
property that allows for prototype
inheritance. Explaining all the other differences is not in the scope of this article. Please refer to the Additional resources and related links section to learn more.
CONCLUSION
JavaScript can be a powerful language given that it has one of the biggest communities and ecosystems. It can be a great fit for almost any project and there is a framework or library for practically everything, and it even has amazing supersets like Typescript for strongly typed code. If you dedicate the time to understand its principles, instead of trying to find ways to avoid the concepts that are not intuitive, you can benefit from an amazing developer experience.
At SSENSE we strongly believe that mastering the concepts of the language and runtime explained in this series of articles brings a lot of value to our product, because we create code that follows best practices, is maintainable, as well as scalable. We have a vast group of developers with a solid JavaScript background, and we’ve learned to understand the quirks of the language to take advantage of this powerful and flexible ecosystem.
Stay tuned for Part 2 of this SSENSE-TECH JavaScript Essentials series, which covers asynchronous execution.
ADDITIONAL RESOURCES AND RELATED LINKS
Editorial reviews by Deanna Chow, Liela Touré, and Pablo Martinez. Want to work with us? Click here to see all open positions at SSENSE!