Skip to content

Latest commit

 

History

History
2685 lines (1905 loc) · 94.3 KB

README-fa-ir.md

File metadata and controls

2685 lines (1905 loc) · 94.3 KB

SWUbanner

جاوا اسکریپت چه کوفتی هست؟

WTFPL 2.0 NPM version Patreon Buy Me A Coffee

یه لیست از مثال های جالب و طنزآمیز جاوا اسکریپت

جاوا اسکریپت یه زبون عالی هست. سینتکس ساده، اکوسیستم بزرگ و از همه مهم تر یه کامیونیتی عالی داره.

و در عین حال، هممون میدونیم که جاوا اسکریپت خیلی زبون جالبی با بخش های سختی هست. بعضی از اونها میتونن خیلی سریع کار هر روزمون رو به یه تجربه‌ی بد تبدیل کنن و بعضی از اونها میتونن باعث بشن با صدای بلند بخندیم.

صاحب ایده‌ی اصلی WTFJS برای Brian Leroux هست. بخش بزرگی از این لیست از سخنرانی همین فرد توی “WTFJS” at dotJS 2012 الهام گرفته شده:

dotJS 2012 - Brian Leroux - WTFJS

پکیج نود جی‌اس کتاب:

میتونید این کتابچه رو با npm نصب کنید. کافیه بزنید:

$ npm install -g wtfjs

حالا باید بتونید wtfjs رو توی ترمینال اجرا کنید. این دستور کتابچه رو داخل $PAGER انتخاب شده باز میکنه. در غیر این صورت، میتونید خوندن رو اینجا ادامه بدید.

منبع اصلی اینجا در دسترس هست: https://github.com/denysdovhan/wtfjs

ترجمه ها

فعلا این ترجمه ها از wtfjs موجود هست:

مشارکت برای ترجمه

توجه: ترجمه ها توسط مترجم هاشون نگهداری میشن، ممکنه همه‌ی مثال ها رو نداشته باشن و یا مطالب ترجمه شده قدیمی باشن.

فهرست مطالب

💪🏻 Motivation

فقط برای سرگرمی

“فقط برای سرگرمی: داستان یک انقلاب تصادفی”، Linus Torvalds

در صورت امکان هدف اولیه‌ی این لیست، جمع کردن یکسری مثال های عجیب و توضیح دادن اینکه چطور کار میکنن هست. صرفا چون جالبه یاد گرفتن چیزی که قبلا نمیدونستیمش.

اگر تازه کار هستید، میتونید از این مطالب استفاده کنید تا توی جاوا اسکریپت عمیق تر بشید. امیدوارم این قسمت باعث بشه انگیزه‌ی بیشتری داشته باشید برای صرف کردن وقت بیشتری روی خوندن داکیومنت.

اگه یه برنامه نویس حرفه ای هستید، میتونید این مثال هارو به عنوان یک رفرنس عالی برای همه‌ی قسمت های عجیب و غریب و غیر منتظره‌ی جاوا اسکریپت محبوبمون در نظر بگیرید.

در هر صورت، مطالعه کنید چون قطعا قراره چیزای جدیدی یاد بگیرید.

⚠️ توجه: اگر از خوندن این رفرنس لذت میبرید، لطفا حمایت از نویسنده‌ش رو در نظر بگیرید.

✍🏻 Notation

از // -> برای نشون دادن نتیجه‌ی عبارت ها استفاده میشه. برای مثال:

1 + 1; // -> 2

منظور از // > نتیجه‌ی console.log یا هر خروجی دیگه‌ای هست. برای مثال:

console.log("hello, world!"); // > hello, world!

// کامنت هست که برای توضیحات بیشتر استفاده میشه. مثل:

// Assigning a function to foo constant
const foo = function() {};

👀 Examples

[] is equal ![]

آرایه، مساوی !آرایه هست:

[] == ![]; // -> true

💡 توضیح:

عملگر == توی جاوا اسکریپت مقدار هر دو طرف رو تبدیل میکنه به عدد تا اونها رو مقایسه کنه، توی این مثال هر دو طرف تبدیل به 0 میشن به دلایل مختلفی. آرایه ها مقدار های truthy هستن پس سمت راست مخالف یه مقدار truthy میشه false‍‍ که به 0 تبدیل میشه. همچنین در سمت چپ یه آرایه‌ی خالی هنگام تبدیل شدن به عدد، میشه 0 بدون اینکه تبدیل به boolean بشه و آرایه های خالی تبدیل به 0 میشن با وجود truthy بودن.

نحوه‌ی اجرا شدن عبارت:

+[] == +![];
0 == +false;
0 == 0;
true;

همچنین ببینید: [] is truthy, but not true

true is not equal ![], but not equal [] too

آرایه، مساوی true نیست ولی !آرایه هم مساوی true نیست، هر دو تا مساوی false هستن. برای مثال:

true == []; // -> false 
true == ![]; // -> false 
false == []; // -> true 
false == ![]; // -> true

💡 توضیح:‍‍

قبلا هم گفتیم، آرایه مساوی true نیست، !آرایه هم همینطور. دلیلش هم این هست که همونطور که قبلا گفتیم، عملگر == مقدار ها رو تبدیل به عدد میکنه و سپس مقایسه میکنه:

toNumber(true); // -> 1 
toNumber([]); // -> 0 
1 == 0; // -> false

و حالا دلیل اینکه چرا زمانی که برابر false میکنیم هر دو رو، true برمیگردونن:

toNumber(false); // -> 0
toNumber([]); // -> 0
0 == 0; // -> true

true is false

اگر بخواید !!"false" و !!"true" رو چه با عملگر == و چه با === تست کنید، true برمیگردونه.

💡 توضیح:

اول با این مثال شروع می‌کنيم که:

true == "true"; // -> false 
false == "false"; // -> false

چون عملگر == تبدیل میکنه به عدد، توی مثال اول از چپ به راست 1 یا همون true که تبدیل شده به عدد شده و سپس NaN که نتیجه ی تبدیل "false" به عدد هست، برابر نیستن. مثال دوم هم به همین شکل هست با این تفاوت که false هنگام تبدیل شدن به عدد، میشه 0. حالا برگردیم به بحث اصلیمون:

!!"false"; // -> true 
!!"true"; // -> true

توی مثال اول چون String خالی نیست، به عنوان یک مقدار true در نظر گرفته میشه، با ! اول تبدیل میشه به false و با عملگر ! دوم دوباره تبدیل میشه به true. توی مثال دوم هم به همین صورت هست.

baNaNa

یه جک خیلی قدیمی جاوا اسکریپت:

"b" + "a" + +"a" + "a"; // -> 'baNaNa'

💡 توضیح:

اتفاقی که اینجا میوفته، اینه که اینجا + +"a" عملگر + قبل از "a"‌ باعث تبدیل شدن "a" به تایپ number میشه که حاصل این قضیه، برابر با NaN هست پس در واقع:

"b" + "a" + NaN + "a"; // -> 'baNaNa

NaN is not a NaN

حاصل این عبارت میشه false:

NaN === NaN; // -> false

💡 توضیح:

این نتیجه برمی‌گرده به نحوه‌ی کار این عملگر، اگر تایپ مقدار دو طرف متفاوت باشه false برمیگردونه و اگر تایپ یکی از مقدار ها number باشه، کافیه یکی از اون مقدار ها NaN باشه تا این عملگر false برگردونه یا توضیح اصلی‌ای که تو داکیومنت نوشته شده:

  1. If Type(x) is different from Type(y), return false.
  2. If Type(x) is Number, then
    1. If x is NaN, return false.
    2. If y is NaN, return false.
    3. … … …

7.2.14 Strict Equality Comparison

“What is the rationale for all comparisons returning false for IEEE754 NaN values?” at StackOverflow

Object.is() and === weird cases (موارد عجیب)

متد Object.is() تعیین میکنه که دو مقدار یکسان هستن یا نه، مثل === کار میکنه ولی یکسری موارد عجیبی وجود داره:

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

💡 توضیح:

همونطور که توی مطالب قبلی گفتیم، NaN و NaN هنگام مقایسه با عملگر === مساوی نیستن ولی هنگام استفاده از Object.is این مشکل حل شده.‌ 0 و 0- هم مساوی هستن ولی مقدارشون به طور دقیق یکسان نیست.

همچنین برای توضیحات بیشتر در مورد NaN === NaN، میتونید این منابع رو هم مطالعه بکنید.

It's a fail

شاید باور نکنید ولی...

(![] + [])[+[]] +
  (![] + [])[+!+[]] +
  ([![]] + [][[]])[+!+[] + [+[]]] +
  (![] + [])[!+[] + !+[]];
// -> 'fail'

💡 توضیح:

با تیکه تیکه کردن این کد به بخش های کوچک تر، متوجه این الگو میشیم:

![] + []; // -> 'false'
![]; // -> false

با اضافه کردن [] به false، به خاطر یکسری عملیات های داخلی زبان (binary + Operator -> ToPrimitive -> [[DefaultValue]]) در نهایت به همچین چیزی تبدیل میشه:

![] + [].toString(); // 'false'

که می‌تونیم به اولین کاراکترش دسترسی داشته باشیم:

"false"[0]; // -> 'f'

بقیه‌ی حروف هم ساده هستن ولی i به صورت تشکیل یک String با مقدار 'falseundefined' و گرفتن ایندکس دهمش با استفاده از ['10'] به دست میاد. چند تا مثال جالب بیشتر:

+![]          // -> 0
+!![]         // -> 1
!![]          // -> true
![]           // -> false
[][[]]        // -> undefined
+!![] / +![]  // -> Infinity
[] + {}       // -> "[object Object]"
+{}           // -> NaN

[] is truthy, but not true

یک آرایه، مقدار truthyای هست ولی مساوی true نیست.

!![] // -> true 
[] == true // -> false

💡 توضیح:

وقتی مقدار های truthy رو با true مقایسه میکنیم، false میگیریم چون مقدار های truthy دقیقا مساوی true نیستن. صرفا مقدار هایی هستن که هنگام تبدیل شدن به boolean، میشن true. هر چند یکسری استثنا ها وجود دارن، مثلا هنگام مقایسه‌ی 1 با true چون وقتی true به number تبدیل میشه نتیجه 1 هست، true برمیگردونه:

1 == true // -> true

بعد از تبدیل true به number:

1 == 1 // -> true

null is falsy, but not false

با وجود اینکه null یک مقدار falsy هست، مساوی false نیست:

!!null; // -> false
null == false; // -> false

ولی مقدار های falsy دیگه مثل 0 یا ''، مساوی false هستن:

0 == false; // -> true
"" == false; // -> true

💡 توضیح:

توضیح این قسمت هم مربوط میشه به توضیحات مثال های قبل، داخل این لینک میتونید مطالعه بکنید دلیلش رو به صورت عمیق تر:

document.all is an object, but it is undefined

⚠️ این قسمت مربوط میشه به جاوا اسکریپت سمت مرورگر، نه ران‌تایم هایی مثل نود جی اس ⚠️

با وجود اینکه document.all یک آبجکت آرایه مانندی هست و بهمون دسترسی به DOM nodes صفحه رو میده، وقتی روش typeof زده بشه undefined برمیگردونه.

document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'

ولی document.all مساوی undefined نیست.

document.all === undefined; // -> false
document.all === null; // -> false

و همزمان

document.all == null; // -> true

💡 توضیح:

متد document.all استفاده میشه برای دسترسی به المنت های DOM داخل ورژن های قدیمی IE. با اینکه یک استاندارد نبود، به طور گسترده‌ای داخل کد های قدیمی جاوا اسکریپت استفاده شده. زمانی که با api های جدید مثل document.getElementById این متد به یک متد منسوخ شده تبدیل شد و لازم بود که تصمیم گرفته بشه چه کاری باهاش انجام بشه. به خاطر استفاده‌ی گسترده‌اش تصمیم گرفتن api رو همچنان نگه دارن ولی یه نقص عمدی معرفی کنن. دلیلی که false برمیگردونه هنگام استفاده از Strict Equality Comparison با undefined ولی true با Abstract Equality Comparison، به خاطر همون نقص عمدی هست.

“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 حداقل مقداری هست که میتونید داشته باشید، که بزرگ‌تر از صفر هست:

Number.MIN_VALUE > 0; // -> true

💡 توضیح:

مقدار Number.MIN_VALUE یک BIGINT هست که به صورت 5e-324 نمایش داده میشه و در واقع کوچک‌ترین مقدار مثبتی هست که میتونید داخل float نمایش بدید. یا به عبارتی دیگه، نزدیک ترین مقدار ممکن به صفر هست و حداکثر دقتی که float میتونه نمایش بده رو داره.

همچنین کمترین مقدار هم Number.NEGATIVE_INFINITY هست که در نگاه دقیق، عدد نیست.

“Why is 0 less than Number.MIN_VALUE in JavaScript?” at StackOverflow

function is not a function

⚠️ این باگ فقط توی V8 ورژن 5.5 یا کمتر و ورژن نود جی اس 7 یا کمتر وجود داره ⚠️

اکثرتون در مورد ارور undefined is not a function میدونید، ولی این چطور؟

// Declare a class which extends null
class Foo extends null {}
// -> [Function: Foo]

new Foo() instanceof null;
// > TypeError: function is not a function
// >     at … … …

💡 توضیح:

این نتیجه، قسمتی از یک اتفاق خاص نیست و صرفا یه باگ بوده که توی ورژن های بعدی رفع شده و نباید مشکلی وجود داشته باشه.

Super constructor null of Foo is not a constructor

این مبحث هم ادامه‌ی باگ قبلی که توی محیط مدرن (تست شده با کروم 71 و نود جی‌اس v11.8.0) هست.

class Foo extends null {}
new Foo() instanceof null;
// > TypeError: Super constructor null of Foo is not a constructor

💡 توضیح:

این یک باگ نیست، چون:

Object.getPrototypeOf(Foo.prototype); // -> null

اگر یک کلاس کانستراکتور نداشت، از prototype chain کانستراکتور یه مرحله بالاتر صدا زده میشه ولی اینجا توی اون هم کانستراکتور وجود نداره و null هم یه آبجکت هست:

typeof null === "object";

و به همین دلیل، میتونید ازش ارث‌بری کنید (هرچند توی دنیای OOP، چنین شرایطی اذیت کننده هست) ولی نمیتونید کانستراکتور null رو صدا بزنید و اگر این کد رو تغییر بدید:

class Foo extends null {
  constructor() {
    console.log("something");
  }
}

این ارور رو میبینید:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

و اگر توی کانستراکتور کلاس از super استفاده کنید:

class Foo extends null {
  constructor() {
    console.log(111);
    super();
  }
}

جی اس یه ارور throw میکنه:

TypeError: Super constructor null of Foo is not a constructor

Adding arrays

اگر بخواید دو تا آرایه رو جمع کنید چه اتفاقی می‌افته؟

[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'

💡 توضیح:

این نتیجه به صورت گام به گام اینطوری به دست میاد:

[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

شما یه آرایه با چهار المنت خالی ساختید ولی با وجود این، یه آرایه با سه المنت میگیرید که دلیلش قضیه‌ی trailing commas هست:

let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'

💡 توضیح:

قضیه‌ی Trailing commas که گاها بهش "final commas" هم میگن، میتونه هنگام اضافه کردن المنت جدید، پارامتر ها یا پراپرتی ها کاربرد داشته باشه. اگر می‌خواید پراپرتی جدیدی اضافه کنید، میتونید خیلی ساده یه خط جدید بدون تغییر دادن خط قبلی اضافه کنید اگه اون خط از trailing comma استفاده میکنه. همچنین باعث میشه diff های ورژن کنترلر هایی مثل گیت تمیز تر باشه و ادیت کردن کد کمتر دردسر ساز باشه.

Trailing commas at MDN

Array equality is a monster

برابر بودن آرایه ها توی جی‌اس، مثل یه اعجوبه می‌مونه همونطور که میتونید ببینید:

[] == ''   // -> 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

💡 توضیح:

باید این مثال ها رو با دقت نگاه کنید. این رفتار توی بخش 7.2.15 Abstract Equality Comparison توضیح داده شده.

undefined and Number

اگه هیچ آرگیومنتی به کانستراکتور Number ندیم، 0 میگیریم. مقدار پیش فرض آرگیومنت ها وقتی چیزی بهشون داده نمیشه، undefined هست پس شاید انتظار داشته باشید که Number بدون آرگیومنت undefined رو به عنوان پارامتر میگیره، هر چند وقتی undefined بدید بهش NaN میگیرید.

Number(); // -> 0
Number(undefined); // -> NaN

💡 توضیح:

با توجه به بدیهیات:

1- اگر هیچ آرگیومنتی هنگام اجرا شدن این فانکشن بهش داده نشه، خروجی میشه 0+.
2- وگرنه ToNumber(value) برگردونده میشه.
3- در صورتی که undefined داده بشه، ToNumber(undefined) باید NaN برگردونه.

بخش های مربوط برای مطالعه‌ی بیشتر:

parseInt is a bad guy

فانکشن parseInt به موارد عجیبش معروف هست:

parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15

💡 توضیح: این اتفاق به این دلیل می‌افته که parseInt تا زمانی به پارس کردن و خوندن حرف به حرف ادامه میده که به حرفی برسه که نشناسه. حرف f هم داخل 'f*ck' نماینده‌ی عدد 15 داخل هگزادسیمال هست.

تبدیل کردن Infinity به عدد یکی از این موارد هست:

//
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

در مورد null هم دقت کنید:

parseInt(null, 24); // -> 23

💡 توضیح:

اینجا null ابتدا تبدیل به تایپ string و "null" میشه و بعدش به عدد تبدیل میشه. از بیس های 0 تا 23، قابل تبدیل به هیچ عددی نیست پس NaN برمیگردونه ولی توی بیس 24 "n"، چهاردهمین حرف به اعداد اضافه میشه. توی بیس 31 هم "u"، بیست و یکمین حرف اضافه میشه و تمام String میتونه دی‌کد بشه. توی بیس های 37 و بیشتر هیچ عدد معتبری وجود نداره که بتونه بهش تبدیل بشه پس NaN برگردونده میشه.

“parseInt(null, 24) === 23… wait, what?” at StackOverflow

همچنین در مورد اوکتال ها هم:

parseInt("06"); // 6
parseInt("08"); // 8 if support ECMAScript 5
parseInt("08"); // 0 if not support ECMAScript 5

💡 توضیح: اگه string ورودی با "0" شروع بشه، نماینده‌ی بیس 8 (octal) یا بیس ده (decimal) هست. اینکه دقیقا کدومشون هست بستگی به ساختار داره. اکما اسکریپت 5 مشخص میکنه که بیس 10 (decimal) استفاده میشه ولی همه‌ی مرورگر ها هنوز این رو پشتیبانی نمیکنن، به همین دلیل همیشه موقع استفاده از parseInt بیس رو مشخص کنید.

فانکشن parseInt همیشه مقدار ورودی رو به String تبدیل میکنه:

parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1

هنگام تبدیل کردن float ها هم دقت کنید:

parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5

💡 توضیح: فانکشن ParseInt یه ورودی string میگیره و یه عدد با بیس مشخص شده برمیگردونه. ParseInt همچنین همه چیز بعد و خود کاراکتری که عدد نیست رو جدا میکنه داخل ورودی stringی که بهش داده میشه. 0.000001 به stringه "0.000001" تبدیل میشه و parseInt هم 0 برمیگردونه. وقتی که 0.0000001 به string تبدیل میشه تبدیل به "1e-7" شده بنابراین parseInt مقدار 1 رو برمیگردونه. 1/1999999 هم به صورت 5.00000250000125e-7e-7 تفسیر میشه و parseInt مقدار 5 رو برمیگردونه.

Math with true and false

یکم ریاضیات انجام بدیم:

true + true; // -> 2
(true + true) * (true + true) - true; // -> 3

هممم 🤔

💡 توضیح:

میتونیم مقدار هارو به عدد تبدیل کنیم با استفاده از کانستراکتور Number، واضح هست که true به 1 تبدیل میشه:

Number(true); // -> 1

عملگر + خالی سعی میکنه که مقدار رو به عدد تبدیل کنه. می‌تونه عدد ها و اعشار ها رو که در قالب string هستن و مقدار های غیر string مثل true و false و null رو تبدیل کنه. اگر نتونه مقدار خاصی رو بخونه، NaN برمیگردونه. این یعنی میتونیم true رو به 1 راحت تر تبدیل کنیم:

+true; // -> 1

وقتی جمع یا ضرب انجام میدید، متد ToNumber اجرا میشه. با توجه به بدیهیات، این متد برمیگردونه:

If argument is true, return 1. If argument is false, return +0.

به همین دلیل میتونیم مقدار های boolean رو مثل عدد های معمولی جمع کنیم و جواب درست رو بگیریم.

بخش های مربوط:

HTML comments are valid in JavaScript

شاید براتون عجیب به نظر برسه، ولی <!-- (که به عنوان کامنت HTML شناخته میشه) یه کامنت معتبر توی جاوا اسکریپت هست.

// valid comment
<!-- valid comment too

💡 توضیح:

کامنت های HTML پشتیبانی میشن تا به مرورگر هایی که تگ <script> رو نمیفهمن، به راحتی نادیدشون بگیرن. مرورگر هایی مثل Netscape 1.x که دیگه مشهور نیستن پس هیچ مزیتی برای قرار دادن کامنت های HTML توی تگ اسکریپت وجود نداره.

از اونجایی که نود جی‌اس بر پایه‌ی انجین V8 هست، کامنت های HTML توی ران تایم نود جی‌اس هم پشتیبانی میشن. علاوه بر این بخشی از مشخصه ها هستن:

NaN is not a number

تایپ NaN مساوی با 'number' هست:

typeof NaN; // -> 'number'

💡 توضیح:

توضیح اینکه چطوری عملگر های typeof و instanceof کار میکنن:

[] and null are objects

typeof []; // -> 'object'
typeof null; // -> 'object'

// با این حال
null instanceof Object; // false

💡 توضیح:

این رفتار عملگر typeof، توی این بخش از داکیومنت توضیح داده شده:

بر اساس داکیومنت، عملگر typeof یه string برمیگردونه با توجه به Table 37: typeof Operator Results. برای null، آبجکت های معمولی استاندارد و یا غیر استاندارد، که [[Call]] پشتیبانی نمیکنن، string خروجی "object" برگردونده میشه.

هر چند میتونید تایپ یک آبجکت رو با استفاده از متد toString چک کنید.

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

💡 توضیح:

عللی که باعث به وجود اومدن این میشه، استاندارد IEEE 754-2008 برای محاسبه کردن اعداد اعشاری هست. توی همچین مقیاسی، مقدارمون به نزدیک ترین عدد زوج تبدیل میشه. مطالعه‌ی بیشتر:

Precision of 0.1 + 0.2

یه قضیه‌ی جالب مشهور، اینه که جمع 0.1 و 0.2 دقت بسیار بالایی دارن:

0.1 + 0.2; // -> 0.30000000000000004
0.1 + 0.2 === 0.3; // -> false

💡 توضیح:

جواب ”Is floating point math broken?” توی استک اور فلو:

مقدار های 0.2 و 0.3 توی برنامه‌ی شما به مقدار های حقیقیشون رند میشن. چیزی که باعث این اتفاق میشه اینه که نزدیک ترین double به 0.2 بزرگ تر از عدد صحیح 0.2 هست ولی نزدیک ترین double به 0.3، کوچک تر از عدد صحیح 0.3 هست. حاصل جمع 0.1 و 0.2 باعث بزرگ تر شدن از عدد صحیح 0.3 و به همین دلیل، 0.1 + 0.2 === 0.3; داخل کدمون false برمیگردونه.

این قضیه به حدی شناخته شده هست که یه سایت به نام 0.30000000000000004.com وجود داره و داخل هر زبونی که floating-point ریاضیات رو استفاده میکنه هست، نه صرفا جاوا اسکریپت.

Patching numbers

شما می تونید متد های شخصی سازی شده خودتون رو به wrapper ه ابجکت مثل Number و String اضافه کنید.

Number.prototype.isOne = function() {
  return Number(this) === 1;
};

(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0).isOne(); // -> false
(7).isOne(); // -> false

💡 توضیح:

در واقع شما میتونید ابجکت خودتون رو از ابجکت Number گسترش بدید و ارث بری بکنید، البته اگه رفتار متد شما بخشی از Spec نیست این توصیه نمیشه.

Comparison of three numbers

1 < 2 < 3; // -> true
3 > 2 > 1; // -> false

💡 توضیح:

چرا اینطوری کار کرد؟خب، موضوع این هست که مشکل اصلی به قسمت اول عبارت زیر بر میگرده، اینجا نشون داده شده چطور کار میکنه:

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

بزرگتر مساوی این رو میتونیم با استفاده از بزرگتر مساوی(>=): حل کنیم

3 > 2 >= 1; // true

درباره عملگر های رابطه ای در Spec بیشتر بخوانید:

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:

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:

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:

  1. We declare x with no value, so it's undefined.
  2. Then we pack the value of x into the object property x.
  3. Then we extract the value of x using destructuring and want to assign it to y. If the value is not defined, then we're going to use 1 as the default value.
  4. Return the value of y.

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");

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.

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.

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'!

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'

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.

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.

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:

  1. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! 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

CC 4.0

© Denys Dovhan