1. Arrays and Equality
[] == ![]; // -> true
// Explanation: Arrays are truthy, so ![] is false, which coerces to 0. [] coerces to 0, so 0 == 0 is true.
true == []; // -> false
true == ![]; // -> false
// Explanation: true converts to 1 and [] converts to 0. 1 != 0.
false == []; // -> true
false == ![]; // -> true
// Explanation: false and [] both convert to 0. 0 == 0.
2. Type Coercion Oddities
!!"false" == !!"true"; // -> true
// Explanation: Both strings are truthy, so !! converts them to true.
"b" + "a" + +"a" + "a"; // -> 'baNaNa'
// Explanation: +"a" converts 'a' to NaN, so it becomes "ba" + NaN + "a" which is 'baNaNa'.
NaN === NaN; // -> false
// Explanation: NaN is not equal to anything, including itself.
3. Object Comparison
Object.is(NaN, NaN); // -> true
NaN === NaN; // -> false
// Explanation: Object.is and === have different behaviors for NaN.
Object.is(-0, 0); // -> false
-0 === 0; // -> true
// Explanation: -0 and 0 are considered equal with === but not with Object.is.
4. Fun with Syntax
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
// Explanation: Arrays are converted to strings and concatenated.
let a = [, , ,];
a.length; // -> 3
// Explanation: Trailing commas create empty slots, affecting array length.
5. Number Coercion and Parsing
parseInt(null, 24); // -> 23
// Explanation: null is converted to a string and then parsed according to the specified radix.
0.1 + 0.2; // -> 0.30000000000000004
// Explanation: Floating-point arithmetic can produce imprecise results.
true + true; // -> 2
(true + true) * true - true; // -> 1
// Explanation: Booleans are coerced to numbers in arithmetic operations.
Number(); // -> 0
Number(undefined); // -> NaN
// Explanation: Number without arguments returns 0, with undefined returns NaN.
6. Unexpected Typeof and Instanceof
typeof NaN; // -> 'number'
// Explanation: Despite its name, NaN is of type 'number'.
typeof null; // -> 'object'
// Explanation: Null is considered an object in JavaScript, although it is a primitive value.
7. Miscellaneous
{} + []; // -> 0
[] + {}; // -> '[object Object]'
// Explanation: The order of operations and type coercion produce different results.
[10, 1, 3].sort(); // -> [1, 10, 3]
// Explanation: Default sorting converts elements to strings, sorting them lexicographically.
let f = () => {};
f(); // -> undefined
// Explanation: Arrow function with empty block returns undefined.
let f = function() { return arguments; };
f("a"); // -> { '0': 'a' }
// Explanation: Regular function captures arguments.
let f = () => arguments;
f("a"); // -> ReferenceError: arguments is not defined
// Explanation: Arrow function does not capture arguments.
(() => {
try {
return 2;
} finally {
return 3;
}
})(); // -> 3
// Explanation: finally block overrides the return statement.
new class F extends (String, Array) {}(); // -> F []
// Explanation: Extends clause uses the last argument, so class extends Array.
let x, { x: y = 1 } = { x };
y; // -> 1
// Explanation: Destructuring with default value when x is undefined.
[...[..."..."]].length; // -> 3
// Explanation: Spreading a string spreads its characters into an array.
foo: {
console.log("first");
break foo;
console.log("second");
}
// Explanation: Labeled block with break statement.
typeof new class { class() {} }(); // -> 'object'
// Explanation: Keyword can be used as method name.
📚 Other resources
qit.tools - JavaScript's Gotchas (WTF JS): Unexpected Behaviors