DEV Community

Ruben Gabrielyan
Ruben Gabrielyan

Posted on

Stop Writing JavaScript Like This

Most of us are used to writing JavaScript code for a long time. But we might not have updated ourselves with new features which can solve your issues with minimal code. These techniques can help you write clean and optimized JavaScript Code. Today, I’ll be summarizing some optimized JavaScript code snippets which can help you develop your skills.

1. Shorthand for if with multiple || conditions

if (fruit === 'apple' || fruit === 'orange' || fruit === 'banana' || fruit ==='grapes') {
    //code
}
Enter fullscreen mode Exit fullscreen mode

Instead of using multiple || (OR) conditions, we can use an array with the values and use the includes() method.

if (['apple', 'orange', 'banana', 'grapes'].includes(fruit)) {
   //code
}

Enter fullscreen mode Exit fullscreen mode

2. Shorthand for if with multiple && conditions

if(obj && obj.address && obj.address.postalCode) {
    console.log(obj.address.postalCode)
}
Enter fullscreen mode Exit fullscreen mode

Use optional chaining (?.) to replace this snippet.

console.log(obj?.address?.postalCode);
Enter fullscreen mode Exit fullscreen mode

3. Shorthand for null, undefined, and empty if checks

if (first !== null || first !== undefined || first !== '') {
    let second = first;
}
Enter fullscreen mode Exit fullscreen mode

Instead of writing so many checks, we can write it better this way using ||
(OR) operator.

const second = first || '';
Enter fullscreen mode Exit fullscreen mode

4. Shorthand for switch case

switch (number) {
  case 1:
     return 'one';
  case 2:
     return 'two';
  default:
     return;
}
Enter fullscreen mode Exit fullscreen mode

Use a map/ object to write it in a cleaner way.

const data = {
  1: 'one',
  2: 'two'
};
//Access it using
data[num]
Enter fullscreen mode Exit fullscreen mode

5. Shorthand for functions with a single line

function doubleOf(value) {
  return 2 * value;
}
Enter fullscreen mode Exit fullscreen mode

Use the arrow function to shorten it.

const doubleOf = (value) => 2 * value
Enter fullscreen mode Exit fullscreen mode

Buy me a coffee

Discussion (54)

Collapse
akashkava profile image
Akash Kava • Edited on

Do some performance testing of both codes in first example and see the difference

On both iOS Safari/Chrome/Firefox it is 90% slower, on Desktop except chrome, Safari and Firefox are 90% slower.

Collapse
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan • Edited on

These kinds of performance optimizations don't matter. At all.

Both code samples have a time complexity of O(1). No, the second code sample is not O(n). And no, testing with millions of array items is not how you benchmark performance in these kinds of scenarios.

This is a classic case of tunnel-visioning into premature optimization. You will never, ever have so many array elements on the front-end that there's a noticeable performance difference between these two code samples. Moreover, if you ever do have that many elements (god forbid), the second code sample is still better and more maintainable.

I don't understand why JS devs are so obsessed with meaningless performance metrics and benchmarks.

Collapse
peerreynders profile image
peerreynders • Edited on

This is a classic case of tunnel-visioning into premature optimization.

It's also exhausting how often claiming "premature optimization" is used to justify sloppy thinking.

Don’t pessimize prematurely:

Easy on yourself, easy on the code: All other things being equal, notably code complexity and readability, certain efficient design patterns and coding idioms should just flow naturally from your fingertips and are no harder to write than the pessimized alternatives. This is not premature optimization; it is avoiding gratuitous pessimization.

Early optimization is the root of all evils," Knuth said, but on the other hand, "belated pessimization is the leaf of no good," according to Len Lattanzi.

Alexandrescu, Andrei. "Modern C++ Design: Generic Programming and Design Patterns Applied", Small Object Allocation, p.77, 2001.

Stated differently:

Don't fetishise the quick-and-dirty. Sometimes you'll save yourself a world of hurt by doing The Right Thing in the first place.

Rich Hickey:

Programmers know the benefit of everything and the tradeoffs of nothing.

V8 is an amazing piece of technology but its heuristics are so complex that the smallest thing can derail JavaScript performance, so it's very inconsistent — at least when compared to languages that are compiled prior to deployment. From that perspective it makes sense to feed it code where is doesn't have to "guess" too much.

WebAssembly for Web Developers (Google I/O ’19):

Both JavaScript and WebAssembly have the same peak performance. They are equally fast. But it is much easier to stay on the fast path with WebAssembly than it is with JavaScript. Or the other way around. It is way too easy sometimes to unknowingly and unintentionally end up in a slow path in your JavaScript engine than it is in the WebAssembly engine.

And finally in the face of the continued proliferation of low-power / low-end / low-spec / small-core devices it seems foolish to rely on the JIT having access to the necessary CPU cycles to reliably optimize the code in a reasonable amount of time at runtime (perhaps we need profiling transpilers - but establishing representative "real-world" operational profile(s) can be a challenge in itself).

Meanwhile the self-improvement industry is pushing for aggregation of marginal gains - which is all about eliminating pessimization.

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

In this case, though, there isn't any sloppy thinking or carelessness. You could use a set or object, but there is no tangible performance benefit for small inputs like in op's example.

Thread Thread
peerreynders profile image
peerreynders

In the meantime real-life users have to deal with this.

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan
  1. If I had to choose between making my code readable and optimizing for a particular browser's proprietary JS engine, I would always choose the former. Fixed input size? Check. Unlikely to run several times in a short period of time? Check.

  2. Benchmarks should be treated as a tool for comparing the theoretical performance of two algorithms under stress conditions. You're never going to run this snippet of code 1) on lots of inputs (n -> infinity in Big-O), or 2) millions of times within the span of a few seconds. If you plan to do either of those things, then your code has much bigger problems that you need to worry about.

So many JS articles have people in the comments section bickering about performance and citing arbitrary benchmark metrics as proof. I blame it on Leetcode and interviews. Developers are more obsessed about micro-optimizations than they are about writing good, understandable code.

Thread Thread
peerreynders profile image
peerreynders • Edited on

If I had to choose between making my code readable and optimizing for a particular browser's proprietary JS engine, I would always choose the former.

Attitudes like that play right into Apple's hands if you believe that the "state of Safari" reflects a desire to deemphasize the importance and relevance of the web.

Then for a web professional that mindset is equivalent to "sawing off the branch you're sitting on".

There is a difference between "efficient coding idioms" and "premature optimization" and those two should not be confused.

PS: Then again maybe it's Chrome that is the real problem: Breaking the web forward.

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

So your solution is to treat Safari as the least common denominator for web performance and... play right into Apple's hands?

Thread Thread
peerreynders profile image
peerreynders • Edited on

Saying it works on Chrome is the web equivalent of "it works on my machine" — and fundamentally fails to recognize the nature of the web — there is no web platform, there’s an immensely varied collection of web platforms so lots of common wisdom from the backend doesn't directly apply.

And how is giving Safari users an adequate UX playing into Apple's hands? Apple doesn't benefit if you actually poly- and ponyfill Safari's inadequacies as you help to keep the web working. Most iOS user's don't realize that iOS Chrome is just Safari with a paint job so if the web is doing poorly on their flagship device it must be the web's fault, not Apple's.

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

Okay, so now we've gone from "this algorithm is easier to read than this other algorithm" to "that attitude is like 'it works on my machine'"?

This is precisely what I'm talking about. You've completely lost sight of what matters and have gone off on a tangent.

giving Safari users an adequate UX

Yes, I'm sure Safari users are anxious to see the performance benefits that your revolutionary if-statement-vs-array-includes tirade will yield.

Thread Thread
peerreynders profile image
peerreynders • Edited on

You've completely lost sight of what matters and have gone off on a tangent.

The issue is the difference between "efficient coding idioms" vs. "premature optimizations".

If your application has to only work over a cooperate intranet with a strictly standardized web browser it's easy to determine what works and what doesn't. Over the public web matters are much more complicated and much less predictable especially when JavaScript is involved. So the blanket

These kinds of performance optimizations don't matter. At all

without consideration of any type of context is entirely inappropriate. For example the iteration mechanism that is consistently performant across the majority of situations is the plain for loop. Does that mean bad things will happen if you prefer array functions? Not likely but context matters. As always — it depends.

The other issue is that current benchmarks don't typically cover memory pressure.

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

Gonna just block you. Judging from your post history, it seems that arguing with people is all that you really do on Dev.to. Weren't you the pink gravatar guy? I vaguely recall seeing your name floating around in basically every comments section.

Collapse
pedro profile image
Pedro M. M. • Edited on

I agree that this is premature optimization and for a simpler reason: This use case can't be a hot-path because you always will have a small list and even in the case that it is a hot-path you will end up using a database if the list grows too much, so by that time you won't be using hard-coded variables anyway.

But I'd like to point out (not for you, but anyone reading this) that 'includes' itself time complexity IS linear O(n), as described in the spec (Step 10, tc39.es/ecma262/#sec-array.prototy... ):

Let O be ? ToObject(this value).
2. Let len be ? LengthOfArrayLike(O).
3. If len is 0, return false.
4. Let n be ? ToIntegerOrInfinity(fromIndex).
5. Assert: If fromIndex is undefined, then n is 0.
6. If n is +∞, return false.
7. Else if n is -∞, set n to 0.
8. If n ≥ 0, then
    a. Let k be n.
9. Else,
    a. Let k be len + n.
    b. If k < 0, set k to 0.
10. Repeat, while k < len,
    a. Let elementK be ? Get(O, ! ToString(𝔽(k))).
    b. If SameValueZero(searchElement, elementK) is true, return true.
    c. Set k to k + 1.
11. Return false.
Enter fullscreen mode Exit fullscreen mode

It just happens to be constant in this case because our data initialization: the array is hard-coded, so it is a fixed constant (as you noted in your article), but the algorithm of the method by itself is O(n) in the scope of the algorithm (and because the worst case for the algorithm is not fixed data initialization I would still say that this is O(n) in a wider scope IMO, just in case we change our data initialization in the future which is not that uncommon).

I just wanted to clarify that to avoid any distracted developers reading this to misleadingly think that 'includes' is O(1) because it uses some kind of hash table.

And yeah, this is premature optimization.

Edit. Fix spec link.

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

Yup, that's what I meant; O(n) in practice but O(1) in this particular case. Thanks for clarifying!

Collapse
Sloan, the sloth mascot
Comment deleted
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan • Edited on

The algorithm is O(n). But when it's applied in this particular example, it will always be O(1).

The whole point of Big-O isn't to evaluate an algorithm's performance in isolation. Context/usage matters. In this case, we always have a fixed-size input. By definition, n is always 4. And O(4) is just O(1).

Again, this does not mean that the algorithm itself isn't O(n) in general. It just so happens that n isn't variable in this example.

how the algorithm scales

The input does not scale in this particular case, so this is moot.

Collapse
akashkava profile image
Akash Kava

Hmm you are correct, people who built these performance metrics aren’t smart. !!

Thread Thread
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

I wouldn't say they're not smart. The problem isn't with the benchmarks, it's with misapplying them.

Collapse
ankk98 profile image
Ankit Khandelwal

+1
99% of the times O(1) level optimization is not required. So maintainability of the code should be preferred.

Collapse
nombrekeff profile image
Keff

I don't think we should worry so much about performance. Yeah it's nice to know and be aware of it, but we should not be conditioned to use a "way" instead of another just because it scores 10% more in some random benchmark. Also as many people pointed out, it differs from browser to browser, and from machine to machine.

We are almost never doing so many operation that we need to worry about these minor performance improvements. User's will not even realize it. Better to worry about UX, page load speed, etc... Though if your doing some intensive work (games, machine learning, etc...) you might benefit from these improvements in some way, but there are other areas where you could benefit more (using efficient data structures that fit your needs for example, instead of just arrays).

Collapse
lukeshiru profile image
LUKESHIRU • Edited on

It has like a 13% difference, which idk if it matters much when we still get 1.3 million op/s and it makes the code more readable in some scenarios.

Edit: LOL, nevermind, when browser caching goes into effect, the includes is faster (2% difference in favor of the includes approach, in Chrome latest).

Collapse
jonrandy profile image
Jon Randy • Edited on

On Firefox, the || method was almost twice as fast as includes. So, if there's almost no difference on Chrome, and a big difference in favour of || on Firefox, || would seem the better choice overall for performance? jsbench.me/njkuruuurr/1

Thread Thread
lukeshiru profile image
LUKESHIRU • Edited on

Oof! That engine surely needs to be updated at some point. I mean in theory obviously the includes approach should be less performant because you're creating an array and looping over it instead of having a chain of ORs, but Chrome seems to be doing an optimization by "understanding" your code and maybe doing something similar, so that's why we get close or even better results with includes. Still, the main argument from my pov at least is that the includes approach feels more readable in some scenarios, because instead of saying:

"If value is a, or value is b, or is c, or value is d, or value is e"

We are saying

"If the value is included in a, b, c, d or e"

Obviously if you're doing a task that requires performance (like, idk, a render of a component that will be re-rendered like it happens with React), a low performance approach like this might no be viable in browsers with unoptimized engines such as Firefox (maybe in Safari, aka the new IE, we have the same issue, I didn't even tried it there).

Thread Thread
peerreynders profile image
peerreynders • Edited on

iPhone 6s Plus iOS 14.7.1
jsbench results
perflink results

While Safari is primarily to blame here, this phone's CPU is still better than what's on a lot of contemporary run-of-mill android phones.

2019 Geekbench scores

Thread Thread
lukeshiru profile image
LUKESHIRU

I wish I knew what does the performance of the browser engine has to do with the performance of the device in this particular scenario. Chrome's V8 in Android has the same or better performance with the includes approach thanks to engine optimizations, which you can't get on an iPhone, so I prefer to have a device that actually allow me to test other browsers on it as my daily driver. With an Android device I can try Firefox, Chrome, Samsung's Browser, gosh even Edge. That's not the case with an iPhone. And the tweet you shared is correct, but when he says "shittiest phone" he's talking about bad performance/cheap phones, and "feature phones", not iPhones 😅

Thread Thread
peerreynders profile image
peerreynders • Edited on

Globally Apple has a 15% Marketshare, 53% in North America, 32% in Europe — those users are locked into a "shitty browser" and not willing to forego their other creature comforts they are not going to switch brands on your account.

Meanwhile Chrome even on desktop has a reputation for being CPU and memory hungry. Apparently some people want lighter-weight alternatives. One has to wonder how pessimized JavaScript performs on Google GO on a "shitty phone".

Collapse
akashkava profile image
Akash Kava • Edited on

On mobile, difference is 90%, try it and then see. You are creating an array, unnecessary memory allocation and calling a method, which are multiple cpu cycles. Compound it multiple time used in single script it is by far the worst practice. Test on iPhone

Thread Thread
echofly profile image
echofly

90% ? are you kidding me ? talk is cheap, show me your testing.

Thread Thread
lukeshiru profile image
LUKESHIRU

Ran it twice in mobile, pretty much the same diff, idk what you're talking about...


Thread Thread
akashkava profile image
Akash Kava

Is it Android or iPhone?

Thread Thread
lukeshiru profile image
LUKESHIRU

Android. As a WebDev is the OS that makes more sense for me.

Thread Thread
akashkava profile image
Akash Kava

Android has V8 engine so it performs same as desktop, Safari and Firefox has different results.

Thread Thread
lukeshiru profile image
LUKESHIRU

Yup, and V8 is the most common JS engine out there, and sadly iPhone doesn't allow you to have actual browsers other than Safari on their devices (every browser there is just Safari with a skin on top of it). Safari is definitely the new IE 😅

Collapse
ravavyr profile image
Ravavyr

The amount of arguing you guys have done over this when in real programs it doesn't matter which one you use. 90% slower when it executes 839Mops/s versus 25Mops/s and you're literally doing like 10ops max.... come on guys let it go, it's completely irrelevant.

Collapse
lukeshiru profile image
LUKESHIRU

Stop writing JavaScript like this, extended edition!

3b. Using || For nullish checking:

const second = first || '';
Enter fullscreen mode Exit fullscreen mode

Instead of writing that outdated ||, use the new ?? operator:

const second = first ?? '';
Enter fullscreen mode Exit fullscreen mode

Best thing about it it only checks for null and undefined, instead of all falsy values like || does!

5b. Arrow functions with parentheses when you have 1 parameter. And magic numbers

const doubleOf = (value) => 2 * value
Enter fullscreen mode Exit fullscreen mode

In arrow functions, you can omit the parentheses when it has only 1 argument, and also for scenarios like that, a curried approach would be ideal:

const multiply = multiplier => multiplicand => multiplier * multiplicand;
const double = multiply(2);
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
kettanaito profile image
Artem Zakharchenko

Instead of writing that outdated ||, use the new ?? operator:

|| is as outdated as function. These two operator have two drastically different semantics:

  • || in case left-hand value is falsy.
  • ?? in case left-hand value is not defined (undefined or null).

Both have their usage and are not interchangeable. ?? is great when false is an intentional value.

Parenthesis in functions is not something you should spend your time on, use Prettier.

Collapse
lukeshiru profile image
LUKESHIRU

With || you go to the right value if the left is false, 0, -0, 0n "", NaN, null and undefined .... not just false. If you need to test for anything else other than null and undefined with ??, the problem is elsewhere (bad architecture). Nowadays || ideally should be used for boolean logic, not for default values :/

And yes, Prettier can add or remove parenthesis, but if you don't write them when you don't need them you're saving time 😅

Thread Thread
kettanaito profile image
Artem Zakharchenko

Yeah, that's why I mentioned "falsy" values, not false explicitly.

Collapse
mellen profile image
Matt Ellen

Strong disagree on using => over function. You'll have a confusing time with this.

Collapse
antonmelnyk profile image
Anton Melnyk

You should not use "this" in modern JavaScript in the first place.

Collapse
mellen profile image
Matt Ellen

Because?

Thread Thread
ifarmgolems profile image
Patrik Jajcay

Because it doesn't promote arguably better immutable state.

But hey, it's a bold statement and I wouldn't say it always applies.

Collapse
ravavyr profile image
Ravavyr

These are good tips:

  1. The performance difference is irrelevant, the array includes way feels cleaner but is actually harder to read because you don't immediately see what you're comparing the value to, and the more items in the list, the farther away the variable is visually so it actually makes the code harder to understand for even slightly more complex lists.

  2. Optional chaining is terrible terrible and let me say it again, terrible.
    See. If obj?.address?.postalCode fails, you don't know if obj doesn't exist, or obj.address doesn't exist or if it's just the obj.address.postalCode that's missing.
    This WILL [not can] lead to problems where no error happens even though an important piece of information is missing and debugging it is a mess where you end up splitting it up anyway so it's better to validate each part of the object exists before using it.

  3. You'll never do second=first....if so you would just use "first" in the first place.
    That scenario literally never happens unless you're writing extra code and creating extra variables you don't need.

  4. I like this one, i usually use arrays for it, but objects work too.

  5. If a function has one line in it...it shouldn't be a damn function. That level of abstraction makes systems a bloody nightmare to debug because you're jumping 20 functions deep to find the culprit and often in 20 different files. Just KISS dammit.

Collapse
pavelloz profile image
Paweł Kowalski

Its impossible to respond in a thread after some levels of nesting so I will just go and post in top level adding to one part of it:

So many JS articles have people in the comments section bickering about performance and citing arbitrary benchmark metrics as proof.

I also think people think about "performance" too narrowly. Developers performance matters. Especially since JIT compilers progress every year and find new ways of applying those microoptimizations. In the IE days it was very often the case that one microoptimization would be an antipattern next version, because engine strategy was so much better.

Collapse
jamesthomson profile image
James Thomson

For #1, another option is to use find instead. That way it stops execution on the first found match.

Collapse
jonrandy profile image
Jon Randy

Would be interesting to also see performance benchmarks on each pair

Collapse
leouofa profile image
Leonid Medovyy

Fantastic post. Had no idea about most this stuff. This makes me like JS a lot more.

Collapse
code913 profile image
code913

Great article but in #3 the value of first can be the integer 0 and it will still become the default value.

Collapse
macsikora profile image
Pragmatic Maciej

Such posts always inspire me to write another article why radical opinions are always wrong.

Collapse
mastodonnine profile image
Mastodon9

Where can you learn more tricks like this?

Collapse
mynameisxue profile image
XueZC

let first = 0
const second = first || ''
second // ''

Collapse
alexhutchisondev profile image
AlexHutchison-Dev

Owning an iPhone doesn’t mean you have to use safari, it is the default sure but you can install Firefox, brave, or if spyware is your jam Chrome.

Collapse
miketalbot profile image
Mike Talbot

But those browsers all use the Safari engine under the hood, Apple won't allow anything else