JavaScript Concepts to Make Your Head Spin: Closures, Currying, and More 🤪
Udit Kumar / February 08, 2022
5 min read • ––– views
Hold onto your hats, folks, because we're about to go on a wild ride through the land of JavaScript. During this journey, we'll cover all the important concepts you need to know to be a JavaScript master.
So, let's get started ✨!
JavaScript prototypes and prototypal inheritance
JavaScript is a prototype-based language. This means that objects in JavaScript have a hidden internal property called [[Prototype]]
(as opposed to classes, which don't). This [[Prototype]]
property is either null
or references another object. That object is called the prototype.
When you try to access a property on an object, and if the object doesn't have that property, JavaScript will look at the object's [[Prototype]]
. If the [[Prototype]]
also doesn't have the property, then JavaScript will look at the [[Prototype]]
of the [[Prototype]]
, and so on. This is called prototypal inheritance.
Let's see this in action:
jsconst person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = 'Udit'; // "name" is a property set on "me", but not on "person"
me.printIntroduction();
// expected output: "My name is Udit. Am I human? false"
In the above example, me
is an object created from the person
object using Object.create()
.
me
doesn't have its own isHuman
property, but since objects inherit properties from their prototypes, me.isHuman
will return the isHuman
property found on person
.
JavaScript closures
A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.
In short, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
Let's see this in action:
jsfunction makeAdder(x) {
// parameter `x` is an inner variable
// inner function `add()` uses `x`, so
// it has a "closure" over it
function add(y) {
return y + x;
}
return add;
}
// `plusOne` gets a reference to the inner `add(..)`
// function with `x` preset to `1`
const plusOne = makeAdder(1);
// `plusTen` gets a reference to the inner `add(..)`
// function with `x` preset to `10`
const plusTen = makeAdder(10);
plusOne(3); // 4 <-- 1 + 3
plusOne(41); // 42 <-- 1 + 41
plusTen(13); // 23 <-- 10 + 13
In the above example, plusOne
and plusTen
are both closures. They share the same function body definition, but store different lexical environments. In plusOne
, x
is 1
, and in plusTen
, x
is 10
.
JavaScript this
The this
keyword refers to the object it belongs to.
It has different values depending on where it is used:
- In a method,
this
refers to the owner object (here method means a function that is a property of an object). - Alone,
this
refers to the global object. - In a function,
this
refers to the global object. - In a function, in strict mode,
this
isundefined
. - In an event,
this
refers to the element that received the event. - Methods like
call()
, andapply()
can referthis
to any object which is passed as an argument.
Let's see this in action:
jsconst person = {
name: 'Udit',
weight: 70,
info: function () {
console.log(`${this.name} weighs ${this.weight} kg.`);
}
};
person.info(); // "Udit weighs 70 kg."
In the above example, this
refers to the person
object that "owns" the info()
method.
JavaScript Currying
Currying is a technique of evaluating function with multiple arguments, into sequence of functions with single argument.
A curried function takes multiple arguments one at a time and returns a new function that takes the next argument until all arguments are passed.
Let's see this in action:
jsfunction curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3)); // 6, still callable normally
console.log(curriedSum(1)(2, 3)); // 6, currying of 1st arg
console.log(curriedSum(1)(2)(3)); // 6, full currying
In the above example, curry()
is a higher-order function that takes a function as an argument and returns a new function. The new function is a curried version of the original function.
Conclusion
Learning JavaScript is a never-ending journey. There are so many concepts to learn and so many things to explore. I hope this blog post helped you understand some of the most important concepts in JavaScript.
That's it for this blog post. I hope you enjoyed it. If you have any questions, feel free to reach out to me on Twitter or LinkedIn. I'd love to hear from you!