Table of Contents
You don’t know JavaScript. That’s right. My apologies for this quite pretentious title but let’s face it – you don’t fully know JavaScript. No matter what level you are on, or how many years of experience you have, you must have a lot of guts to say you understand this complex, ever-evolving language.
But that’s okay! You and I, and every JavaScript developer are all in the same boat, navigating through the murky waters of this goddamn language. And that’s the beauty of it. Sure, it can be frustrating at times, but that’s just part of the experience. Embrace the confusion, be not afraid to make mistakes and one by one we will unravel the mystery of JavaScript together. And because of that, today I will present you some of the core JavaScript features that confuse the hell out of me, and we will try to debunk them together, shall we?
Table of Contents
Type coercion
Type coercion, or type confusion (I’m kidding, don’t refer to it in scientific papers), is a term used in JavaScript to describe the automatic conversion of one data type to another. It is not a relatively new term, and I think all of us when first getting our head into the language were introduced with this example to show how freakish JavaScript is:
And if you think you understand it by now, “sure, a number adds a string returns a string…”, then try to explain this:
Type coercion is confusing, and it stems from the fact that JavaScript is a weakly typed language. This feature allows for flexibility in data types and will attempt to automatically convert them when necessary, rather than strictly enforcing type rules, which can lead to unexpected results as JavaScript may try to convert data in ways that seem reasonable, but can sometimes result in extreme behavior.
In JavaScript, there are two types of coercion: explicit and implicit. Explicit coercion occurs when you manually convert a value from one type to another using a function or an operator, like the Number() or String() functions. On the other hand, implicit coercion occurs when JavaScript automatically converts a value from one type to another in a certain context, such as when using the + operator to concatenate strings and numbers. To simply put, explicit type coercion means you can control the conversion of type in JavaScript, while in an implicit coercion you trust JavaScript to do the conversion for you (why?). All that being said, type coercion is a powerful feature for writing concise and expressive code, as long as you are aware of the potential for implicit type coercion and to use explicit coercion when necessary to avoid unexpected behavior. This cheat sheet here will guide you through the pain of converting JavaScript types, and always remember: using “===” instead of “==”.
Fun fact: Based on the weird typing convention of JavaScript, some of the language’s enthusiasts create an esoteric programming style that writes JavaScript in only 6 (!) characters. Check out JSFuck with caution since the name is quite self-explanatory, it is f—king mindbending, and when you have mastered the art of type coercion, try writing one similar style in 5 or fewer characters!
Closure
If type coercion is sometimes considered a beginner’s confusion, closure is more of an intermediate one, where it does not feel difficult at first glance but when you delve into the problems, it can be very tricky to master.
Consider the following snippets of code:
As is visible, the outer() function creates a local variable (which states an indisputable fact). The inner() function, though having no local variable of its own, can still access the variables of the outer function, and in the first figure, by executing the inner function inside the outer function, we get the expected result which is the “name” variable being logged in the console. This demonstrates the concept of lexical scoping, which refers to how a programming language identifies variable names within nested functions. When functions are nested, the inner functions can access variables declared in their outer scope. This is a fundamental JavaScript knowledge that I believe most JS developers have dealt with, so far, so good. Now, consider this piece of code:
The code here attempts to do the same thing, however, there is a twist. What makes this code example unique and noteworthy is that the inner() function is returned from the outer function before it is executed. And because of that, at first glance you will think that this code won’t work, since variables that are defined within a function are temporary and only available during the time when the function is running. Since outer() has finished its execution by the time testFunc() is called, we expect it to throw an error since the “name” variable would no longer be accessible. But what have we learnt so far? We Don’t Know JavaScript! This second code turns out to be fine and logs the statement similarly to the first piece of code.
The reason for this behavior is a feature called closure. A closure is the combination of a function and the lexical environment within which that function was declared, including the variables of the outer-nested functions. The closure created in this code example is maintained by the testFunc reference to the inner() function instance, which retains its lexical environment, including the “name” variable. It seems now more confusing doesn’t it, and if you want a more hardcore example of closures, consider this code:
This is an example of a closure scope chain, just to demonstrate how complicated closure could be. You may now ask, why even bother learning all these complex features and will it be any practical to develop real-life JavaScript applications? Well, in fact, it is! Closures play a significant role in determining the scope of variables within a function and also establish which variables are shared among sibling functions in the same scope. Having a clear understanding of the relationship between variables and functions is vital to comprehending the workings of your code, whether you’re using functional or object-oriented programming techniques.
So, the next time you face closure, or complicated nested functions in your quest to conquer JavaScript, be not afraid, since with this article today you have now grasped its core concept and be ready to implement it in a practical situation. Well, theoretically it is anyway…
Fun fact: When I was writing this article, I asked a colleague of mine if he was familiar with the concept of closures. He asked me in return if by closures I meant “the conclusion of an article”. Yo what the f—!
Event loop
Event loop is an advanced concept in JavaScript, so before delving into this problem, let’s warm up (if you are not already in flames after reading the first two parts) with a fun quiz: in what order do you think the console logs out the message?
The first thought would probably be linear: “start” – “setTimeout” – “Promise” – “end”, since the setTimeout() callback function is default to run immediately, and the promise also resolves right after it is called. Well, we will save the answer until after we have talked about what JavaScript’s event loop is.
The event loop is an essential mechanism that supports JavaScript asynchronous programming. Since JavaScript runs in a single-threaded environment, only one block of code can be executed at a time, which poses a challenge because certain operations like network requests or file I/O may take a while to finish, causing the program to stall and become unresponsive. To avoid this type of behavior, the event loop plays a crucial role as a persistent observer to the call stack, which has references to the currently executed functions, and the event queue, which stores a list of events with their respective callback functions. JavaScript first executes all the functions in the main call stack before continuously looping time and again to check the event queue for the pending messages, and pushes the callbacks to the call stack which will be executed orderly.
The event queue can also be further divided into two branches: macrotasks and microtasks. Macrotasks are queued in the task queue and executed one at a time, in a first-in-first-out (FIFO) order. Common macrotasks include I/O events, timers, and rendering. On the other hand, microtasks are typically used for tasks that need to happen after the current task completes, but before the user can interact with the page. Examples of microtasks include promise callbacks, mutation observer callbacks, and queueMicrotask function calls. Because of this, the event loop, when monitoring pending events in the queue, first looks into and resolves the unfinished microtasks, before doing the same to the macrotasks.
Now, let’s get back to the question posed at the beginning of this section and analyze it. On the first run, the event loop checks the main call stack, which contains the two logging methods. Although the setTimeout is default to 0ms and the promise is resolved immediately, they do not belong to the call stack, hence they will not be checked on the first run. Afterwards, the event loop checks the event queue again, beginning with the microtasks, which contains the promise callback, and executes it. Finally, it loops again and checks the macrotasks, resolving the setTimeout. Therefore, the logged order will be:
As confusing as it is, event loop is an extremely powerful tool in JavaScript to handle asynchronous events, and by mastering this technique you can believe in yourself that no JavaScript code ever can harm you (I immediately regret saying this but… let’s be positive for now).
Conclusion
Today we have talked about three very confusing features of JavaScript, ranging from different expertise levels. Obviously, this article cannot talk about all the problematic JavaScript functionalities, or else I might have to turn it into a book, which is INSANE because there is not just a book, but a series of 6 (!) books just to deal with this language. This is to prove that, whether you are a novice who has just toe-stepped into the world of web development, or a tech guru who has had years of experience in JavaScript, this language can still manage to piss you off, make you shout at the screen or cry uncontrollably worrying about your coding capabilities.
All that being said, well, you cannot say that JavaScript is not beautiful in its own way. It is like your girlfriend, how unpredictable she is, how she manages to get on your nerves time and again, you just can’t stop loving her. The beauty of JavaScript is in its ability to create stunningly complex and sophisticated web applications with just a few lines of code; it is the Picasso of programming languages – a beautiful mess that somehow just works. So, together, let’s embrace the madness and let JavaScript take you on a wild ride.
AnCX