That being said, evolution has its challenges, especially with a language that was not originally conceived to do what it’s currently capable of. This means some bad habits have to carried over a few generations.
Quite some time ago, we as programmers had to be really efficient when it came to how we used our resources since they were pretty limited. We worried a lot about things like processor cycles, memory consumption and leaks, garbage collection, file size and download size.
Just as an example, Windows XP used 128MB of RAM and a fully functioning OS used 128MB of RAM to work. Today we see apps and frameworks that use 128MB of RAM just to do a "hello world." Sometimes we don’t even know why so much memory is needed, and then we start noticing that some libraries or packages are not as performant as you’d hoped. Some would even come with jokes included, like babel-core which comes with an ASCII Art of Guy Fieri just for fun: https://github.com/babel/babel/pull/3641/files
Let’s go back even further and think about the fact that when the entire Super Mario Bros. game was released in 1985, it was only 31 KB in size.
Fast forward 32 years later and we are writing apps that eat up RAM like dogs eat treats, take gigabytes of space and require gigahertz of processing power. What happened?
Short answer: economics. We live in a world where we need to push code faster and deploy more frequently. Taking the time to see where we can save a couple of bytes of space costs money. Plus, resources are now readily available and cheap, so we’re not as meticulous with our code as our colleagues from decades ago used to be.
Since ES5 and ES6 came into the spotlight and more browsers supported them, a plethora of array methods emerged. ES6s cleaner syntax makes it painless to chain codes in order to produce our desired results. The problem lies in how we are chaining.
As an example, consider this function that iterates through an object and filters any user younger than 18 years old and then returns the names of the users not filtered.
The code seems totally legit. It uses two ES5 methods, filter and map, and ES6 arrow functions and object de-structuring. It’s well written and easy to read. Looks great!
Chaining iterators are a common error I flag in my code reviews. Filter and map are iterators and they both get into a loop that cycles n-times, depending on how many items the array has. Perhaps now that there are only three items in the array, it’s not an issue, but when there are thousands or hundreds of thousands then we have a performance issue.
If there were 100,000 items in that array and you filtered half of them, there would still be 50,000 items to iterate over again to retrieve their names. So your program will perform 150,000 iterations rather than the 100,000 iterations it actually needed in order to perform the action.
What would be the correct way of doing this? There are actually two ways, a single loop or using the new reduce method.
The reduce syntax might look less readable, but it does the exact same thing as the single loop, the way we used to do things before ES5 array methods. By the way, I could have written it in a single line, but I split it to make it a bit more readable.
And since they don’t have a scope, you obviously can’t bind a different scope since they always use the scope of their parent.
Let’s check out an example to notice the difference:
Since the arrow function does not have a scope, it inherits the scope of the parent. In this case, the myClass object. Now let’s watch what happens with an anonymous function when we try to bind a different scope:
As you can see, it still pulls the scope from the parent, and now the supposedly new scope we passed to the function is nowhere to be found.
This is especially problematic when working on a modular app which needs to be changing or applying scopes to handlers.
Now don’t get me wrong, I love arrow functions, but the trick is knowing when to use them. So, when should we use arrow functions?
So what do arrow functions have to do with performance? Programmers sometimes forget that arrow functions don’t posses scopes, sometimes the arrow function is within another arrow function, and the scope ends up being something completely unexpected for the programmer. Then the programmer ends up writing a contraption to make the wanted scope accessible to the arrow function. These contraptions somehow tend to stick as a reference to another object being used, so the garbage collection ends up skipping them and in the long run they become a memory leak.
Thanks to broadband internet connections, developers tend to care less about build sizes. They just jam up the entire app in a single page app which weighs several megabytes in size. A basic Angular 1 build with all its modules loaded takes about 1 MB for a single Hello World app unoptimized!
Even with Webpack, Gulp or other task runners compressing and running through the code, most of these libraries are not optimized in a way that these runners can just pick the function needed, so they end up pulling up the full library.
Therefore, we end up with huge libraries in our build that increase the build size just for the one small function we need from them.
For example, pulling the whole jQuery library just to use the Ajax functions.
What’s the solution then? Simple, always prefer native. Sure there are utilitarian libraries like Lodash and Underscore that provide great functions, but many of those are now native.
jQuery for DOM traversing? Not needed! We have document.querySelector now.
Toastr popups? For what? We have desktop notifications, which are cooler!
All those asynchronous functions run on a single thread, waiting to finish and interrupt code execution.
This is a topic for a different article, so let’s get back to it in the future, shall we?