In this blog post, I explore another form of truthiness in Javascript. What happens if you use a bitwise operator on a value like 0|value
or ~~value
.
Context
We recently turned on the JSHint bitwise rule by default and the following code was caught.
var isValid = false; for (var i = 0; i < elements.length; ++i) { isValid |= elements.someProperty; } return isValid; |
The author didn't mean to use a bitwise operator and wanted to write the following instead:
isValid = isValid || elements.someProperty; |
Unfortunately, while the two lines look similar, they do not behave the same for all Javascript values. Instead of using the truthiness rules, the value is first converted to a signed 32bit integer and compared against 0.
Booleans and special values
In this system, booleans and special values are behaving as expected:
- 0 | undefined === 0
- 0 | null === 0
- 0 | false === 0
- 0 | true === 1
Numbers
It becomes a bit more tricky with numbers. The double is first converted to signed 32 bits integer and then compared to 0.
For usual integers, it is working as expected, only 0 is falsy.
- 0 | 0 === 0
- 0 | 1 === 1
- 0 | 42 === 42
- 0 | -1 === -1
NaN (not a number) is considered as falsy, still going as expected.
- 0 | NaN === 0
For non integers, it is more tricky. Any number in the range ]-1, 1[ is going to be falsy. Lines highlighted in yellow do not behave the same as normal truthy values.
- 0 | 0.99 === 0
- 0 | -0.99 === 0
- 0 | 1.01 === 1
For big integers, the situation is even more confusing. They are all truthy except multiples of 232. This is true even after 253.
- 0 | Math.pow(2, 32) === 0
- 0 | 3 * Math.pow(2, 32) === 0
- 0 | Math.pow(2, 53) === 0
- 0 | Math.pow(2, 60) === 0
- 0 | Math.pow(2, 32) + 1 === 1
Strings
Contrary to normal truthiness, strings in general are falsy.
- 0 | "" === 0
- 0 | " " === 0
- 0 | "a" === 0
But, strings that represent numbers now follow number rules.
- 0 | "0" === 0
- 0 | "1" === 1
- 0 | "0.5" === 0
The number parser is not rigid and accept inputs that have whitespace around for example.
- 0 | " 1 " === 1
Objects
Objects are first converted to string before being converted to int32. So nearly all objects are falsy because their string representation is "[object Object]"
.
- 0 | {} === 0
- 0 | {key: 1} === 0
You can still craft objects that passes that check by overriding toString
.
- 0 | {toString: function() { return "1"; }} === 1
Conclusion
The normal truthiness rules can be quite misleading, but if you want to shoot yourself in the foot, I highly encourage you to replace all your !!expression
into 0|expression
. This will give you endless hours of debugging fun.