Zachariaharticle

And it’s a footgun to boot

For those of you not in the know, JavaScript has two equality operators, because conveying the difference between assignment = and equality == to beginners wasn’t already difficult enough. Along with their negations they are nearly identical, except the algorithm for === will return false if the types are different, and == will try to coerce them (eg 4 -> “4”) if their types are different, and then proceed to test for equality of value.

When should one use !== over !=?

One reading StackOverflow, Hacker News or any other tech website post that has any excuse to discuss it would come to the conclusion it is imperative never to use == or != and to always use strict equality operators. What do we gain or lose from this?

JavaScript runs in a very unique environment

The upsides are obvious: reducing surface area of failures is always a benefit, right? Only if it doesn’t introduce other errors, and depending on one’s environment, reasoning about the value of data instead of the type as it flows through one’s system may be easier than memorizing the types of all one’s framework or platform’s getters. Afterall, web development is a trip. Types and accessors and so on for the different data the JS runtime exposes, especially in a browser environment, are diverse, euphemistically. Consider:

<input type=number id=numin />
<script>
    document.getElementById("numin")
        .addEventListener("input", e => {
            console.log(e.target.value == 5)
        });
</script>

If one were to fire this up in the browser, would entering 5 into the number input ever cause “true” to be printed to the console? No. Because HTMLInputElement.value is a string. For another opportunity to groan, note that checked checkboxes have the value of "on" rather than true.

This is what the frameworks are all for!

This is part of the reason we use frameworks and platforms and libraries in JS: They obscure away and standardize a lot of this accrued cruft. But obscuration and standardization require opinions, and opinions necessarily provoke disagreement: how one thinks JS ought to work is not how another thinks JS ought to work, and therefor, the user is yet again at the mercy of the coder when using their API.

The gains to be made

We have to consider when we use it in order to make postulations on what we gain from it. A most contrived example:

if (x === y) {
    doEqualityDance();
} else {
    chastiseLeadership();
}

Is x or y a constant in the abstract version of this? That is, in our real code, is it more like x === 5? Then we know the type of y upfront. Why wouldn’t we know the type of x? Under what circumstances would we be comparing the two where we wouldn’t know the type of x based on data flow, but would know that if it were not a number, it should not pass? If we don’t know either of the types, under what situations are we not using === as an imprecise short hand for typeof x == typeof y?

Efficiency?

Knowing the algorithm for abstract equality and strict equality checks has led commenters on StackOverflow to speculate that, not requiring a cast, the strict equality checker is more efficient. I’m not well versed in performance testing, but I wrote up a JSBench for the above that tries to manually type check before testing for equality. After many runs, strict equality was roughly 72% slower. What about not checking, and letting it cast? Then strict equality wins out by about 2% occasionally. But if it’s possible we could have in this example stringified numbers that we want to process, we have to run through the list twice to make sure they’re all the right type if we’re fixated on strict equality, which does reduce efficiency, though probably by a negligible amount.

The edge

Most errors happen at the edge, but a rigid adoption of === to protect in these cases is hardly a salve for the deep problems such code would inevitably feature.

Environment variables

When taking in parameters via Node.Process or similar means, we have to expect strings. This means one may very well find oneself in need of comparing integers or boolean constants to strings. In these cases it can be a wash: if using the value, a conversion will likely be necessary eventually so it only makes sense to do it right away. If it’s a flag or something that one’s code defines, it would make most sense to not use a number. But if one is converting such values to reasonable types, then one knows the types of said values are comparable and so strict equality is a typographic superfluity.

RESTful HTTP APIs

JSON? Forget about it! APIs are generally documented, and JSON codecs are usually well-fleshed out for type conversion. There’s little opportunity here for type equality to be a meaningful short circuit, save perhaps in the production of objects based on primitives: data switches and the like.

Query params

Checking query params was where I was first introduced to obstinate strict type equality stans. The query param could only be a string or a null and was being compared to a constant before a different value would be used to acquire still another value. This is not all situations, but consider what kind of data could be stored in the address bar that would have type safety concerns in a comparison? Or is it the other value, do we not control it? Is there upfront bug squashing work we’re not doing?

The DOM

I already noted above the potentially unexpected values we can receive querying the DOM. Generally, we know what type to expect from the HTML inputs and events we write, however, so demanding strict type equality becomes arbitrary.

So abstract equality is better?

No! Nothing changes as a result of this lengthy and unconclusive diatribe as far as I’m concerned, except, perhaps, hopefully some more pedantically stringent linter/code formatting rules. Both equality operators have their uses: restricting usage can serve a purpose but will also demand extra logic, some of which is already wired into the abstract equality algorithm. Are strict code guidelines better than utilizing the right tool for the job? In terms of avoiding bugs, the best way to do that is to understand the consumed data sources. And maybe that’s something code reviewers as well as writers need to keep in mind.