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.
Returning Functions
Returning functions is possible in JavaScript since functions are just regular objects.
For instance, we can define a function that returns another function as follows:
const foo = () => {
return () => {
console.log('bar');
}
}
We return a function that logs 'bar'
in the function above.
We can call it if we call foo
to return the function. Then we can call it:
const bar = foo();
bar();
Self-Defining Functions
If we assign a function to a variable that already holds a function, then the old function will overwrite the existing one.
For instance, we can write:
let foo = () => {
console.log("foo");
foo = () => {
console.log("bar");
};
};
Then if we call it twice, we’ll get 2 different results:
foo()
foo()
And we’ll get:
foo
bar
in the console log output.
However, it’s probably not very useful in most cases even though we can do it.
Immediate Functions
We can also define immediately invoked function expressions (IIFEs), which are functions that are created immediately and called.
For instance, we can write an IIFE as follows:
(() => {
console.log('foo');
})();
In the code above, we have the anonymous function that’s wrapped in parentheses.
Then we can call it right away by putting parentheses at the end.
This way, we don’t have to create a named function and call it somewhere else.
The pattern for creating an IIFE is as follows:
- Define a function using a function expression or arrow function
- Add a set of parentheses at the end to make the function run immediately.
- Wrap the whole function in parentheses
This is useful because we can have variables that aren’t accessible to the outside.
It also can act as a module in that it can return things inside the function.
Then we can assign the returned values to a variable.
However, now that we have modules, we can use those instead of using IIFEs to hide private variables.
One good use of IIFEs is that we can use them to call async functions immediately after it’s defined.
So we can write:
(async () => {
const val1 = await promise1;
//...
const val2 = await promise2;
//...
})();
This way, we can use async functions without defining a named function.
Parameters of an Immediate Function
Immediate functions can also take parameters like any other function.
For instance, we can add parameters and use them as follows:
((firstName, lastName) => {
console.log(`${firstName} ${lastName}`);
})('joe', 'smith');
The arguments are passed in at the end of the IIFE and run the function with those arguments right away.
So we get ‘joe smith’
from the console log output.
We can also use IIFEs to access global objects in a safe manner.
It lets us access global variables by using this
at the top level.
So we can write:
const global = ((global) => {
return global
})(this);
We pass in this
from the outside, so we can use an arrow function to return the global
parameter.
In the browser, this
should be the window
object.
Returned Values from Immediate Functions
We can return values from IIFEs as we saw before.
For instance, we can write the following:
const result = (() => {
return 1 + 2;
})();
We’ll see 3 assigned to result
since we called the function and returned the value right away.
It’s also useful for storing private data while returning others.
For instance, we can write:
const result = (() => {
let x = 1;
return x + 2;
})();
Then we get the same result. x
isn’t available on the outside of the function.
IIFEs can also be used to define object properties.
So we can write something like:
const obj = {
message: (() => {
const who = "me",
what = "call";
return `${what} ${who}`;
}()),
getMsg() {
return this.message;
}
};
Then we get the message
is 'call me'
since the value of the template, the string is returned by the IIFE.
getMsg
is the same because this
is obj
and message
is 'call me'
.
Conclusion
We can use IIFEs to store private data. Also, we can use them to return what we want.
They’re also handy for running async functions without defining a named function.
They take parameters like any other function.
Functions can also return other functions.