// Arrays and Equality
[] == ![]; // -> true
// Arrays are truthy, so ![] is false, which coerces to 0. [] coerces to 0, so 0 == 0 is true.
true == []; // -> false
true == ![]; // -> false
// true converts to 1 and [] converts to 0. 1 != 0.
false == []; // -> true
false == ![]; // -> true
// false and [] both convert to 0. 0 == 0.
// Type Coercion Oddities
!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true
// Both strings are truthy, so !! converts them to true.
"b" + "a" + +"a" + "a"; // -> 'baNaNa'
// +"a" converts 'a' to NaN, so it becomes "ba" + NaN + "a" which is 'baNaNa'.
NaN === NaN; // -> false
// NaN is not equal to anything, including itself.
// Object Comparison
Object.is(NaN, NaN); // -> true
NaN === NaN; // -> false
Object.is(-0, 0); // -> false
-0 === 0; // -> true
// Object.is and === have different behaviors for NaN and -0.
// Fun with Syntax
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
// Arrays are converted to strings and concatenated.
let a = [, , ,];
a.length; // -> 3
// Trailing commas create empty slots, affecting array length.
[] == ''; // -> true
[] == 0; // -> true
[''] == ''; // -> true
[0] == 0; // -> true
[0] == ''; // -> false
[''] == 0; // -> true
[[[[[[]]]]]] == ''; // -> true
[[[[[[]]]]]] == 0; // -> true
// Abstract equality comparison can produce unexpected results.
// Number Coercion and Parsing
parseInt(null, 24); // -> 23
// null is converted to a string and then parsed according to the specified radix.
parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5
// Parsing floats can produce unexpected results due to string conversion.
0.1 + 0.2; // -> 0.30000000000000004
// Floating-point arithmetic can produce imprecise results.
true + true; // -> 2
(true + true) * true - true; // -> 1
// Booleans are coerced to numbers in arithmetic operations.
Number(); // -> 0
Number(undefined); // -> NaN
// Number without arguments returns 0, with undefined returns NaN.
// Unexpected Typeof and Instanceof
typeof NaN; // -> 'number'
// Despite its name, NaN is of type 'number'.
typeof null; // -> 'object'
// Null is considered an object in JavaScript, although it is a primitive value.
(1).__proto__.__proto__.__proto__; // -> null
// Primitives are wrapped into their object counterparts.
const c = "constructor";
c[c][c]('console.log("WTF?")')(); // -> WTF?
// c accesses the constructor property and calls Function.
// Miscellaneous
{} + []; // -> '[object Object]'
[] + {}; // -> '[object Object]'
// The order of operations and type coercion produce different results.
[10, 1, 3].sort(); // -> [1, 10, 3]
// Default sorting converts elements to strings, sorting them lexicographically.
let f = () => {};
f(); // -> undefined
// Arrow function with empty block returns undefined.
let f = function() { return arguments; };
f("a"); // -> { '0': 'a' }
// Regular function captures arguments.
let f = () => arguments;
f("a"); // -> ReferenceError: arguments is not defined
// Arrow function does not capture arguments.
(() => {
try {
return 2;
} finally {
return 3;
}
})(); // -> 3
// finally block overrides the return statement.
new class F extends (String, Array) {}(); // -> F(0) []
// Extends clause uses the last argument, so class extends Array.
let x, { x: y = 1 } = { x };
y; // -> 1
// Destructuring with default value when x is undefined.
[...[..."..."]].length; // -> 3
// Spreading a string spreads its characters into an array.
foo: {
console.log("first");
break foo;
console.log("second");
}
// -> 'first'
// Labeled block with break statement.
typeof new class { class() {} }(); // -> 'object'
// Keyword can be used as method name.
var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
foo.x; // -> undefined
bar; // -> { n: 1, x: { n: 2 } }
// Chain assignment with side effect.
var obj = { property: 1 };
var array = ["property"];
obj[array]; // -> 1
// Array key is coerced to string.
var nestedArray = [[[[[[[[[["property"]]]]]]]]]];
obj[nestedArray]; // -> 1
// Array key is coerced to string.
var map = {};
var x = 1;
var y = 2;
var z = 3;
map[[x, y, z]] = true;
map[[x + 10, y, z]] = true;
map["1,2,3"]; // -> true
map["11,2,3"]; // -> true
// Array keys are coerced to strings.
Math.min() > Math.max(); // -> true
Math.min() < Math.max(); // -> false
// Math.min() returns Infinity, Math.max() returns -Infinity.
null == 0; // -> false
null > 0; // -> false
null >= 0; // -> true
// Different comparisons yield different results.
JSON.stringify("production") === "production"; // -> false
// JSON.stringify adds quotes to the string.
1 == true; // -> true
Boolean(1.1); // -> true
1.1 == true; // -> false
// Different comparisons with true and numbers.
function a(x) {
arguments[0] = "hello";
console.log(x);
}
a(); // -> undefined
a(1); // -> "hello"
// Arguments object affects parameter value.
setTimeout(() => console.log("called"), Infinity); // -> "called"
// Infinity timeout executes immediately.
setTimeout(123, 100); // -> <timeoutId>
// Non-function callback is converted to string and evaluated.
27..toString(); // -> '27'
// Double dot allows to call toString on a number.
(() => {
return {
b: 10
};
})(); // -> { b: 10 }
// return and the returned expression must be in the same line.
๐ Other resources
- wtfjs.com โ a collection of those very special irregularities, inconsistencies and just plain painfully unintuitive moments for the language of the web.
- Zeros in JavaScript โ a comparison table of
==
,===
,+
and*
in JavaScript.
The original idea for WTFJS belongs to Brian Leroux.