The Magical Kingdom of Undefined, NaN, False, Null, 0, [] and ‘’ in JavaScript. How to.
Well, we are almost in 2018, but this topic definitely needs to be covered and explained. I’ll try to explain all the “magic” which is happening with empty types. I’d like to mention that I love JS, even I’m ironical sometimes.
Let’s figure with types of data first
Now we have 7 data types in JS (at least now):
- Boolean
- Undefined
- Null
- String
- Number
- Symbol
- Object
Array? Map? Set? Function? — They are objects. Infinity? NaN? They are numbers.
We have a typeof operator in order to determine type of value: But, there are several easter eggs:
typeof null === 'object'
well, other cases are more predictable
typeof undefined === 'undefined'
typeof false === 'boolean'
typeof NaN === 'number'
typeof 999 === 'number'
typeof Infinity === 'number'
typeof Symbol() === 'symbol'
typeof [] === 'object' // well, not so effective
typeof {} === 'object'
We can also validate functions, which are not data types, but it’s useful.
typeof function() {} === 'function'
typeof (() => {}) === 'function'
So, some data types can be determined very easy. Some of them not. In particular you can’t determine Array (we know it’s object, but we need to know is this object an array or not). We need to determine null and we need to be able to check NaN. Also there is some mess with strings and numbers:
let a = 'test';
typeof a === 'string'let b = new String('test');
typeof b === 'object':
There are even libraries to check is value a string. As a simple workaround you can use something like:
isString = (a) => (typeof a === string || a instanceof String)
That’s it with strings. Absolutely similar situation is with numbers.
If you know coercion you can do (BUT PLEASE NEVER DO IT IN PRODUCTION):
typeof (String('a') + []) === 'string';
I will explain some coercion magic further, and also will explain why it’s kinda criminal to use such hacks in real code. It represents the reasons for popularity of such things as TypeScript and Flow.
Let’s focus on empty values now
NaN as a Representation Of Lost Value
Actually, NaN doesn’t mean emptiness. It was something, but we will never guess what it was. For us NaN — it’s broken value. The value which doesn’t make any sense for us anymore: we can’t trace how it has appeared, we can’t return its previous state.
The only thing we can do with it — find and prevent it’s usage anywhere. Because typeof doesn’t provide us any useful information — JS has built in instrument to validate it:
isNan(NaN) === true
isNan(0) === false
isNaN('some string') === true // ?!! WATDAFAQ?
You expected it to be easy? It’s never easy, relax. Actually, such behavior has some logic — actually string is not a number. Yes, it’s not a NaN, but it’s not a number either. What is NaN — it’s “Not a Number.” The only thing we need to understand — “isNaN” does coercion for its argument and if you will do following:
+a === NaN
in this case
isNaN(a) === NaN+'test' === NaN
+NaN === NaN
+99 === 99
So, remember about coercion. Once I’ve got a question on the interview like “How to make sure we have NaN without isNaN. The idea of this questions to make sure you understand that NaN never equals itself:
const isNaNCustom = (a) => {
return (a !== a);
}
But because I personally think it’s a terrible question for interview I’ll mention another way to do so (BUT PLEASE NEVER USE IT ON PRODUCTION)
NaN + [] === 'NaN';const isNaNForSomeStupidInterview = (a) => (a + [] === 'NaN')
Let’s give answers which are not practical in real life for questions which are not useful in real life! :)
I promised to explain you those coercion hacks; please wait :)
Emptiness
Well, now it’s time to talk about some values which have similar meanings. Actually, from some points of view, some values are similar.
There are some coercion which explains my idea (it’s given as an explanation, don’t use it in your code). There are all expressions, which are “true”
[] == false
0 == false
'' == false
false == false
On the opposite side:
({}) != false
undefined != false (but !!undefined === false)
null != false
That’s the reason why strict comparison (===) is a MUST:
[] === false // false!
0 === false // false!
'' === false // false!
false === false // well, true
And even you understand that it’s still possible to make mistakes when you pass values as arguments of functions or use the plain condition like:
if (val) { console.log(val) }
The strict comparison doesn’t happen in such a scenario and can make you tricked.
Validation
Let’s come back to our validation. We are still questions about array and null
Null can be checked very easy; there is no complexity to it:
null === null
Situation with array also became simple in ES5.1
const arr = [];
Array.isArray(arr) === true;
But if you like hard life and develop for old IE — there are alternatives:
arr instanceof Array === true;
// or even this way. Please don't use it.
Object.prototype.toString.call(arr) === '[object Array]';
arr.constructor.name === "Array"
Once I decided to go as far as my fantasy allows me and written a library isDefinitelyUndefined https://github.com/tryshchenko/isDefinitelyUndefined . It checks undefined in many different ways. Feel free to enjoy it and star it to force this madness further :).
Why Coercion is So Cool (And Why You ShouldAvoid it)
Just consider this part as a funny bonus. I’m not sure this paragraph will be useful for you.
All the behavior is logical. Yes, probably it differs from your logic, but after reading the specification, I got the understanding for most of the cases.
In most cases adding an empty array makes that a string.
123 + [] === '123';
new Object() + [] === '[object Object]';
false + [] === 'false';
undefined + [] === 'undefined';
null + [] === 'null';
[1, 2] + [] === '1,2';
Infinity + [] === "Infinity"
(function() {}) + [] === "function () {}"
etc… But!
{} + [] === 0;
[] + {} === "[object Object]";
Making a string is also quite a cool thing:
+[] === 0;
+false === 0;
true + true === 2;
So
(true + true + true)*(true + true) === 6;
(true + true)*(([] + {}).length) === 30;
((!0)/(0) + []).length = 8; // Yeah, I divided by zero and survived!
({} + {}).length === 30;// And even functions
(function() {}) + (function() {}) = "function () {}function () {}";
It’s possible to fool around that for a long time, but in the real life isn’t very useful and it really makes sense to avoid it.
Summarizing:
- typeof will not help you with all the cases
- sometimes instanseof helps
- NaN and Arrays should be validated with special functions
- null and undefined can be easily compared with a strict comparison.
- It makes sense to avoid coercion in real projects in most cases
Thanks for reading and have a nice day! I have just started using twitter and would be happy to see you there https://twitter.com/