Top Related Projects
A book series on JavaScript. @YDKJS on twitter.
A long list of (advanced) JavaScript questions, and their explanations :sparkles:
:bathtub: Clean Code concepts adapted for JavaScript
JavaScript Style Guide
ð Algorithms and data structures implemented in JavaScript with explanations and links to further readings
ð 33 JavaScript concepts every developer should know.
Quick Overview
"wtfjs" is a collection of JavaScript examples that showcase quirky and unexpected behaviors in the language. It aims to highlight the funny and tricky parts of JavaScript, serving as both an educational resource and a source of amusement for developers.
Pros
- Provides insights into JavaScript's lesser-known behaviors and edge cases
- Helps developers avoid potential pitfalls in their code
- Serves as an entertaining resource for JavaScript enthusiasts
- Encourages deeper understanding of JavaScript's inner workings
Cons
- May confuse beginners or those new to JavaScript
- Some examples might be considered esoteric or not applicable to everyday coding
- Could potentially encourage the use of confusing or unclear code practices
- Lacks in-depth explanations for some of the more complex examples
Code Examples
- Unexpected array comparison:
[] == ![] // -> true
This example demonstrates how JavaScript's type coercion can lead to unexpected results when comparing an empty array to its negation.
- NaN is not equal to itself:
NaN === NaN // -> false
This showcases the unique property of NaN (Not-a-Number) in JavaScript, where it's not equal to any value, including itself.
- Tricky addition:
[] + {} // -> "[object Object]"
{} + [] // -> 0
These examples illustrate how JavaScript handles addition between arrays and objects, resulting in unexpected string and number outputs.
Getting Started
To explore the "wtfjs" examples:
- Visit the GitHub repository: https://github.com/denysdovhan/wtfjs
- Browse through the README.md file to see various JavaScript quirks
- Try running the examples in your browser's console or a Node.js environment
- Contribute your own discoveries by opening a pull request on the repository
Competitor Comparisons
A book series on JavaScript. @YDKJS on twitter.
Pros of You-Dont-Know-JS
- Comprehensive and in-depth coverage of JavaScript concepts
- Well-structured series of books for different skill levels
- Regularly updated to reflect latest JavaScript standards
Cons of You-Dont-Know-JS
- Can be overwhelming for beginners due to its depth
- Requires significant time investment to read through all books
- Less focus on quirky or unexpected language behaviors
Code Comparison
You-Dont-Know-JS often provides detailed explanations with code examples:
function foo() {
console.log(this.a);
}
var obj = { a: 2, foo: foo };
obj.foo(); // 2
wtfjs typically showcases shorter, unexpected behaviors:
[] == ![] // -> true
Summary
You-Dont-Know-JS is a comprehensive resource for deep JavaScript learning, offering thorough explanations and examples. It's ideal for developers seeking in-depth understanding but may be overwhelming for beginners. wtfjs, on the other hand, focuses on quirky language behaviors and unexpected results, making it a quick reference for JavaScript's peculiarities. While You-Dont-Know-JS provides a structured learning path, wtfjs offers bite-sized, interesting facts about the language. The choice between them depends on the learner's goals and preferred learning style.
A long list of (advanced) JavaScript questions, and their explanations :sparkles:
Pros of javascript-questions
- More comprehensive, covering a wider range of JavaScript topics
- Includes detailed explanations for each question and answer
- Regularly updated with new questions and community contributions
Cons of javascript-questions
- May be overwhelming for beginners due to the large number of questions
- Some questions might be too advanced for those just starting with JavaScript
Code Comparison
javascript-questions:
function sayHi() {
console.log(name);
console.log(age);
var name = 'Lydia';
let age = 21;
}
sayHi();
wtfjs:
[] == ![] // -> true
The javascript-questions example demonstrates variable hoisting and temporal dead zone concepts, while the wtfjs example showcases JavaScript's quirky type coercion behavior.
javascript-questions provides a more in-depth learning experience with explanations and multiple-choice options, whereas wtfjs focuses on concise, surprising JavaScript behaviors without extensive explanations.
Both repositories serve as valuable resources for JavaScript developers, with javascript-questions being more suitable for structured learning and wtfjs for quick, interesting insights into the language's peculiarities.
:bathtub: Clean Code concepts adapted for JavaScript
Pros of clean-code-javascript
- Focuses on best practices and writing maintainable code
- Provides comprehensive guidelines for various aspects of JavaScript development
- Includes examples of both good and bad code practices
Cons of clean-code-javascript
- May be overwhelming for beginners due to its extensive content
- Some guidelines might be subjective or context-dependent
- Lacks the entertaining and quirky nature of wtfjs
Code Comparison
clean-code-javascript example (good practice):
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.';
// ...
}
wtfjs example (quirky behavior):
[] == ![] // -> true
clean-code-javascript emphasizes writing clear, maintainable code, while wtfjs highlights JavaScript's unusual behaviors and quirks. The former is more practical for everyday development, while the latter is educational and entertaining, showcasing language peculiarities.
clean-code-javascript is comprehensive and serious, suitable for developers looking to improve their coding practices. wtfjs, on the other hand, is concise and fun, perfect for those interested in JavaScript's oddities and edge cases.
Both repositories serve different purposes: clean-code-javascript as a guide for writing better code, and wtfjs as a collection of JavaScript's surprising aspects. They complement each other, offering developers a well-rounded understanding of the language.
JavaScript Style Guide
Pros of javascript
- Comprehensive style guide for JavaScript best practices
- Widely adopted in the industry, enhancing code consistency across projects
- Regularly updated to reflect modern JavaScript features and patterns
Cons of javascript
- Can be overwhelming for beginners due to its extensive rules
- May require additional configuration and tooling for enforcement
- Some rules might be opinionated and not universally agreed upon
Code Comparison
wtfjs example:
[] == ![] // -> true
javascript style guide example:
// bad
const items = new Array();
// good
const items = [];
wtfjs focuses on quirky JavaScript behavior, while javascript provides clear guidelines for writing clean, consistent code. The wtfjs example demonstrates unexpected type coercion, whereas the javascript example shows preferred array initialization.
wtfjs is educational and entertaining, highlighting JavaScript's peculiarities. In contrast, javascript is a practical tool for maintaining code quality in real-world projects. While wtfjs may help developers understand language nuances, javascript directly influences coding practices and standards.
Both repositories serve different purposes: wtfjs as a collection of JavaScript oddities, and javascript as a comprehensive style guide. Developers can benefit from both: wtfjs for deepening language understanding, and javascript for establishing consistent coding practices.
ð Algorithms and data structures implemented in JavaScript with explanations and links to further readings
Pros of javascript-algorithms
- Comprehensive collection of algorithms and data structures
- Well-organized with explanations and implementations in JavaScript
- Includes Big O notation and complexity analysis for each algorithm
Cons of javascript-algorithms
- More complex and time-consuming to understand for beginners
- Focuses on traditional computer science concepts rather than JavaScript quirks
- May not be as entertaining or engaging for casual learners
Code Comparison
wtfjs example:
[] == ![] // -> true
javascript-algorithms example:
function binarySearch(array, target) {
let left = 0;
let right = array.length - 1;
while (left <= right) {
// ... implementation details
}
}
Summary
wtfjs is a collection of funny and tricky JavaScript examples, ideal for understanding language quirks and edge cases. It's more accessible and entertaining for beginners.
javascript-algorithms provides a comprehensive resource for learning classic algorithms and data structures implemented in JavaScript. It's better suited for those looking to deepen their computer science knowledge and prepare for technical interviews.
Both repositories serve different purposes and cater to different learning objectives within the JavaScript ecosystem.
ð 33 JavaScript concepts every developer should know.
Pros of 33-js-concepts
- Comprehensive coverage of core JavaScript concepts
- Well-structured with clear explanations and examples
- Includes links to additional resources for deeper learning
Cons of 33-js-concepts
- May be overwhelming for absolute beginners
- Less focus on quirky or unexpected behavior
- Requires more time investment to work through all concepts
Code Comparison
33-js-concepts example (Closures):
function outer() {
let count = 0;
return function inner() {
return count++;
};
}
wtfjs example (Array-like objects):
const arrLike = { 0: 'foo', 1: 'bar', length: 2 };
Array.from(arrLike); // ['foo', 'bar']
[...arrLike]; // TypeError: object is not iterable
Summary
33-js-concepts offers a structured approach to learning JavaScript fundamentals, ideal for developers seeking a comprehensive understanding. wtfjs, on the other hand, focuses on quirky and unexpected behaviors, making it more suitable for experienced developers looking to deepen their knowledge of JavaScript's nuances. While 33-js-concepts provides a solid foundation, wtfjs offers insights into edge cases and potential pitfalls. Both repositories serve different purposes and can be valuable resources depending on the developer's needs and experience level.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
What the f*ck JavaScript?
A list of funny and tricky JavaScript examples
JavaScript is a great language. It has a simple syntax, large ecosystem and, what is most important, a great community.
At the same time, we all know that JavaScript is quite a funny language with tricky parts. Some of them can quickly turn our everyday job into hell, and some of them can make us laugh out loud.
The original idea for WTFJS belongs to Brian Leroux. This list is highly inspired by his talk ÃĒÂÂWTFJSÃĒÂÂ at dotJS 2012:
Node Packaged Manuscript
You can install this handbook using npm
. Just run:
$ npm install -g wtfjs
You should be able to run wtfjs
at the command line now. This will open the manual in your selected $PAGER
. Otherwise, you may continue reading on here.
The source is available here: https://github.com/denysdovhan/wtfjs
Translations
Currently, there are these translations of wtfjs:
- ÃĪÂļÂÃĶÂÂ
- à ÂĪÂđà ÂĪÂŋà ÂĪÂà ÂĪÂĶà ÂĨÂ
- Français
- PortuguÊs do Brasil
- Polski
- Italiano
- Russian (on Habr.com)
- ÃÂÂÊÂĩÂÃŽÂÂī
Help translating to your language
Note: Translations are maintained by their translators. They may not contain every example, and existing examples may be outdated.
Table of Contents
- ðŠðÂÂÂŧ Motivation
- ÃĒÂÂðÂÂÂŧ Notation
- ð Examples
[]
is equal![]
true
is not equal![]
, but not equal[]
too- true is false
- baNaNa
NaN
is not aNaN
Object.is()
and===
weird cases- It's a fail
[]
is truthy, but nottrue
null
is falsy, but notfalse
document.all
is an object, but it is undefined- Minimal value is greater than zero
- function is not a function
- Adding arrays
- Trailing commas in array
- Array equality is a monster
undefined
andNumber
parseInt
is a bad guy- Math with
true
andfalse
- HTML comments are valid in JavaScript
NaN
isnota number[]
andnull
are objects- Magically increasing numbers
- Precision of
0.1 + 0.2
- Patching numbers
- Comparison of three numbers
- Funny math
- Addition of RegExps
- Strings aren't instances of
String
- Calling functions with backticks
- Call call call
- A
constructor
property - Object as a key of object's property
- Accessing prototypes with
__proto__
`${{Object}}`
- Destructuring with default values
- Dots and spreading
- Labels
- Nested labels
- Insidious
try..catch
- Is this multiple inheritance?
- A generator which yields itself
- A class of class
- Non-coercible objects
- Tricky arrow functions
- Arrow functions can not be a constructor
arguments
and arrow functions- Tricky return
- Chaining assignments on object
- Accessing object properties with arrays
Number.toFixed()
display different numbersMath.max()
less thanMath.min()
- Comparing
null
to0
- Same variable redeclaration
- Default behavior Array.prototype.sort()
- resolve() won't return Promise instance
{}{}
is undefinedarguments
binding- An
alert
from hell - An infinite timeout
- A
setTimeout
object - Double dot
- Extra Newness
- Why you should use semicolons
- Split a string by a space
- A stringified string
- Non-strict comparison of a number to
true
- ð Other resources
- ðÂÂĪ Supporting
- ð License
ðŠðÂÂÂŧ Motivation
Just for fun
â ÃĒÂÂJust for Fun: The Story of an Accidental RevolutionaryÃĒÂÂ, Linus Torvalds
The primary goal of this list is to collect some crazy examples and explain how they work, if possible. Just because it's fun to learn something that we didn't know before.
If you are a beginner, you can use these notes to get a deeper dive into JavaScript. I hope these notes will motivate you to spend more time reading the specification.
If you are a professional developer, you can consider these examples as a great reference for all of the quirks and unexpected edges of our beloved JavaScript.
In any case, just read this. You're probably going to find something new.
ÃĒÂÂ ÃŊÂļÂ Note: If you enjoy reading this document, please, consider supporting the author of this collection.
ÃĒÂÂðÂÂÂŧ Notation
// ->
is used to show the result of an expression. For example:
1 + 1; // -> 2
// >
means the result of console.log
or another output. For example:
console.log("hello, world!"); // > hello, world!
//
is just a comment used for explanations. Example:
// Assigning a function to foo constant
const foo = function() {};
ð Examples
[]
is equal ![]
Array is equal not array:
[] == ![]; // -> true
ðÂÂÂĄ Explanation:
The abstract equality operator converts both sides to numbers to compare them, and both sides become the number 0
for different reasons. Arrays are truthy, so on the right, the opposite of a truthy value is false
, which is then coerced to 0
. On the left, however, an empty array is coerced to a number without becoming a boolean first, and empty arrays are coerced to 0
, despite being truthy.
Here is how this expression simplifies:
+[] == +![];
0 == +false;
0 == 0;
true;
See also []
is truthy, but not true
.
true
is not equal ![]
, but not equal []
too
Array is not equal true
, but not Array is not equal true
too;
Array is equal false
, not Array is equal false
too:
true == []; // -> false
true == ![]; // -> false
false == []; // -> true
false == ![]; // -> true
ðÂÂÂĄ Explanation:
true == []; // -> false
true == ![]; // -> false
// According to the specification
true == []; // -> false
toNumber(true); // -> 1
toNumber([]); // -> 0
1 == 0; // -> false
true == ![]; // -> false
![]; // -> false
true == false; // -> false
false == []; // -> true
false == ![]; // -> true
// According to the specification
false == []; // -> true
toNumber(false); // -> 0
toNumber([]); // -> 0
0 == 0; // -> true
false == ![]; // -> true
![]; // -> false
false == false; // -> true
true is false
!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true
ðÂÂÂĄ Explanation:
Consider this step-by-step:
// true is 'truthy' and represented by value 1 (number), 'true' in string form is NaN.
true == "true"; // -> false
false == "false"; // -> false
// 'false' is not the empty string, so it's a truthy value
!!"false"; // -> true
!!"true"; // -> true
baNaNa
"b" + "a" + +"a" + "a"; // -> 'baNaNa'
This is an old-school joke in JavaScript, but remastered. Here's the original one:
"foo" + +"bar"; // -> 'fooNaN'
ðÂÂÂĄ Explanation:
The expression is evaluated as 'foo' + (+'bar')
, which converts 'bar'
to not a number.
NaN
is not a NaN
NaN === NaN; // -> false
ðÂÂÂĄ Explanation:
The specification strictly defines the logic behind this behavior:
- If
Type(x)
is different fromType(y)
, return false.- If
Type(x)
is Number, then
- If
x
is NaN, return false.- If
y
is NaN, return false.- ÃĒÂÂĶ ÃĒÂÂĶ ÃĒÂÂĶ
Following the definition of NaN
from the IEEE:
Four mutually exclusive relations are possible: less than, equal, greater than, and unordered. The last case arises when at least one operand is NaN. Every NaN shall compare unordered with everything, including itself.
â ÃĒÂÂWhat is the rationale for all comparisons returning false for IEEE754 NaN values?ÃĒÂÂ at StackOverflow
Object.is()
and ===
weird cases
Object.is()
determines if two values have the same value or not. It works similar to the ===
operator but there are a few weird cases:
Object.is(NaN, NaN); // -> true
NaN === NaN; // -> false
Object.is(-0, 0); // -> false
-0 === 0; // -> true
Object.is(NaN, 0 / 0); // -> true
NaN === 0 / 0; // -> false
ðÂÂÂĄ Explanation:
In JavaScript lingo, NaN
and NaN
are the same value but they're not strictly equal. NaN === NaN
being false is apparently due to historical reasons so it would probably be better to accept it as it is.
Similarly, -0
and 0
are strictly equal, but they're not the same value.
For more details about NaN === NaN
, see the above case.
It's a fail
You would not believe, but ÃĒÂÂĶ
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'
ðÂÂÂĄ Explanation:
By breaking that mass of symbols into pieces, we notice that the following pattern occurs often:
![] + []; // -> 'false'
![]; // -> false
So we try adding []
to false
. But due to a number of internal function calls (binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
) we end up converting the right operand to a string:
![] + [].toString(); // 'false'
Thinking of a string as an array we can access its first character via [0]
:
"false"[0]; // -> 'f'
The rest is obvious, but the i
is tricky. The i
in fail
is grabbed by generating the string 'falseundefined'
and grabbing the element on index ['10']
.
More examples:
+![] // -> 0
+!![] // -> 1
!![] // -> true
![] // -> false
[][[]] // -> undefined
+!![] / +![] // -> Infinity
[] + {} // -> "[object Object]"
+{} // -> NaN
- Brainfuck beware: JavaScript is after you!
- Writing a sentence without using the Alphabet ÃĒÂÂ generate any phrase using JavaScript
[]
is truthy, but not true
An array is a truthy value, however, it's not equal to true
.
!![] // -> true
[] == true // -> false
ðÂÂÂĄ Explanation:
Here are links to the corresponding sections in the ECMA-262 specification:
null
is falsy, but not false
Despite the fact that null
is a falsy value, it's not equal to false
.
!!null; // -> false
null == false; // -> false
At the same time, other falsy values, like 0
or ''
are equal to false
.
0 == false; // -> true
"" == false; // -> true
ðÂÂÂĄ Explanation:
The explanation is the same as for previous example. Here's the corresponding link:
document.all
is an object, but it is undefined
ÃĒÂÂ ÃŊÂļÂ This is part of the Browser API and won't work in a Node.js environment ÃĒÂÂ ÃŊÂļÂ
Despite the fact that document.all
is an array-like object and it gives access to the DOM nodes in the page, it responds to the typeof
function as undefined
.
document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'
At the same time, document.all
is not equal to undefined
.
document.all === undefined; // -> false
document.all === null; // -> false
But at the same time:
document.all == null; // -> true
ðÂÂÂĄ Explanation:
document.all
used to be a way to access DOM elements, in particular with old versions of IE. While it has never been a standard it was broadly used in the old age JS code. When the standard progressed with new APIs (such asdocument.getElementById
) this API call became obsolete and the standard committee had to decide what to do with it. Because of its broad use they decided to keep the API but introduce a willful violation of the JavaScript specification. The reason why it responds tofalse
when using the Strict Equality Comparison withundefined
whiletrue
when using the Abstract Equality Comparison is due to the willful violation of the specification that explicitly allows that.â ÃĒÂÂObsolete features - document.allÃĒÂÂ at WhatWG - HTML spec â ÃĒÂÂChapter 4 - ToBoolean - Falsy valuesÃĒÂÂ at YDKJS - Types & Grammar
Minimal value is greater than zero
Number.MIN_VALUE
is the smallest number, which is greater than zero:
Number.MIN_VALUE > 0; // -> true
ðÂÂÂĄ Explanation:
Number.MIN_VALUE
is5e-324
, i.e. the smallest positive number that can be represented within float precision, i.e. that's as close as you can get to zero. It defines the best resolution that floats can give you.Now the overall smallest value is
Number.NEGATIVE_INFINITY
although it's not really numeric in a strict sense.â ÃĒÂÂWhy is
0
less thanNumber.MIN_VALUE
in JavaScript?ÃĒÂÂ at StackOverflow
function is not a function
ÃĒÂÂ ÃŊÂļÂ A bug present in V8 v5.5 or lower (Node.js <=7) ÃĒÂÂ ÃŊÂļÂ
All of you know about the annoying undefined is not a function, but what about this?
// Declare a class which extends null
class Foo extends null {}
// -> [Function: Foo]
new Foo() instanceof null;
// > TypeError: function is not a function
// > at ÃĒÂÂĶ ÃĒÂÂĶ ÃĒÂÂĶ
ðÂÂÂĄ Explanation:
This is not a part of the specification. It's just a bug that has now been fixed, so there shouldn't be a problem with it in the future.
Super constructor null of Foo is not a constructor
It's continuation of story with previous bug in modern environment (tested with Chrome 71 and Node.js v11.8.0).
class Foo extends null {}
new Foo() instanceof null;
// > TypeError: Super constructor null of Foo is not a constructor
ðÂÂÂĄ Explanation:
This is not a bug because:
Object.getPrototypeOf(Foo.prototype); // -> null
If the class has no constructor the call from prototype chain. But in the parent has no constructor. Just in case, IÃĒÂÂll clarify that null
is an object:
typeof null === "object";
Therefore, you can inherit from it (although in the world of the OOP for such terms would have beaten me). So you can't call the null constructor. If you change this code:
class Foo extends null {
constructor() {
console.log("something");
}
}
You see the error:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
And if you add super
:
class Foo extends null {
constructor() {
console.log(111);
super();
}
}
JS throws an error:
TypeError: Super constructor null of Foo is not a constructor
Adding arrays
What if you try to add two arrays?
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
ðÂÂÂĄ Explanation:
The concatenation happens. Step-by-step, it looks like this:
[1, 2, 3] +
[4, 5, 6][
// call toString()
(1, 2, 3)
].toString() +
[4, 5, 6].toString();
// concatenation
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");
Trailing commas in array
You've created an array with 4 empty elements. Despite all, you'll get an array with three elements, because of trailing commas:
let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'
ðÂÂÂĄ Explanation:
Trailing commas (sometimes called "final commas") can be useful when adding new elements, parameters, or properties to JavaScript code. If you want to add a new property, you can simply add a new line without modifying the previously last line if that line already uses a trailing comma. This makes version-control diffs cleaner and editing code might be less troublesome.
â Trailing commas at MDN
Array equality is a monster
Array equality is a monster in JS, as you can see below:
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
ðÂÂÂĄ Explanation:
You should watch very carefully for the above examples! The behaviour is described in section 7.2.15 Abstract Equality Comparison of the specification.
undefined
and Number
If we don't pass any arguments into the Number
constructor, we'll get 0
. The value undefined
is assigned to formal arguments when there are no actual arguments, so you might expect that Number
without arguments takes undefined
as a value of its parameter. However, when we pass undefined
, we will get NaN
.
Number(); // -> 0
Number(undefined); // -> NaN
ðÂÂÂĄ Explanation:
According to the specification:
- If no arguments were passed to this function's invocation, let
n
be+0
. - Else, let
n
be ?ToNumber(value)
. - In case of
undefined
,ToNumber(undefined)
should returnNaN
.
Here's the corresponding section:
parseInt
is a bad guy
parseInt
is famous by its quirks:
parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15
ðÂÂÂĄ Explanation: This happens because parseInt
will continue parsing character-by-character until it hits a character it doesn't know. The f
in 'f*ck'
is the hexadecimal digit 15
.
Parsing Infinity
to integer is somethingÃĒÂÂĶ
//
parseInt("Infinity", 10); // -> NaN
// ...
parseInt("Infinity", 18); // -> NaN...
parseInt("Infinity", 19); // -> 18
// ...
parseInt("Infinity", 23); // -> 18...
parseInt("Infinity", 24); // -> 151176378
// ...
parseInt("Infinity", 29); // -> 385849803
parseInt("Infinity", 30); // -> 13693557269
// ...
parseInt("Infinity", 34); // -> 28872273981
parseInt("Infinity", 35); // -> 1201203301724
parseInt("Infinity", 36); // -> 1461559270678...
parseInt("Infinity", 37); // -> NaN
Be careful with parsing null
too:
parseInt(null, 24); // -> 23
ðÂÂÂĄ Explanation:
It's converting
null
to the string"null"
and trying to convert it. For radixes 0 through 23, there are no numerals it can convert, so it returns NaN. At 24,"n"
, the 14th letter, is added to the numeral system. At 31,"u"
, the 21st letter, is added and the entire string can be decoded. At 37 on there is no longer any valid numeral set that can be generated andNaN
is returned.â ÃĒÂÂparseInt(null, 24) === 23ÃĒÂÂĶ wait, what?ÃĒÂÂ at StackOverflow
Don't forget about octals:
parseInt("06"); // 6
parseInt("08"); // 8 if support ECMAScript 5
parseInt("08"); // 0 if not support ECMAScript 5
ðÂÂÂĄ Explanation: If the input string begins with "0", radix is eight (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 specifies that 10 (decimal) is used, but not all browsers support this yet. For this reason always specify a radix when using parseInt
.
parseInt
always convert input to string:
parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1
Be careful while parsing floating point values
parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5
ðÂÂÂĄ Explanation: ParseInt
takes a string argument and returns an integer of the specified radix. ParseInt
also strips anything after and including the first non-digit in the string parameter. 0.000001
is converted to a string "0.000001"
and the parseInt
returns 0
. When 0.0000001
is converted to a string it is treated as "1e-7"
and hence parseInt
returns 1
. 1/1999999
is interpreted as 5.00000250000125e-7
and parseInt
returns 5
.
Math with true
and false
Let's do some math:
true + true; // -> 2
(true + true) * (true + true) - true; // -> 3
HmmmÃĒÂÂĶ ðÂÂĪÂ
ðÂÂÂĄ Explanation:
We can coerce values to numbers with the Number
constructor. It's quite obvious that true
will be coerced to 1
:
Number(true); // -> 1
The unary plus operator attempts to convert its value into a number. It can convert string representations of integers and floats, as well as the non-string values true
, false
, and null
. If it cannot parse a particular value, it will evaluate to NaN
. That means we can coerce true
to 1
easier:
+true; // -> 1
When you're performing addition or multiplication, the ToNumber
method is invoked. According to the specification, this method returns:
If
argument
is true, return 1. Ifargument
is false, return +0.
That's why we can add boolean values as regular numbers and get correct results.
Corresponding sections:
HTML comments are valid in JavaScript
You will be impressed, but <!--
(which is known as HTML comment) is a valid comment in JavaScript.
// valid comment
<!-- valid comment too
ðÂÂÂĄ Explanation:
Impressed? HTML-like comments were intended to allow browsers that didn't understand the <script>
tag to degrade gracefully. These browsers, e.g. Netscape 1.x are no longer popular. So there is really no point in putting HTML comments in your script tags anymore.
Since Node.js is based on the V8 engine, HTML-like comments are supported by the Node.js runtime too. Moreover, they're a part of the specification:
NaN
is not a number
Type of NaN
is a 'number'
:
typeof NaN; // -> 'number'
ðÂÂÂĄ Explanation:
Explanations of how typeof
and instanceof
operators work:
[]
and null
are objects
typeof []; // -> 'object'
typeof null; // -> 'object'
// however
null instanceof Object; // false
ðÂÂÂĄ Explanation:
The behavior of typeof
operator is defined in this section of the specification:
According to the specification, the typeof
operator returns a string according to Table 37: typeof
Operator Results. For null
, ordinary, standard exotic and non-standard exotic objects, which do not implement [[Call]]
, it returns the string "object"
.
However, you can check the type of an object by using the toString
method.
Object.prototype.toString.call([]);
// -> '[object Array]'
Object.prototype.toString.call(new Date());
// -> '[object Date]'
Object.prototype.toString.call(null);
// -> '[object Null]'
Magically increasing numbers
999999999999999; // -> 999999999999999
9999999999999999; // -> 10000000000000000
10000000000000000; // -> 10000000000000000
10000000000000000 + 1; // -> 10000000000000000
10000000000000000 + 1.1; // -> 10000000000000002
ðÂÂÂĄ Explanation:
This is caused by IEEE 754-2008 standard for Binary Floating-Point Arithmetic. At this scale, it rounds to the nearest even number. Read more:
- 6.1.6 The Number Type
- IEEE 754 on Wikipedia
Precision of 0.1 + 0.2
A well-known joke. An addition of 0.1
and 0.2
is deadly precise:
0.1 + 0.2; // -> 0.30000000000000004
0.1 + 0.2 === 0.3; // -> false
ðÂÂÂĄ Explanation:
The answer for the ÃĒÂÂIs floating point math broken?ÃĒÂÂ question on StackOverflow:
The constants
0.2
and0.3
in your program will also be approximations to their true values. It happens that the closestdouble
to0.2
is larger than the rational number0.2
but that the closestdouble
to0.3
is smaller than the rational number0.3
. The sum of0.1
and0.2
winds up being larger than the rational number0.3
and hence disagreeing with the constant in your code.
This problem is so known that there is even a website called 0.30000000000000004.com. It occurs in every language that uses floating-point math, not just JavaScript.
Patching numbers
You can add your own methods to wrapper objects like Number
or String
.
Number.prototype.isOne = function() {
return Number(this) === 1;
};
(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0).isOne(); // -> false
(7).isOne(); // -> false
ðÂÂÂĄ Explanation:
Obviously, you can extend the Number
object like any other object in JavaScript. However, it's not recommended if the behavior of the defined method is not a part of the specification. Here is the list of Number
's properties:
Comparison of three numbers
1 < 2 < 3; // -> true
3 > 2 > 1; // -> false
ðÂÂÂĄ Explanation:
Why does this work that way? Well, the problem is in the first part of an expression. Here's how it works:
1 < 2 < 3; // 1 < 2 -> true
true < 3; // true -> 1
1 < 3; // -> true
3 > 2 > 1; // 3 > 2 -> true
true > 1; // true -> 1
1 > 1; // -> false
We can fix this with Greater than or equal operator (>=
):
3 > 2 >= 1; // true
Read more about Relational operators in the specification:
Funny math
Often the results of arithmetic operations in JavaScript might be quite unexpected. Consider these examples:
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
ðÂÂÂĄ Explanation:
What's happening in the first four examples? Here's a small table to understand addition in JavaScript:
Number + Number -> addition
Boolean + Number -> addition
Boolean + Boolean -> addition
Number + String -> concatenation
String + Boolean -> concatenation
String + String -> concatenation
What about other examples? A ToPrimitive
and ToString
methods are being implicitly called for []
and {}
before addition. Read more about evaluation process in the specification:
- 12.8.3 The Addition Operator (
+
) - 7.1.1 ToPrimitive(
input
[,PreferredType
]) - 7.1.12 ToString(
argument
)
Notably, {} + []
here is the exception. The reason why it differs from [] + {}
is that, without parenthesis, it is interpreted as a code block and then a unary +, converting []
into a number. It sees the following:
{
// a code block here
}
+[]; // -> 0
To get the same output as [] + {}
we can wrap it in parenthesis.
({} + []); // -> [object Object]
Addition of RegExps
Did you know you can add numbers like this?
// Patch a toString method
RegExp.prototype.toString =
function() {
return this.source;
} /
7 /
-/5/; // -> 2
ðÂÂÂĄ Explanation:
Strings aren't instances of String
"str"; // -> 'str'
typeof "str"; // -> 'string'
"str" instanceof String; // -> false
ðÂÂÂĄ Explanation:
The String
constructor returns a string:
typeof String("str"); // -> 'string'
String("str"); // -> 'str'
String("str") == "str"; // -> true
Let's try with a new
:
new String("str") == "str"; // -> true
typeof new String("str"); // -> 'object'
Object? What's that?
new String("str"); // -> [String: 'str']
More information about the String constructor in the specification:
Calling functions with backticks
Let's declare a function which logs all params into the console:
function f(...args) {
return args;
}
No doubt, you know you can call this function like this:
f(1, 2, 3); // -> [ 1, 2, 3 ]
But did you know you can call any function with backticks?
f`true is ${true}, false is ${false}, array is ${[1, 2, 3]}`;
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// -> true,
// -> false,
// -> [ 1, 2, 3 ] ]
ðÂÂÂĄ Explanation:
Well, this is not magic at all if you're familiar with Tagged template literals. In the example above, f
function is a tag for template literal. Tags before template literal allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions. Example:
function template(strings, ...keys) {
// do something with strings and keysÃĒÂÂĶ
}
This is the magic behind famous library called ð styled-components, which is popular in the React community.
Link to the specification:
Call call call
Found by @cramforce
console.log.call.call.call.call.call.apply(a => a, [1, 2]);
ðÂÂÂĄ Explanation:
Attention, it could break your mind! Try to reproduce this code in your head: we're applying the call
method using the apply
method. Read more:
- 19.2.3.3 Function.prototype.call(
thisArg
, ...args
) - **19.2.3.1 ** Function.prototype.apply(
thisArg
,argArray
)
A constructor
property
const c = "constructor";
c[c][c]('console.log("WTF?")')(); // > WTF?
ðÂÂÂĄ Explanation:
Let's consider this example step-by-step:
// Declare a new constant which is a string 'constructor'
const c = "constructor";
// c is a string
c; // -> 'constructor'
// Getting a constructor of string
c[c]; // -> [Function: String]
// Getting a constructor of constructor
c[c][c]; // -> [Function: Function]
// Call the Function constructor and pass
// the body of new function as an argument
c[c][c]('console.log("WTF?")'); // -> [Function: anonymous]
// And then call this anonymous function
// The result is console-logging a string 'WTF?'
c[c][c]('console.log("WTF?")')(); // > WTF?
An Object.prototype.constructor
returns a reference to the Object
constructor function that created the instance object. In case with strings it is String
, in case with numbers it is Number
and so on.
Object as a key of object's property
{ [{}]: {} } // -> { '[object Object]': {} }
ðÂÂÂĄ Explanation:
Why does this work so? Here we're using a Computed property name. When you pass an object between those brackets, it coerces object to a string, so we get the property key '[object Object]'
and the value {}
.
We can make "brackets hell" like this:
({ [{}]: { [{}]: {} } }[{}][{}]); // -> {}
// structure:
// {
// '[object Object]': {
// '[object Object]': {}
// }
// }
Read more about object literals here:
Accessing prototypes with __proto__
As we know, primitives don't have prototypes. However, if we try to get a value of __proto__
for primitives, we would get this:
(1).__proto__.__proto__.__proto__; // -> null
ðÂÂÂĄ Explanation:
This happens because when something doesn't have a prototype, it will be wrapped into a wrapper object using the ToObject
method. So, step-by-step:
(1).__proto__; // -> [Number: 0]
(1).__proto__.__proto__; // -> {}
(1).__proto__.__proto__.__proto__; // -> null
Here is more information about __proto__
:
`${{Object}}`
What is the result of the expression below?
`${{ Object }}`;
The answer is:
// -> '[object Object]'
ðÂÂÂĄ Explanation:
We defined an object with a property Object
using Shorthand property notation:
{
Object: Object;
}
Then we've passed this object to the template literal, so the toString
method calls for that object. That's why we get the string '[object Object]'
.
Destructuring with default values
Consider this example:
let x,
{ x: y = 1 } = { x };
y;
The example above is a great task for an interview. What the value of y
? The answer is:
// -> 1
ðÂÂÂĄ Explanation:
let x,
{ x: y = 1 } = { x };
y;
// ÃĒÂÂ ÃĒÂÂ ÃĒÂÂ ÃĒÂÂ
// 1 3 2 4
With the example above:
- We declare
x
with no value, so it'sundefined
. - Then we pack the value of
x
into the object propertyx
. - Then we extract the value of
x
using destructuring and want to assign it toy
. If the value is not defined, then we're going to use1
as the default value. - Return the value of
y
.
- Object initializer at MDN
Dots and spreading
Interesting examples could be composed with spreading of arrays. Consider this:
[...[..."..."]].length; // -> 3
ðÂÂÂĄ Explanation:
Why 3
? When we use the spread operator, the @@iterator
method is called, and the returned iterator is used to obtain the values to be iterated. The default iterator for string spreads a string into characters. After spreading, we pack these characters into an array. Then we spread this array again and pack it back to an array.
A '...'
string consists with three .
characters, so the length of resulting array is 3
.
Now, step-by-step:
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3
Obviously, we can spread and wrap the elements of an array as many times as we want:
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...[...'...']]] // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]] // -> [ '.', '.', '.' ]
// and so on ÃĒÂÂĶ
Labels
Not many programmers know about labels in JavaScript. They are kind of interesting:
foo: {
console.log("first");
break foo;
console.log("second");
}
// > first
// -> undefined
ðÂÂÂĄ Explanation:
The labeled statement is used with break
or continue
statements. You can use a label to identify a loop, and then use the break
or continue
statements to indicate whether a program should interrupt the loop or continue its execution.
In the example above, we identify a label foo
. After that console.log('first');
executes and then we interrupt the execution.
Read more about labels in JavaScript:
Nested labels
a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5
ðÂÂÂĄ Explanation:
Similar to previous examples, follow these links:
Insidious try..catch
What will this expression return? 2
or 3
?
(() => {
try {
return 2;
} finally {
return 3;
}
})();
The answer is 3
. Surprised?
ðÂÂÂĄ Explanation:
Is this multiple inheritance?
Take a look at the example below:
new class F extends (String, Array) {}(); // -> F []
Is this a multiple inheritance? Nope.
ðÂÂÂĄ Explanation:
The interesting part is the value of the extends
clause ((String, Array)
). The grouping operator always returns its last argument, so (String, Array)
is actually just Array
. That means we've just created a class which extends Array
.
A generator which yields itself
Consider this example of a generator which yields itself:
(function* f() {
yield f;
})().next();
// -> { value: [GeneratorFunction: f], done: false }
As you can see, the returned value is an object with its value
equal to f
. In that case, we can do something like this:
(function* f() {
yield f;
})()
.next()
.value()
.next()(
// -> { value: [GeneratorFunction: f], done: false }
// and again
function* f() {
yield f;
}
)()
.next()
.value()
.next()
.value()
.next()(
// -> { value: [GeneratorFunction: f], done: false }
// and again
function* f() {
yield f;
}
)()
.next()
.value()
.next()
.value()
.next()
.value()
.next();
// -> { value: [GeneratorFunction: f], done: false }
// and so on
// ÃĒÂÂĶ
ðÂÂÂĄ Explanation:
To understand why this works that way, read these sections of the specification:
A class of class
Consider this obfuscated syntax playing:
typeof new class {
class() {}
}(); // -> 'object'
It seems like we're declaring a class inside of class. Should be an error, however, we get the string 'object'
.
ðÂÂÂĄ Explanation:
Since ECMAScript 5 era, keywords are allowed as property names. So think about it as this simple object example:
const foo = {
class: function() {}
};
And ES6 standardized shorthand method definitions. Also, classes can be anonymous. So if we drop : function
part, we're going to get:
class {
class() {}
}
The result of a default class is always a simple object. And its typeof should return 'object'
.
Read more here:
Non-coercible objects
With well-known symbols, there's a way to get rid of type coercion. Take a look:
function nonCoercible(val) {
if (val == null) {
throw TypeError("nonCoercible should not be called with null or undefined");
}
const res = Object(val);
res[Symbol.toPrimitive] = () => {
throw TypeError("Trying to coerce non-coercible object");
};
return res;
}
Now we can use this like this:
// objects
const foo = nonCoercible({ foo: "foo" });
foo * 10; // -> TypeError: Trying to coerce non-coercible object
foo + "evil"; // -> TypeError: Trying to coerce non-coercible object
// strings
const bar = nonCoercible("bar");
bar + "1"; // -> TypeError: Trying to coerce non-coercible object
bar.toString() + 1; // -> bar1
bar === "bar"; // -> false
bar.toString() === "bar"; // -> true
bar == "bar"; // -> TypeError: Trying to coerce non-coercible object
// numbers
const baz = nonCoercible(1);
baz == 1; // -> TypeError: Trying to coerce non-coercible object
baz === 1; // -> false
baz.valueOf() === 1; // -> true
ðÂÂÂĄ Explanation:
Tricky arrow functions
Consider the example below:
let f = () => 10;
f(); // -> 10
Okay, fine, but what about this:
let f = () => {};
f(); // -> undefined
ðÂÂÂĄ Explanation:
You might expect {}
instead of undefined
. This is because the curly braces are part of the syntax of the arrow functions, so f
will return undefined. It is however possible to return the {}
object directly from an arrow function, by enclosing the return value with brackets.
let f = () => ({});
f(); // -> {}
Arrow functions can not be a constructor
Consider the example below:
let f = function() {
this.a = 1;
};
new f(); // -> f { 'a': 1 }
Now, try do to the same with an arrow function:
let f = () => {
this.a = 1;
};
new f(); // -> TypeError: f is not a constructor
ðÂÂÂĄ Explanation:
Arrow functions cannot be used as constructors and will throw an error when used with new
. Because they have a lexical this
, and do not have a prototype
property, so it would not make much sense.
arguments
and arrow functions
Consider the example below:
let f = function() {
return arguments;
};
f("a"); // -> { '0': 'a' }
Now, try do to the same with an arrow function:
let f = () => arguments;
f("a"); // -> Uncaught ReferenceError: arguments is not defined
ðÂÂÂĄ Explanation:
Arrow functions are a lightweight version of regular functions with a focus on being short and lexical this
. At the same time arrow functions do not provide a binding for the arguments
object. As a valid alternative use the rest parameters
to achieve the same result:
let f = (...args) => args;
f("a");
- Arrow functions at MDN.
Tricky return
return
statement is also tricky. Consider this:
(function() {
return
{
b: 10;
}
})(); // -> undefined
ðÂÂÂĄ Explanation:
return
and the returned expression must be in the same line:
(function() {
return {
b: 10
};
})(); // -> { b: 10 }
This is because of a concept called Automatic Semicolon Insertion, which automagically inserts semicolons after most newlines. In the first example, there is a semicolon inserted between the return
statement and the object literal, so the function returns undefined
and the object literal is never evaluated.
Chaining assignments on object
var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
foo.x; // -> undefined
foo; // -> {n: 2}
bar; // -> {n: 1, x: {n: 2}}
From right to left, {n: 2}
is assigned to foo, and the result of this assignment {n: 2}
is assigned to foo.x, that's why bar is {n: 1, x: {n: 2}}
as bar is a reference to foo. But why foo.x is undefined while bar.x is not ?
ðÂÂÂĄ Explanation:
Foo and bar references the same object {n: 1}
, and lvalues are resolved before assignations. foo = {n: 2}
is creating a new object, and so foo is updated to reference that new object. The trick here is foo in foo.x = ...
as a lvalue was resolved beforehand and still reference the old foo = {n: 1}
object and update it by adding the x value. After that chain assignments, bar still reference the old foo object, but foo reference the new {n: 2}
object, where x is not existing.
It's equivalent to:
var foo = { n: 1 };
var bar = foo;
foo = { n: 2 }; // -> {n: 2}
bar.x = foo; // -> {n: 1, x: {n: 2}}
// bar.x point to the address of the new foo object
// it's not equivalent to: bar.x = {n: 2}
Accessing object properties with arrays
var obj = { property: 1 };
var array = ["property"];
obj[array]; // -> 1
// this also works with nested arrays
var nestedArray = [[[[[[[[[["property"]]]]]]]]]];
obj[nestedArray]; // -> 1
What about pseudo-multidimensional arrays?
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
ðÂÂÂĄ Explanation:
The brackets []
operator converts the passed expression using toString
. Converting a one-element array to a string is akin to converting the contained element to the string:
["property"].toString(); // -> 'property'
Number.toFixed()
display different numbers
Number.toFixed()
can behave a bit strange in different browsers. Check out this example:
(0.7875).toFixed(3);
// Firefox: -> 0.787
// Chrome: -> 0.787
// IE11: -> 0.788
(0.7876).toFixed(3);
// Firefox: -> 0.788
// Chrome: -> 0.788
// IE11: -> 0.788
ðÂÂÂĄ Explanation:
While your first instinct may be that IE11 is correct and Firefox/Chrome are wrong, the reality is that Firefox/Chrome are more directly obeying standards for numbers (IEEE-754 Floating Point), while IE11 is minutely disobeying them in (what is probably) an effort to give clearer results.
You can see why this occurs with a few quick tests:
// Confirm the odd result of rounding a 5 down
(0.7875).toFixed(3); // -> 0.787
// It looks like it's just a 5 when you expand to the
// limits of 64-bit (double-precision) float accuracy
(0.7875).toFixed(14); // -> 0.78750000000000
// But what if you go beyond the limit?
(0.7875).toFixed(20); // -> 0.78749999999999997780
Floating point numbers are not stored as a list of decimal digits internally, but through a more complicated methodology that produces tiny inaccuracies that are usually rounded away by toString and similar calls, but are actually present internally.
In this case, that "5" on the end was actually an extremely tiny fraction below a true 5. Rounding it at any reasonable length will render it as a 5... but it is actually not a 5 internally.
IE11, however, will report the value input with only zeros appended to the end even in the toFixed(20) case, as it seems to be forcibly rounding the value to reduce the troubles from hardware limits.
See for reference NOTE 2
on the ECMA-262 definition for toFixed
.
Math.max()
less than Math.min()
I find this example hilarious:
Math.min() > Math.max(); // -> true
Math.min() < Math.max(); // -> false
ðÂÂÂĄ Explanation:
This is a simple one. Let's consider each part of this expression separately:
Math.min(); // -> Infinity
Math.max(); // -> -Infinity
Infinity > -Infinity; // -> true
Why so? Well, Math.max()
is not the same thing as Number.MAX_VALUE
. It does not return the largest possible number.
Math.max
takes arguments, tries to convert the to numbers, compares each one and then returns the largest remaining. If no arguments are given, the result is ÃĒÂÂÃĒÂÂ. If any value is NaN
, the result is NaN
.
The opposite is happening for Math.min
. Math.min
returns ÃĒÂÂ, if no arguments are given.
- 15.8.2.11 Math.max
- 15.8.2.11 Math.min
- Why is
Math.max()
less thanMath.min()
? by Charlie Harvey
Comparing null
to 0
The following expressions seem to introduce a contradiction:
null == 0; // -> false
null > 0; // -> false
null >= 0; // -> true
How can null
be neither equal to nor greater than 0
, if null >= 0
is actually true
? (This also works with less than in the same way.)
ðÂÂÂĄ Explanation:
The way these three expressions are evaluated are all different and are responsible for producing this unexpected behavior.
First, the abstract equality comparison null == 0
. Normally, if this operator can't compare the values on either side properly, it converts both to numbers and compares the numbers. Then, you might expect the following behavior:
// This is not what happens
(null == 0 + null) == +0;
0 == 0;
true;
However, according to a close reading of the spec, the number conversion doesn't actually happen on a side that is null
or undefined
. Therefore, if you have null
on one side of the equal sign, the other side must be null
or undefined
for the expression to return true
. Since this is not the case, false
is returned.
Next, the relational comparison null > 0
. The algorithm here, unlike that of the abstract equality operator, will convert null
to a number. Therefore, we get this behavior:
null > 0
+null = +0
0 > 0
false
Finally, the relational comparison null >= 0
. You could argue that this expression should be the result of null > 0 || null == 0
; if this were the case, then the above results would mean that this would also be false
. However, the >=
operator in fact works in a very different way, which is basically to take the opposite of the <
operator. Because our example with the greater than operator above also holds for the less than operator, that means this expression is actually evaluated like so:
null >= 0;
!(null < 0);
!(+null < +0);
!(0 < 0);
!false;
true;
Same variable redeclaration
JS allows to redeclare variables:
a;
a;
// This is also valid
a, a;
Works also in strict mode:
var a, a, a;
var a;
var a;
ðÂÂÂĄ Explanation:
All definitions are merged into one definition.
Default behavior Array.prototype.sort()
Imagine that you need to sort an array of numbers.
[10, 1, 3].sort(); // -> [ 1, 10, 3 ]
ðÂÂÂĄ Explanation:
The default sort order is built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.
Hint
Pass compareFn
if you try to sort anything but string.
[10, 1, 3].sort((a, b) => a - b); // -> [ 1, 3, 10 ]
resolve() won't return Promise instance
const theObject = {
a: 7
};
const thePromise = new Promise((resolve, reject) => {
resolve(theObject);
}); // Promise instance object
thePromise.then(value => {
console.log(value === theObject); // > true
console.log(value); // > { a: 7 }
});
The value
which is resolved from thePromise
is exactly theObject
.
How about input another Promise
into the resolve
function?
const theObject = new Promise((resolve, reject) => {
resolve(7);
}); // Promise instance object
const thePromise = new Promise((resolve, reject) => {
resolve(theObject);
}); // Promise instance object
thePromise.then(value => {
console.log(value === theObject); // > false
console.log(value); // > 7
});
ðÂÂÂĄ Explanation:
This function flattens nested layers of promise-like objects (e.g. a promise that resolves to a promise that resolves to something) into a single layer.
The specification is ECMAScript 25.6.1.3.2 Promise Resolve Functions. But it is not quite human-friendly.
{}{}
is undefined
Write them in the console. They will return the value defined in the last object.
{}{}; // -> undefined
{}{}{}; // -> undefined
{}{}{}{}; // -> undefined
{foo: 'bar'}{}; // -> 'bar'
{}{foo: 'bar'}; // -> 'bar'
{}{foo: 'bar'}{}; // -> 'bar'
{a: 'b'}{c:' d'}{}; // -> 'd'
{a: 'b', c: 'd'}{}; // > SyntaxError: Unexpected token ':'
({}{}); // > SyntaxError: Unexpected token '{'
ðÂÂÂĄ Explanation:
When inspecting each {}
, they returns undefined. If you inspect {foo: 'bar'}{}
, you will find {foo: 'bar'}
is 'bar'
.
There are two meanings for {}
: an object or a block. For example, the {}
in () => {}
means block. So we need to use () => ({})
to return an object.
Let's use {foo: 'bar'}
as a block. Write this snippet in your console:
if (true) {
foo: "bar";
} // -> 'bar'
Surprisingly, it behaviors the same! You can guess here that {foo: 'bar'}{}
is a block.
arguments
binding
Consider this function:
function a(x) {
arguments[0] = "hello";
console.log(x);
}
a(); // > undefined
a(1); // > "hello"
ðÂÂÂĄ Explanation:
arguments
is an Array-like object that contains the values of the arguments passed to that function. When no arguments are passed, then there's no x
to override.
- The arguments object on MDN
An alert
from hell
This on is literally from hell:
[666]["\155\141\160"]["\143\157\156\163\164\162\165\143\164\157\162"](
"\141\154\145\162\164(666)"
)(666); // alert(666)
ðÂÂÂĄ Explanation:
This one is based on octal escape sequences and multiple strings.
Any character with a character code lower than 256 (i.e. any character in the extended ASCII range) can be escaped using its octal-encoded character code, prefixed with \
. An example above is basically and alert
ecoded by octal escape sequances.
An infinite timeout
Guess what would happen if we set an infinite timeout?
setTimeout(() => console.log("called"), Infinity); // -> <timeoutId>
// > 'called'
It will executed immediately instead of infinity delay.
ðÂÂÂĄ Explanation:
Usually, runtime stores the delay as a 32-bit signed integer internally. This causes an integer overflow, resulting in the timeout being executed immediately.
For example, in Node.js we will get this warning:
(node:1731) TimeoutOverflowWarning: Infinity does not fit into a 32-bit signed integer.
Timeout duration was set to 1.
(Use `node --trace-warnings ...` to show where the warning was created)
A setTimeout
object
Guess what would happen if we set an callback that's not a function to setTimeout
?
setTimeout(123, 100); // -> <timeoutId>
// > 'called'
This is fine.
setTimeout('{a: 1}', 100); // -> <timeoutId>
// > 'called'
This is also fine.
setTimeout({a: 1}, 100); // -> <timeoutId>
// > 'Uncaught SyntaxError: Unexpected identifier setTimeout (async) (anonymous) @ VM__:1'
This throws an SyntaxError.
Note that this can easily happen if your function returns an object and you call it here instead of passing it! What if the content - policy is set to self
?
setTimeout(123, 100); // -> <timeoutId>
// > console.error("[Report Only] Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'report-sample' 'self' ")
The console refuses to run it at all!
ðÂÂÂĄ Explanation:
WindowOrWorkerGlobalScope.setTimeout()
can be called with code
as first argument, which will be passed on to eval
, which is bad. Eval will coerce her input to String, and evaluate what is produced, so Objects becomes '[object Object]'
which has hmmm ... an 'Unexpected identifier'
!
- eval() on MDN (don't use this)
- WindowOrWorkerGlobalScope.setTimeout() on MDN
- Content Security Policy
- Timers on W3C
Double dot
Let's try to coerce a number to a string:
27.toString() // > Uncaught SyntaxError: Invalid or unexpected token
Maybe we should try with two dots?
27..toString(); // -> '27'
But why doesn't first example work?
ðÂÂÂĄ Explanation:
It's just a language grammar limitation.
The .
character presents an ambiguity. It can be understood to be the member operator, or a decimal, depending on its placement.
The specification's interpretation of the .
character in that particular position is that it will be a decimal. This is defined by the numeric literal syntax of ECMAScript.
You must always use parenthesis or an addition dot to make such expression valid.
(27).toString(); // -> '27'
// or
27..toString(); // -> '27'
- Usage of toString in JavaScript on StackOverflow
- Why does 10..toString() work, but 10.toString() does not?
Extra Newness
I present this as an oddity for your amusement.
class Foo extends Function {
constructor(val) {
super();
this.prototype.val = val;
}
}
new new Foo(":D")().val; // -> ':D'
ðÂÂÂĄ Explanation:
Constructors in JavaScript are just functions with some special treatment. By extending Function using the class syntax you create a class that, when instantiated, is now a function, which you can then additionally instantiate.
While not exhaustively tested, I believe the last statement can be analyzed thus:
new new Foo(":D")().val(new newFooInstance()).val;
veryNewFooInstance.val;
// -> ':D'
As a tiny addendum, doing new Function('return "bar";')
of course creates a function with the body return "bar";
. Since super()
in the constructor of our Foo
class is calling Function
's constructor, it should come as no surprise now to see that we can additionally manipulate things in there.
class Foo extends Function {
constructor(val) {
super(`
this.val = arguments[0];
`);
this.prototype.val = val;
}
}
var foo = new new Foo(":D")("D:");
foo.val; // -> 'D:'
delete foo.val; // remove the instance prop 'val', deferring back to the prototype's 'val'.
foo.val; // -> ':D'
Why you should use semicolons
Writing some standard JavaScriptÃĒÂÂĶ and then BOOM!
class SomeClass {
["array"] = []
["string"] = "str"
}
new SomeClass().array; // -> 'str'
What the ÃĒÂÂĶ?
ðÂÂÂĄ Explanation:
Once again, this is all thanks to the Automatic Semicolon Insertion.
An example above is basically the same as:
class SomeClass {
["array"] = ([]["string"] = "str");
}
You basically assign a string str
into an array
property.
- An original tweet with an example by Ryan Cavanaugh
- TC39 meeting when they debated about it
Split a string by a space
Have you ever tried to split a string by a space?
"".split(""); // -> []
// butÃĒÂÂĶ
"".split(" "); // -> [""]
ðÂÂÂĄ Explanation:
This is expected behaviour. Its responsibility is to divide the input string every time a separator occurs in that input string. When you pass in an empty string it'll never find a separator and thus return that string.
Let's quote the specification:
The substrings are determined by searching from left to right for occurrences of
separator
; these occurrences are not part of any String in the returned array, but serve to divide up the String value.
- 22.1.3.21 String.prototype.split
- An original tween with an example by Ryan Cavanaugh
- A tween with an explanation by Nabil Tharwat
A stringified string
This caused a bug that I've been solving for a few days:
JSON.stringify("production") === "production"; // -> false
ðÂÂÂĄ Explanation:
Let's see what JSON.stringify
is returning:
JSON.stringify("production"); // -> '"production"'
It is actually a stringified string, so it's true:
'"production"' === "production"; // -> false
Non-strict comparison of a number to true
1 == true; // -> true
// butÃĒÂÂĶ
Boolean(1.1); // -> true
1.1 == true; // -> false
ðÂÂÂĄ Explanation:
According to the specification:
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If
Type(x)
is Number andType(y)
is String, return the result of the comparisonx == ! ToNumber(y)
.
So this comparison is performed like this:
1 == true;
1 == Number(true);
1 == 1; // -> true
// butÃĒÂÂĶ
1.1 == true;
1.1 == Number(true);
1.1 == 1; // -> false
ð Other resources
- wtfjs.com ÃĒÂÂ a collection of those very special irregularities, inconsistencies and just plain painfully unintuitive moments for the language of the web.
- Wat ÃĒÂÂ A lightning talk by Gary Bernhardt from CodeMash 2012
- What the... JavaScript? ÃĒÂÂ Kyle Simpsons talk for Forward 2 attempts to ÃĒÂÂpull out the crazyÃĒÂÂ from JavaScript. He wants to help you produce cleaner, more elegant, more readable code, then inspire people to contribute to the open source community.
- Zeros in JavaScript ÃĒÂÂ a comparison table of
==
,===
,+
and*
in JavaScript.
ðÂÂĪ Supporting
Hi! I work on this project in my spare time, in addition to my primary job. I hope you enjoy reading it. If you do, please, consider supporting me ðÂÂÂ.
Every single donation is important. Your donation is gonna make a clear statement: My work is valued.
ð Thank you for your support! ðÂÂÂ
Service | Link | Action |
---|---|---|
Patreon | Become a patron | |
BuyMeACoffee | Buy me a cup of ÃĒÂÂÃŊÂļ or ðÂÂĨÂĪ | |
Bitcoin | 1EJsKs6rPsqa7QLoVLpe3wgcdL9Q8WmDxE | |
Ethereum | 0x6aF39C917359897ae6969Ad682C14110afe1a0a1 |
ÃĒÂÂ ÃŊÂļÂ Note: I live in Ukraine and services like PayPal and Stripe don't work with Ukrainian bank accounts. This means there's no way for me to set up GitHub Sponsors, OpenCollective, or services relied on them. Sorry, those are the only ways you can support me for now.
ð License
ÂĐ Denys Dovhan
Top Related Projects
A book series on JavaScript. @YDKJS on twitter.
A long list of (advanced) JavaScript questions, and their explanations :sparkles:
:bathtub: Clean Code concepts adapted for JavaScript
JavaScript Style Guide
ð Algorithms and data structures implemented in JavaScript with explanations and links to further readings
ð 33 JavaScript concepts every developer should know.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot