Special Values
There are several special values spread across the various types that the alert JS developer needs to be aware of, and use properly.
The Non-value Values
For the undefined
type, there is one and only one value: undefined
. For the null
type, there is one and only one value: null
. So for both of them, the label is both its type and its value.
Both undefined
and null
are often taken to be interchangeable as either “empty” values or “non” values. Other developers prefer to distinguish between them with nuance. For example:
null
is an empty valueundefined
is a missing value
Or:
undefined
hasn’t had a value yetnull
had a value and doesn’t anymore
Regardless of how you choose to “define” and use these two values, null
is a special keyword, not an identifier, and thus you cannot treat it as a variable to assign to (why would you!?). However, undefined
is (unfortunately) an identifier. Uh oh.
Undefined
In non-strict
mode, it’s actually possible (though incredibly ill-advised!) to assign a value to the globally provided undefined
identifier:
function foo() {
undefined = 2; // really bad idea!
}
foo();
function foo() {
"use strict";
undefined = 2; // TypeError!
}
foo();
In both non-strict
mode and strict
mode, however, you can create a local variable of the name undefined
. But again, this is a terrible idea!
function foo() {
"use strict";
var undefined = 2;
console.log( undefined ); // 2
}
foo();
Friends don’t let friends override undefined
. Ever.
void
Operator
While undefined
is a built-in identifier that holds (unless modified — see above!) the built-in undefined
value, another way to get this value is the void
operator.
The expression void ___
“voids” out any value, so that the result of the expression is always the undefined
value. It doesn’t modify the existing value; it just ensures that no value comes back from the operator expression.
var a = 42;
console.log( void a, a ); // undefined 42
By convention (mostly from C-language programming), to represent the undefined
value stand-alone by using void
, you’d use void 0
(though clearly even void true
or any other void
expression does the same thing). There’s no practical difference between void 0
, void 1
, and undefined
.
But the void
operator can be useful in a few other circumstances, if you need to ensure that an expression has no result value (even if it has side effects).
For example:
function doSomething() {
// note: `APP.ready` is provided by our application
if (!APP.ready) {
// try again later
return void setTimeout( doSomething, 100 );
}
var result;
// do some other stuff
return result;
}
// were we able to do it right away?
if (doSomething()) {
// handle next tasks right away
}
Here, the setTimeout(..)
function returns a numeric value (the unique identifier of the timer interval, if you wanted to cancel it), but we want to void
that out so that the return value of our function doesn’t give a false-positive with the if
statement.
Many devs prefer to just do these actions separately, which works the same but doesn’t use the void
operator:
if (!APP.ready) {
// try again later
setTimeout( doSomething, 100 );
return;
}
In general, if there’s ever a place where a value exists (from some expression) and you’d find it useful for the value to be undefined
instead, use the void
operator. That probably won’t be terribly common in your programs, but in the rare cases you do need it, it can be quite helpful.
Special Numbers
The number
type includes several special values. We’ll take a look at each in detail.
The Not Number, Number
Any mathematic operation you perform without both operands being number
s (or values that can be interpreted as regular number
s in base 10 or base 16) will result in the operation failing to produce a valid number
, in which case you will get the NaN
value.
NaN
literally stands for “not a number
“, though this label/description is very poor and misleading, as we’ll see shortly. It would be much more accurate to think of NaN
as being “invalid number,” “failed number,” or even “bad number,” than to think of it as “not a number.”
For example:
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
In other words: “the type of not-a-number is ‘number’!” Hooray for confusing names and semantics.
NaN
is a kind of “sentinel value” (an otherwise normal value that’s assigned a special meaning) that represents a special kind of error condition within the number
set. The error condition is, in essence: “I tried to perform a mathematic operation but failed, so here’s the failed number
result instead.”
So, if you have a value in some variable and want to test to see if it’s this special failed-number NaN
, you might think you could directly compare to NaN
itself, as you can with any other value, like null
or undefined
. Nope.
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
NaN
is a very special value in that it’s never equal to another NaN
value (i.e., it’s never equal to itself). It’s the only value, in fact, that is not reflexive (without the Identity characteristic x === x
). So, NaN !== NaN
. A bit strange, huh?
So how do we test for it, if we can’t compare to NaN
(since that comparison would always fail)?
var a = 2 / "foo";
isNaN( a ); // true
Easy enough, right? We use the built-in global utility called isNaN(..)
and it tells us if the value is NaN
or not. Problem solved!
Not so fast.
The isNaN(..)
utility has a fatal flaw. It appears it tried to take the meaning of NaN
(“Not a Number”) too literally — that its job is basically: “test if the thing passed in is either not a number
or is a number
.” But that’s not quite accurate.
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; // "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true -- ouch!
Clearly, "foo"
is literally not a number
, but it’s definitely not the NaN
value either! This bug has been in JS since the very beginning (over 19 years of ouch).
As of ES6, finally a replacement utility has been provided: Number.isNaN(..)
. A simple polyfill for it so that you can safely check NaN
values now even in pre-ES6 browsers is:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false -- phew!
Actually, we can implement a Number.isNaN(..)
polyfill even easier, by taking advantage of that peculiar fact that NaN
isn’t equal to itself. NaN
is the only value in the whole language where that’s true; every other value is always equal to itself.
So:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}
Weird, huh? But it works!
NaN
s are probably a reality in a lot of real-world JS programs, either on purpose or by accident. It’s a really good idea to use a reliable test, like Number.isNaN(..)
as provided (or polyfilled), to recognize them properly.
If you’re currently using just isNaN(..)
in a program, the sad reality is your program has a bug, even if you haven’t been bitten by it yet!
Infinities
Developers from traditional compiled languages like C are probably used to seeing either a compiler error or runtime exception, like “Divide by zero,” for an operation like:
var a = 1 / 0;
However, in JS, this operation is well-defined and results in the value Infinity
(aka Number.POSITIVE_INFINITY
). Unsurprisingly:
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
As you can see, -Infinity
(aka Number.NEGATIVE_INFINITY
) results from a divide-by-zero where either (but not both!) of the divide operands is negative.
JS uses finite numeric representations (IEEE 754 floating-point, which we covered earlier), so contrary to pure mathematics, it seems it is possible to overflow even with an operation like addition or subtraction, in which case you’d get Infinity
or -Infinity
.
For example:
var a = Number.MAX_VALUE; // 1.7976931348623157e+308
a + a; // Infinity
a + Math.pow( 2, 970 ); // Infinity
a + Math.pow( 2, 969 ); // 1.7976931348623157e+308
According to the specification, if an operation like addition results in a value that’s too big to represent, the IEEE 754 “round-to-nearest” mode specifies what the result should be. So, in a crude sense, Number.MAX_VALUE + Math.pow( 2, 969 )
is closer to Number.MAX_VALUE
than to Infinity
, so it “rounds down,” whereas Number.MAX_VALUE + Math.pow( 2, 970 )
is closer to Infinity
so it “rounds up”.
If you think too much about that, it’s going to make your head hurt. So don’t. Seriously, stop!
Once you overflow to either one of the infinities, however, there’s no going back. In other words, in an almost poetic sense, you can go from finite to infinite but not from infinite back to finite.
It’s almost philosophical to ask: “What is infinity divided by infinity”. Our naive brains would likely say “1” or maybe “infinity.” Turns out neither is true. Both mathematically and in JavaScript, Infinity / Infinity
is not a defined operation. In JS, this results in NaN
.
But what about any positive finite number
divided by Infinity
? That’s easy! 0
. And what about a negative finite number
divided by Infinity
? Keep reading!
Zeros
While it may confuse the mathematics-minded reader, JavaScript has both a normal zero 0
(otherwise known as a positive zero +0
) and a negative zero -0
. Before we explain why the -0
exists, we should examine how JS handles it, because it can be quite confusing.
Besides being specified literally as -0
, negative zero also results from certain mathematic operations. For example:
var a = 0 / -3; // -0
var b = 0 * -3; // -0
Addition and subtraction cannot result in a negative zero.
A negative zero when examined in the developer console will usually reveal -0
, though that was not the common case until fairly recently, so some older browsers you encounter may still report it as 0
.
However, if you try to stringify a negative zero value, it will always be reported as "0"
, according to the spec.
var a = 0 / -3;
// (some browser) consoles at least get it right
a; // -0
// but the spec insists on lying to you!
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// strangely, even JSON gets in on the deception
JSON.stringify( a ); // "0"
Interestingly, the reverse operations (going from string
to number
) don’t lie:
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
Warning: The JSON.stringify( -0 )
behavior of "0"
is particularly strange when you observe that it’s inconsistent with the reverse: JSON.parse( "-0" )
reports -0
as you’d correctly expect.
In addition to stringification of negative zero being deceptive to hide its true value, the comparison operators are also (intentionally) configured to lie.
var a = 0;
var b = 0 / -3;
a == b; // true
-0 == 0; // true
a === b; // true
-0 === 0; // true
0 > -0; // false
a > b; // false
Clearly, if you want to distinguish a -0
from a 0
in your code, you can’t just rely on what the developer console outputs, so you’re going to have to be a bit more clever:
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false
Now, why do we need a negative zero, besides academic trivia?
There are certain applications where developers use the magnitude of a value to represent one piece of information (like speed of movement per animation frame) and the sign of that number
to represent another piece of information (like the direction of that movement).
In those applications, as one example, if a variable arrives at zero and it loses its sign, then you would lose the information of what direction it was moving in before it arrived at zero. Preserving the sign of the zero prevents potentially unwanted information loss.
Special Equality
As we saw above, the NaN
value and the -0
value have special behavior when it comes to equality comparison. NaN
is never equal to itself, so you have to use ES6’s Number.isNaN(..)
(or a polyfill). Similarly, -0
lies and pretends that it’s equal (even ===
strict equal — see Chapter 4) to regular positive 0
, so you have to use the somewhat hackish isNegZero(..)
utility we suggested above.
As of ES6, there’s a new utility that can be used to test two values for absolute equality, without any of these exceptions. It’s called Object.is(..)
:
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false
There’s a pretty simple polyfill for Object.is(..)
for pre-ES6 environments:
if (!Object.is) {
Object.is = function(v1, v2) {
// test for `-0`
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// test for `NaN`
if (v1 !== v1) {
return v2 !== v2;
}
// everything else
return v1 === v2;
};
}
Object.is(..)
probably shouldn’t be used in cases where ==
or ===
are known to be safe (see Chapter 4 “Coercion”), as the operators are likely much more efficient and certainly are more idiomatic/common. Object.is(..)
is mostly for these special cases of equality.