This post covers Stage 1 proposal JavaScript Decorators. If you'd like to learn more about Stage 1 vs. Stage 2 proposal JavaScript Decorators, I recommend watching Decorators in Depth by Marco Otte-Witte.
Let's say we have the following class:
class Foo {
bar() {
return this.baz().join(', ');
}
baz() {
let numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i);
}
return numbers;
}
}
We want to figure out how long it takes for bar
to execute. One way to do this is to modify bar
with console.time()
and console.timeEnd()
:
class Foo {
bar() {
console.time();
let result = this.baz().join(', ');
console.timeEnd();
return result;
}
baz() {
let numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i);
}
return numbers;
}
}
With JavaScript Decorators, we can add this functionality without modifying bar
at all.
First, we'll add the decorator above our bar
method:
class Foo {
@captureTime
bar() {
return this.baz().join(', ');
}
baz() {
const numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i);
}
return numbers;
}
}
Decorators use a special syntax that starts with an @
sign followed by the name of a function.
Let's create our captureTime
decorator function:
function captureTime(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.time(name);
const result = original.apply(this, args);
console.timeEnd(name);
return result;
};
}
Decorators are just functions that receive information about the function being decorated. Our captureTime
decorator function will receive the following parameters:
target
: Theprototype
of the class that the decorated method belongs to (Foo.prototype
)name
: The name of the decorated method in the class (bar
)descriptor
: A descriptor object, which is the same descriptor object that would be passed toObject.defineProperty
The decorated function can be accessed from the descriptor object at descriptor.value
. Here, we replaced the decorated function with one that adds in time logging.
Let's say we want to allow developers the option to specify a label for console.time()
and console.timeEnd()
instead of using the method name (bar
in this example). We can modify our decorator so that it receives a label as an argument:
class Foo {
@captureTime('my custom label')
bar() {
return this.baz().join(', ');
}
baz() {
const numbers = [];
for (let i = 0; i < 100; i++) {
numbers.push(i);
}
return numbers;
}
}
Then, we can modify our decorator function like so:
function captureTime(label) {
return function (target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.time(label || name);
let result = original.apply(this, args);
console.timeEnd(label || name);
return result;
};
};
}