Introduction
More often than not, we are obsessed with writing concise code. Who wouldn't, right? Concise code is short code that is easier to take in and usually more readable. It is what separates quick-and-dirty code from elegant code. The key word here is elegant. Using shorter and vaguer variable names at the expense of readability just to achieve "concise code" is indeed not concise code. Rather, it is minified gibberish more than anything else.
As developers, we strive to write such code whenever possible. This is why JavaScript has received a huge facelift over the years. To put into perspective how much JavaScript has changed, there was a time not so long ago before ES6 (or ES2015 if you're edgy) when it was mandatory to write the word function
to define a function, may it be anonymous or named. For example, the code below attaches a click listener (anonymous function) to an HTML element with an ID of veryNiceExample
. For simplicity, the listener then logs the MouseEvent
object to the console.
// Using "var" for added immersion
var element = document.getElementById('veryNiceExample');
// Attaches listener
element.addEventListener('click', function(event) {
console.log(event);
});
With the introduction of ES6, the whole JavaScript community went crazy for arrow functions. We can now do the same thing in a shorter syntax.
// Using "const" for added immersion
const element = document.getElementById('veryNiceExample');
// Attaches listener
element.addEventListener('click', event => {
console.log(event);
});
If it wasn't short enough already, clever folks began utilizing the implicit return feature of arrow functions to push the limits even more. Implicit returns can then be applied in the code example. Although console.log
returns nothing, an implicitly returned arrow function can still be used in this case since it is just a single-purpose function where its return value is not really used for anything.
// Using "const" for added immersion
const element = document.getElementById('veryNiceExample');
// Attaches listener
element.addEventListener('click', event => console.log(event));
Functions are also objects
In JavaScript, everything is an object. Unless an object is created via Object.create(null)
, everything inherits from Object
since it is the last link in the prototype chain. Functions are no exception to this rule. Even primitive data types are objects. To stress this point, all data types (except Symbol
s) have object wrappers. By that, I mean it is possible to instantiate a primitive as an object by calling its constructor with the new
keyword.
DISCLAIMER: For performance reasons, it is not recommended to use object wrappers. This is for demonstration purposes only.
const primitiveString = 'This is a string.';
const wrapperObjectString = new String('This is a string.');
console.log(typeof primitiveString); // 'string'
console.log(typeof wrapperObjectString); // 'object'
Since JavaScript treats functions like objects, it is possible to store functions as values in variables.
// Using "const" for added immersion
const element = document.getElementById('veryNiceExample');
// Stores function through declaration
function handler(event) {
console.log(event);
}
// Attaches listener
element.addEventListener('click', handler);
It is worth noting that handler
is different from handler()
. The variable handler
returns the value it stores. In this case, the value it stores is the actual definition of the function. On the other hand, handler()
executes the function stored in handler
and returns the necessary values. In this case, handler
(the definition) does not explicitly return
a value. Therefore, if handler
is executed, handler()
returns undefined
.
With that said, the code example can now be shortened using the same concept. Since console.log
is essentially a function that accepts an argument, its definition can directly be used as the listener for the mouse click event.
// Using "const" for added immersion
const element = document.getElementById('veryNiceExample');
// Attaches listener
element.addEventListener('click', console.log);
EDIT: As raised by @jburgy in his comment, one has to be aware of all the parameters of a function. Some parameter conflicts may arise if one is not careful such as the case with the code below. See the full discussion to see why this does not work as expected.
['0', '1', '2'].map(parseInt); // [0, NaN, NaN]
Catching Promises
With the previous example, it may seem pointless to even bother with considering functions as objects. However, this concept can prove to be useful in the context of promises, where callback functions are ubiquitous.
During the prototyping stage of any JavaScript application, it is understandable to write quick-and-dirty code. For fast debugging, rejected promises are often handled by logging the errors. As an example, the code below fetches data from the main endpoint of the GitHub REST API v3 and logs the received data as JSON. In case of any errors, the catch
accepts console.log
as its argument. That way, it also logs the Error
object.
fetch('https://api.github.com/')
.then(res => res.json())
.then(console.log)
.catch(console.log);
Despite the code above being syntactically legal, it is still common to see one-line arrow functions (or even normal functions) wrapping other functions. In turn, these one-line wrapper functions are unnecessarily passed in as arguments. For instance, consider the following lines of code.
fetch('https://api.github.com/')
.then(res => {
return res.json();
})
.then(function(data) {
console.log(data);
})
.catch(err => console.log(err));
The two examples do the same operations and yield the same results, but the former is simply more concise and elegant. In contrast, the latter is outright cumbersome and difficult to read. Although it is unlikely that such terribly written code exists (especially in a professional setting), the exaggeration is meant to prove the point.
As an added bonus, negligibly less memory is taken up by the program since the JavaScript interpreter/engine no longer needs to store unnecessary functions into memory.
Conclusion
It never hurts to make code more concise. To write such code, one must always remember that functions, even the built-in ones, are simply values that can be passed into other functions as arguments. That is the basis of callback functions after all. Of course, it is more important to find the balance between elegance and readability. It really just depends on the situation as do most things in life.
In conclusion, thinking more critically about functions can save a few lines of code... and the sanity of a code reviewer.
Top comments (26)
You do have to be careful with things like
Oh, wow. This is indeed weird. I didn't know this was an outlier.
Why does this happen? Are there any other outliers I should be aware of so I could update the article to mention them?
That one is a side effect of how the combination of
Array.prototype.map
andparseInt
work - the former calls it's argument with(value, index, array)
repeatedly, parseInt expects(value, base)
where2 <= base <= 36
, or it returns NaN (ecma-262 1e, 15.1.2.2 "parseInt(string, radix)").Most of the array iteration methods (forEach, map, every, some) pass 3 arguments; I believe reduce passes 4.
Oh, I see now. There is a conflict between the two parameters (
index
andbase
). Since the code you mentioned returns[0, NaN, NaN]
, why does it return0
in the "zeroth" element of the array? What even is a base 0 number to JavaScript?As I experimented on passing in
0
as an argument for thebase
parameter ofparseInt
, I found that it works normally. Why would that work? Is it just all in the spec?From MDN (
radix
being the same asbase
with the previously used verbage):Thanks for looking into this! We appreciate your efforts. I'll go make a quick edit to the article now to raise this point.
Ah, thanks for this example! It was fun digging into this.
Except that it's not the same thing.
Taking a more concise syntax without understanding it's implications is going to lead you to bugs in the long term.
I didn't even think about the context of the execution when I wrote this article. That's my mistake on my part. I just wanted to show that one can generally use function definitions* as arguments in the hopes of shortening code just a bit more. Thanks for clarifying and pointing that out, though!
*By functions, I refer to short, simple, single-purpose functions that don't necessarily have significant side effects.
^ I did not even know that this could be shortened the way you suggested. Thanks for explaining this idea of a stored function definition. I'm not quite sure I've got my head wrapper around the idea, but I believe to have a better understanding now.
Thanks! However, as mentioned by jburgy, we do have to be aware of some weird outliers.
You do have to be careful with things like
This is great - but remember there are some gotchas when you start passing
console.log
in as the function argument. If the function you're passing to will take a variadic function (i.e a function that can take a different numbers of arguments), you may get unexpected results.For instance
results in
It really all comes down to being careful with parameters. As useful as this trick is, it can be dangerous if one has not read enough documentation. "With great power comes great responsibility" after all. Thanks for this! I appreciate all the quirks being discussed here in the comments section because even I wouldn't have thought of these quirks.
A useful article thanks but the smug tone is not necessary. "Of course, in a professional setting one would never do such a thing!" Didn't add to the utility or point of what you said, just irritated.
Sorry for that. I didn't mean to sound boastful. For my improvement, how would I have rewritten it?
Don't forget that there are folk of different knowledge levels on dev.to. Maybe focus on the explanation with some descriptive 'colour' from your experience - I think most readers value new insight and want confidence in the person writing about it.
Thank you for the advice! I will definitely be more sensitive with my words in my future posts.
Great article. This is something I tell to my co-workers but they don't understand at all. They still think that a function is a function not that everything in JS is an object and you must use it as it is... an object.
I struggled with the concept myself when I was a beginner in JavaScript. I couldn't understand why primitives and functions had properties and methods even though they weren't "objects" so to speak. I believe the confusion stems from the constant desire to mold JavaScript into another familiar language such as C++ or Java.
An example would be the implementation of ES6 Classes. There is no such thing as a class in JavaScript, yet it was added as syntactic sugar to accommodate those who came from other object-oriented languages. It also allowed for a straightforward interpretation of inheritance. JavaScript only emulates classical inheritance. Under the hood, it still uses prototypal inheritance.
This is true for functions. In object-oriented languages, a function/method exists as a member of a class. When the class is instantiated as an object, the function is merely a method of the instantiated object, and not a standalone object itself; unlike in JavaScript where a function is an "object". To truly master JavaScript, one must accept that JavaScript is not like the other languages. It is its own beast that needs to be tamed without any preconceived knowledge of other languages.
In conclusion, I think your co-workers are hindered by preconceived knowledge. They just have to treat JavaScript as it is, and not as how they want to mold it. But please do take my advice with a grain of salt. I am only with three years of experience in JavaScript myself. Surely there are others who are more qualified than I am to help you and your co-workers.
I think this is fine for little things, but often maintainability wins over conciseness. It is always easier to maintain explicit code.
More often than not, ‘concise’ is just an alias for ‘dense’. And dense code is almost always unmaintainable code.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.