JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.
In this article, we’ll look at some antipatterns that we should avoid when we’re defining and using functions.
Names and Hoisting
Function declarations ae hoisted. This means that they’re pulled up to the top.
Their definition can’t be assigned to variables or properties or appear in invocations as parameters.
For instance, if they’re at the top-level:
function foo() {}
Then they’re available everywhere.
It the function is inside a function as follows:
function local() {
// local scope
function bar() {}
return bar;
}
Then bar
is returned when we called local
, so that it’s not available everywhere.
Function’s name Property
To get the name of the function, we can use the name
property of the function.
For instance, if we write:
function foo() {}
Then we can check the name as follows:
console.log(foo.name);
However, if we have the following:
let bar = function foo() {}
Then bar.name
is still 'foo'
.
This also works with arrow functions. So if we write:
let bar = () => {}
Then bar.name
is still 'bar'
.
Function Hoisting
Functions declarations are hoisted.
So we can define a function declaration anywhere and call it.
For instance, we can write:
foo();
function foo() {
console.log('foo');
}
And 'foo'
will be logged.
However, if we the following function expression:
bar();
var bar = function() {
alert('bar');
};
Then we get bar
is not a function error.
This is one reason we shouldn’t use var
to declare variables.
var
only hoists the variable, but not its value.
This is a major source of confusion for many people looking at JavaScript apps.
Callback Pattern
Functions are objects, so we can pass them into other functions as parameters.
For instance, we can write the following:
function foo(callback) {
// ...
callback();
// ...
}
or:
const foo = (callback) => {
// ...
callback();
// ...
}
callback
is a function that can be called.
A callback can be a standalone function or a method of an object.
For instance, we can write:
const person = {
name: 'joe',
getName() {
console.log(this.name);
}
};
const foo = (callback) => {
callback();
}
foo(person.getName);
We don’t get 'joe'
logged.
Instead, we should write:
const person = {
name: 'joe',
getName() {
console.log(this.name);
}
};
const foo = (callback, obj) => {
callback.call(obj);
}
foo(person.getName, person);
We need the call
method to call the callback with the object that we want to set as the value of this
inside the callback function.
call
lets us change the value of this
and call it with arguments that we want.
We may also want to check if the value passed in is actually a function.
To do that, we can use the typeof
operator to do that.
For instance, we can write:
const foo = (callback, obj) => {
if (typeof callback === "function") {
callback.call(obj);
}
}
This will make sure that callback
is actually a function before calling it.
Asynchronous Event Listeners
Async event listeners are used everywhere in JavaScript.
Therefore, we should be aware of them.
The callbacks passed in there are called in an indeterminate amount of time. For instance, when an event is triggered.
For instance, we can write:
document.addEventListener("click", console.log, false);
to log the clicks events triggered on the page.
We can replace 'click'
with other events like 'keypress'
, 'mouseover'
, 'mousemove'
, and many others.
Timeouts
Async callbacks are also used by timers.
So setTimeout
and setInterval
all take callbacks which let us call them in when the given amount of time in milliseconds has elapsed.
For instance, we can write:
setTimeout(() => console.log('time is up'), 500);
And we get that string logged in 500ms.
For setInterval
, we can do the same thing:
setInterval(() => console.log('time is up'), 500);
Then the string will be logged every half a second.
Conclusion
Functions are first-class in JavaScript, which means that they’re treated like any object.
They have properties and can be passed into other functions as arguments.
This is why we can have callbacks in JavaScript. We can pass in a function and call it inside another function.