Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
What are decorators and how are they used in JavaScript?
In this tutorial, you will learn about JavaScript decorators and get to know about their inner workings and uses.
What are Decorators in JavaScript?
The word decorator means combining a set of code or program with another or can be said as wrapping a function with another function to extend the functionality or working of the function. Decorator can also be called as decorator function.
Developers have been using this decorator term in other languages like Python, C# and now JavaScript has also introduced the decorator pattern.
Function Decorators
In JavaScript functions behave like objects so they are also known as first-class objects, this happens because we can assign the function to a variable or return the function from the function or can pass the function as a parameter to a function.
Example: Function as Variable
const func_variable = function() {
console.log("Hey there");
}
func_variable()
Hey there
Example: Passing Function to Another Function
// MainFunction function takes a function as a parameter
function MainFunction(func) {
func()
console.log("Main function")
}
// Assigning function to a variable
var argumentFunction = function() {
console.log("passed function")
}
//passing argumentFunction function to MainFunction
MainFunction(argumentFunction)
passed function Main function
Example: Returning a Function by Another Function
function SumElement(par1, par2) {
var addNumbers = function () {
result = par1 + par2;
console.log("Sum is:", result);
}
// Returns the addNumbers function
return addNumbers
}
var PrintElement = SumElement(3, 4)
console.log(PrintElement);
PrintElement()
[Function: addNumbers] Sum is: 7
Higher Order Functions
Higher order functions are functions that take a function as a parameter and after performing some operations return the function. These are the foundation of the decorator pattern in JavaScript.
Example: Basic Higher Order Function
// higher order function
function PrintName(name) {
return function () {
console.log("My name is", name);
}
}
// output1 is a function, PrintName returns a function
var output1 = PrintName("Kalyan")
output1()
var output2 = PrintName("Ashish")
output2()
My name is Kalyan My name is Ashish
The Problem with Classes
You might be thinking that we already have decorators as higher-order functions then why do we need decorators separately? When classes came in JavaScript, higher-order functions became problematic with class methods due to the this binding issue.
Example: Issue with Higher Order Function and Classes
// higher is a decorator function
function higher(arg) {
return function() {
console.log("First " + arg.name)
try {
arg() // Decorator function call
} catch (error) {
console.log("Error:", error.message)
}
console.log("Last " + arg.name)
}
}
class Website {
constructor(fname, lname) {
this.fname = fname
this.lname = lname
}
websiteName() {
return this.fname + " " + this.lname
}
}
var ob1 = new Website("Tutorials", "Point")
// Passing the class method websiteName to the higher order function
var PrintName = higher(ob1.websiteName)
PrintName()
First websiteName Error: Cannot read properties of undefined (reading 'fname') Last websiteName
Here what happens is when higher function is called then it calls its parameter which is a member function of class as websiteName function. As the function websiteName is called from outside class context, the value of this is undefined inside the websiteName function.
Solution: Preserving Context
To solve this we will also pass the object of Website class which will preserve the value of this.
// higher is a decorator function
function higher(obj, arg) {
return function() {
console.log("Execution of " + arg.name + " begin")
let result = arg.call(obj) // Decorator function call with proper context
console.log("Result:", result)
console.log("Execution of " + arg.name + " end")
}
}
class Website {
constructor(fname, lname) {
this.fname = fname
this.lname = lname
}
websiteName() {
return this.fname + " " + this.lname
}
}
var ob1 = new Website("Tutorials", "Point")
// Passing the class method websiteName to the higher order function
var PrintName = higher(ob1, ob1.websiteName)
PrintName()
Execution of websiteName begin Result: Tutorials Point Execution of websiteName end
Here inside PrintName function we call arg (which is websiteName function) using the call function which invokes the websiteName function with the correct this context from the Website class object, so it works without any error.
Conclusion
Decorators in JavaScript are powerful higher-order functions that wrap and extend functionality. When working with classes, proper context binding using call() or bind() is essential to maintain the this reference.
