Skip to content

Latest commit

 

History

History
1743 lines (1210 loc) · 55.4 KB

README-pl-pl.md

File metadata and controls

1743 lines (1210 loc) · 55.4 KB

What the f*ck JavaScript?

WTFPL 2.0 NPM version

Lista zabawnych i podchwytliwych przykładów JavaScript

JavaScript to świetny język. Ma prostą składnię, duży ekosystem i, co najważniejsze, wspaniałą społeczność.

Jednocześnie wszyscy wiemy, że JavaScript jest dość zabawnym językiem z podchwytliwymi częściami. Niektóre z nich mogą szybko zamienić naszą codzienną pracę w piekło, a niektóre mogą rozśmieszyć nas na głos.

Oryginalny pomysł na WTFJS należy do Brian Leroux. Ta lista jest bardzo zainspirowana jego przemową “WTFJS” na dotJS 2012:

dotJS 2012 - Brian Leroux - WTFJS

Node Packaged Manuscript

Możesz zainstalować ten podręcznik za pomocą npm. Po prostu uruchom:

$ npm install -g wtfjs

Powinieneś być teraz w stanie uruchomić wtfjs w linii poleceń. Spowoduje to otwarcie instrukcji w wybranym $PAGER. W przeciwnym razie możesz kontynuować czytanie tutaj.

Źródło jest dostępne tutaj: https://github.com/denysdovhan/wtfjs

Tłumaczenia

Obecnie są następujące tłumaczenia wtfjs:

Poproś o kolejne tłumaczenie

Table of Contents

💪🏻 Motywacja

Dla zabawy

“Just for Fun: The Story of an Accidental Revolutionary”, Linus Torvalds

Głównym celem tej listy jest zebranie szalonych przykładów i wyjaśnienie, w jaki sposób działają, jeśli to możliwe. Tylko dlatego, że fajnie jest nauczyć się czegoś, czego wcześniej nie znaliśmy.

Jeśli jesteś początkujący, możesz skorzystać z tych notatek, aby głębiej zagłębić się w JavaScript. Mam nadzieję, że te notatki zmotywują cię do spędzenia więcej czasu na czytaniu specyfikacji.

Jeśli jesteś profesjonalnym programistą, możesz rozważyć te przykłady, jako świetne źródło informacji o wszystkich dziwactwach i nieoczekiwanych krawędziach naszego ukochanego JavaScript.

W każdym razie po prostu przeczytaj to. Prawdopodobnie znajdziesz coś nowego.

✍🏻 Notacja

// -> służy do wyświetlenia wyniku wyrażenia. Na przykład:

1 + 1; // -> 2

// > oznacza wynik console.log lub wyświetlenie innego wyniku. Na przykład:

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

// jest tylko komentarzem używanym w celu wyjaśnienia. Przykład:

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

👀 Przykłady

[] jest równe ![]

Tablica jest równa zanegowanej tablicy:

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

💡 Wytłumaczenie:

Abstrakcyjny operator równości przekształca obie strony na liczby, aby je porównać, a obie strony stają się liczbą 0 z różnych powodów. Tablice są prawdziwe, więc po prawej stronie przeciwieństwem prawdziwej wartości jest false, który jest następnie wymuszany na 0. Po lewej jednak pusta tablica jest wymuszana na liczbę, nie będąc najpierw wartością logiczną, a puste tablice są wymuszane na 0, mimo że są prawdziwe.

Oto jak to wyrażenie upraszcza:

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

Zobacz też [] jest prawdziwe, ale nie true.

true nie jest równe ![], ale też nie równe []

Tablica nie jest równa true, ale zanegowana tablica też nie jest równa true; Tablica jest równa false, zanegowana tablica również jest równa false:

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

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

💡 Wytłumaczenie:

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

prawda to fałsz

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

💡 Wytłumaczenie:

Rozważ to krok po kroku:

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

To stary żart w JavaScript, ale odnowiony. Oto oryginał:

"foo" + +"bar"; // -> 'fooNaN'

💡 Wytłumaczenie:

Wyrażenie jest oceniane jako 'foo' + (+'bar'), które konwertuje 'bar' nie na liczbę.

NaN nie jest NaN

NaN === NaN; // -> false

💡 Wytłumaczenie:

Specyfikacja ściśle określa logikę tego zachowania:

  1. Jeśli Type(x) jest różny od Type(y), zwraca false.
  2. Jeśli Type(x) jest Number, wtedy
    1. Jeśli x jest NaN, zwraca false.
    2. Jeśli y jest NaN, zwraca false.
    3. … … …

7.2.14 Strict Equality Comparison

Zgodnie z definicją NaN z IEEE:

Możliwe są cztery wzajemnie wykluczające się relacje: mniejszy, równy, większy niż i nieuporządkowany. Ostatni przypadek powstaje, gdy co najmniej jednym operandem jest NaN. Każdy NaN porównuje się nieuporządkowany ze wszystkim, w tym samym sobą.

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

To jest fail

Nie uwierzyłbyś, ale …

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

💡 Wytłumaczenie:

Po rozbiciu masy symboli na części zauważamy, że często występuje następujący wzór:

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

Więc próbujemy dodać [] do false. Ale z powodu wielu wywołań funkcji wewnętrznych (binary + Operator -> ToPrimitive -> [[DefaultValue]]) w końcu konwertujemy odpowiedni operand na ciąg:

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

Myśląc o łańcuchu jako tablicy, możemy uzyskać dostęp do jego pierwszego znaku za pośrednictwem [0]:

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

Reszta jest oczywista, ale i jest podchwytliwe. i w fail jest pobierany przez generowanie ciągu 'falseundefined' i łapanie elementu na indeks ['10']

[] jest prawdziwe, ale nie true

Tablica jest prawdziwą wartością, jednak nie jest równa true.

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

💡 Wytłumaczenie:

Oto linki do odpowiednich sekcji specyfikacji ECMA-262:

null jest fałszywe, ale nie false

Pomimo faktu, żenull jest wartością fałszywą, nie jest równa false.

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

W tym samym czasie inne wartości fałszywe, takie jak 0 lub '' są równe do false.

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

💡 Wytłumaczenie:

Wytłumaczenie jest takie samo jak w poprzednim przykładzie. Oto odpowiedni link:

document.all jest obiektem, ale jest undefined

⚠️ Jest to część interfejsu API przeglądarki i nie będzie działać w środowisku Node.js ⚠️

Pomimo faktu, że document.all jest obiektem tablicowym i daje dostęp do węzłów DOM na stronie, odpowiada na funkcję typeof jako undefined.

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

W tym samym czasie, document.all nie jest równe undefined.

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

Ale w tym samym czasie:

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

💡 Wytłumaczenie:

document.all kiedyś był sposobem na dostęp do elementów DOM, w szczególności w starszych wersjach IE. Chociaż nigdy nie był standardem, był szeroko stosowany w starszym kodzie JS. Kiedy standard rozwijał się z nowymi interfejsami API (takimi jak document.getElementById), to wywołanie interfejsu API stało się przestarzałe i komitet standardowy musiał zdecydować, co z nim zrobić. Ze względu na szerokie zastosowanie postanowili zachować interfejs API, ale wprowadzili umyślne naruszenie specyfikacji JavaScript. Powód, dla którego reaguje na false podczas korzystania ze Strict Equality Comparison z undefined gdy true podczas korzystania z Abstract Equality Comparison wynika z umyślnego naruszenia specyfikacji, która wyraźnie na to pozwala.

“Obsolete features - document.all” na WhatWG - HTML spec — “Chapter 4 - ToBoolean - Falsy values” na YDKJS - Types & Grammar

Minimalna wartość jest większa od zera

Number.MIN_VALUE jest najmniejszą liczbą, która jest większa od zera:

Number.MIN_VALUE > 0; // -> true

💡 Wytłumaczenie:

Number.MIN_VALUE jest 5e-324, np. najmniejsza liczba dodatnia, która może być reprezentowana z precyzją zmiennoprzecinkową, tj. jest tak blisko, jak można dojść do zera. Określa najlepszą rozdzielczość, jaką mogą zaoferować floaty.

Teraz ogólna najmniejsza wartość to Number.NEGATIVE_INFINITY chociaż nie jest to tak naprawdę liczbowe w ścisłym tego słowa znaczeniu.

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

funkcja nie jest funkcją

⚠️ Bug obecny w wersji V8 5.5 lub nowszej (Node.js <=7) ⚠️

Wszyscy wiecie o irytującym niezdefiniowany nie jest funkcją, ale co z tym?

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

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

💡 Wytłumaczenie:

To nie jest część specyfikacji. To tylko błąd, który został już naprawiony, więc nie powinno być z tym problemu w przyszłości.

Dodawanie tablic

Co jeśli spróbujesz dodać dwie tablice?

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

💡 Wytłumaczenie:

Zachodzi konkatenacja. Krok po kroku wygląda to tak:

[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

Utworzyłeś tablicę z 4 pustymi elementami. Mimo wszystko otrzymasz tablicę z trzema elementami ze względu na końcowe przecinki:

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

💡 Wytłumaczenie:

Trailing commas (czasami nazywane "final commas") może być przydatne podczas dodawania nowych elementów, parametrów lub właściwości do kodu JavaScript. Jeśli chcesz dodać nową właściwość, możesz po prostu dodać nową linię bez modyfikowania poprzedniej poprzedniej linii, jeśli linia ta już używa przecinka końcowego. To sprawia, że różnice w kontroli wersji są czystsze, a edycja kodu może być mniej kłopotliwa.

Trailing commas na MDN

Równość tablic to potwór

Równość tablic jest potworem w JS, jak widać poniżej:

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

💡 Wytłumaczenie:

Powinieneś uważnie obserwować powyższe przykłady! Zachowanie opisano w rozdziale 7.2.13 Abstract Equality Comparison specyfikacji.

undefined oraz Number

Jeśli nie przekażemy żadnych argumentów do konstruktura Number, otrzymamy 0. Wartość undefined jest przypisana do formalnych argumentów, gdy nie ma rzeczywistych argumentów, więc możesz się spodziewać, że Number bez argumentów dostanie undefined jako wartość jego parametru. Jednak kiedy przekażemy undefined, dostaniemy NaN.

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

💡 Wytłumaczenie:

Zgodnie ze specyfikacją:

  1. Jeśli do wywołania tej funkcji nie zostaną przekazane żadne argumenty, pozwól n być +0.
  2. Inaczej, pozwól n być ? ToNumber(value).
  3. W przypadku undefined, ToNumber(undefined) powinno zwrócić NaN.

Oto odpowiednia sekcja:

parseInt jest złym gościem

parseInt słynie ze swoich dziwactw:

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

💡 Wytłumaczenie: Dzieje się tak, ponieważ parseInt będzie kontynuować analizowanie znak po znaku, dopóki nie trafi na postać, której nie zna. f w 'f*ck' jest cyfrą szesnastkową 15.

Parsowanie Infinity do integer jest czymś…

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

Uważaj na parsowanie null także:

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

💡 Wytłumaczenie:

Konwertuje null na string "null" i próbuje to przekonwertować. W przypadku podstaw od 0 do 23 nie ma cyfr, które mógłby przekonwertować, więc zwraca NaN. Na 24, "n", 14ta litera, jest dodawana do systemu liczbowego. Na 31, "u", 21sza litera, jest dodawana, a cały ciąg można dekodować. Na 37 nie ma już żadnego poprawnego zestawu liczb, który można by wygenerować i NaN jest zwrócony.

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

Nie zapomnij o ósemkach:

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

💡 Wytłumaczenie: Jeśli ciąg wejściowy zaczyna się od "0", podstawa to osiem (ósemka) lub 10 (dziesiętnie). To, która podstawa jest wybrana, zależy od implementacji. ECMAScript 5 określa, że używana jest liczba 10 (dziesiętna), ale nie wszystkie przeglądarki obsługują to jeszcze. Z tego powodu zawsze określaj podstawę podczas używania parseInt.

parseInt zawsze konwertuj dane wejściowe na ciąg:

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

Zachowaj ostrożność podczas analizowania wartości zmiennoprzecinkowych

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

💡 Wytłumaczenie: ParseInt pobiera argument ciągu i zwraca liczbę całkowitą określonej podstawy. ParseInt usuwa również wszystko po pierwszej wartości cyfrowej i włącznie z nią w parametrze ciągu. 0.000001 jest konwertowany na ciąg znaków "0.000001" i parseInt zwraca 0. Gdy 0.0000001 jest konwertowany na ciąg, który jest traktowany jako "1e-7" i stąd parseInt zwraca 1. 1/1999999 jest interpretowane jako 5.00000250000125e-7 i parseInt zwraca 5.

Matematyka z true i false

Zróbmy trochę matematyki:

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

Hmmm… 🤔

💡 Wytłumaczenie:

Możemy narzucić wartości do liczb za pomocą konstruktora Number. To całkiem oczywiste że true będzie zmienione na 1:

Number(true); // -> 1

Jednoargumentowy operator plus próbuje przeliczyć swoją wartość na liczbę. Może konwertować reprezentacje ciągu liczb całkowitych i liczb zmiennoprzecinkowych, a także wartości nie łańcuchowe true, false, i null. Jeśli nie może przeanalizować określonej wartości, oceni to jako NaN. To oznacza, że możemy narzucić true na 1 łatwiej:

+true; // -> 1

Podczas dodawania lub mnożenia, metoda ToNumber jest przywoływana. Zgodnie ze specyfikacją ta metoda zwraca:

Jeśli argument jest true, zwraca 1. Jeśli argument jest false, zwraca +0.

Dlatego możemy dodawać wartości logiczne jako liczby regularne i uzyskiwać prawidłowe wyniki.

Odpowiednie sekcje:

Komentarze HTML są obowiązujące w JavaScript

Będziesz pod wrażeniem, ale <!-- (który jest znany jako komentarz HTML) jest poprawnym komentarzem w JavaScript.

// valid comment
<!-- valid comment too

💡 Wytłumaczenie:

Pod wrażeniem? Komentarze w formacie HTML miały umożliwić przeglądarkom, które nie rozumieją tagu <script> degradować z wdziękiem. Te przeglądarki, np. Netscape 1.x nie są już popularne. Dlatego naprawdę nie ma sensu umieszczać komentarzy HTML w tagach skryptu.

Ponieważ Node.js jest oparty na silniku V8, komentarze podobne do HTML są obsługiwane również przez środowisko uruchomieniowe Node.js. Ponadto są częścią specyfikacji:

NaN is not a number

Typ NaN jest 'number':

typeof NaN; // -> 'number'

💡 Wytłumaczenie:

Wytłumaczenia jak operatory typeof i instanceof działają:

[] i null są obiektami

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

// however
null instanceof Object; // false

💡 Wytłumaczenie:

Zachowanie operatora typeof jest zdefiniowane w tej sekcji specyfikacji:

Zgodnie ze specyfikacją, operator typeof zwraca ciąg zgodnie z Table 35: typeof Operator Results. For null, ordinary, standard exotic and non-standard exotic objects, which do not implement [[Call]], it returns the string "object".

Możesz jednak sprawdzić typ obiektu, używając metody toString.

Object.prototype.toString.call([]);
// -> '[object Array]'

Object.prototype.toString.call(new Date());
// -> '[object Date]'

Object.prototype.toString.call(null);
// -> '[object Null]'

Magicznie rosnące liczby

999999999999999; // -> 999999999999999
9999999999999999; // -> 10000000000000000

10000000000000000; // -> 10000000000000000
10000000000000000 + 1; // -> 10000000000000000
10000000000000000 + 1.1; // -> 10000000000000002

💡 Wytłumaczenie:

Jest to spowodowane standardem IEEE 754-2008 dla binarnej arytmetyki zmiennoprzecinkowej. W tej skali zaokrągla się do najbliższej liczby parzystej. Czytaj więcej:

Precyzja 0.1 + 0.2

Dobrze znany żart. An addition of 0.1 and 0.2 is deadly precise:

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

💡 Wytłumaczenie:

Odpowiedź na pytanie ”Is floating point math broken?” ze StackOverflow:

Stałe 0.2 i 0.3 w twoim programie będzie również przybliżenie ich prawdziwych wartości. Zdarza się, że najbliższa double do 0.2 jest większa niż liczba wymierna 0.2, ale najbliższa double do 0.3 jest mniejsza niż liczba wymierna 0.3. Suma 0.1 i 0.2 kończy się na wartości większej od liczby wymiernej 0.3, a zatem nie zgadza się ze stałą w kodzie.

Ten problem jest tak znany, że istnieje nawet strona internetowa o nazwie 0.30000000000000004.com. Występuje w każdym języku wykorzystującym matematykę zmiennoprzecinkową, nie tylko JavaScript.

Patching numbers

Możesz dodać własne metody do wrapowania obiektów takich jak Number lub String.

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

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

💡 Wytłumaczenie:

Oczywiście możesz rozszerzyć obiekt Number jak każdy inny obiekt w JavaScript. Jednak nie jest zalecane, jeśli zachowanie zdefiniowanej metody nie jest częścią specyfikacji. Oto lista właściwości Number:

Porównanie trzech liczb

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

💡 Wytłumaczenie:

Dlaczego to działa w ten sposób? Problem tkwi w pierwszej części wyrażenia. Oto jak to działa:

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

Możemy to naprawić za pomocą Operatora większy lub równy (>=):

3 > 2 >= 1; // true

Przeczytaj więcej o operatorach relacyjnych w specyfikacji:

Zabawna matematyka

Często wyniki operacji arytmetycznych w JavaScript mogą być dość nieoczekiwane. Rozważ te przykłady:

 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

💡 Wytłumaczenie:

Co dzieje się w pierwszych czterech przykładach? Oto mała tabelka, aby zrozumieć dodawanie w JavaScript:

Number  + Number  -> addition
Boolean + Number  -> addition
Boolean + Boolean -> addition
Number  + String  -> concatenation
String  + Boolean -> concatenation
String  + String  -> concatenation

Co z innymi przykładami? Metody ToPrimitive i ToString są domyślnie wywoływane dla [] i {} przed dodaniem. Przeczytaj więcej o procesie oceny w specyfikacji:

Szczególnie, {} + [] tutaj jest wyjątek. Powód, dla którego się różni z [] + {} polega na tym, że bez nawiasów interpretuje się go jako blok kodu, a następnie jako jedność +, konwertując [] na liczbę. Wygląda następująco:

{
  // a code block here
}
+[]; // -> 0

Aby uzyskać ten sam wynik jak [] + {} możemy owrapować to w nawias.

({} + []); // -> [object Object]

Dodanie RegExps

Czy wiesz, że możesz dodawać takie liczby?

// Patch a toString method
RegExp.prototype.toString =
  function() {
    return this.source;
  } /
  7 /
  -/5/; // -> 2

💡 Wytłumaczenie:

Stringi nie są instancjami String

"str"; // -> 'str'
typeof "str"; // -> 'string'
"str" instanceof String; // -> false

💡 Wytłumaczenie:

Konstruktor String zwraca string:

typeof String("str"); // -> 'string'
String("str"); // -> 'str'
String("str") == "str"; // -> true

Spróbujmy z new:

new String("str") == "str"; // -> true
typeof new String("str"); // -> 'object'

Obiekt? Co to jest?

new String("str"); // -> [String: 'str']

Więcej informacji o konstruktorze String w specyfikacji:

Wywoływanie funkcji za pomocą backticksa

Zadeklarujmy funkcję, która rejestruje wszystkie parametry w konsoli:

function f(...args) {
  return args;
}

Bez wątpienia wiesz, że możesz wywołać tę funkcję w następujący sposób:

f(1, 2, 3); // -> [ 1, 2, 3 ]

Ale czy wiesz, że możesz wywołać dowolną funkcję za pomocą backticksa?

f`true is ${true}, false is ${false}, array is ${[1, 2, 3]}`;
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// ->   true,
// ->   false,
// ->   [ 1, 2, 3 ] ]

💡 Wytłumaczenie:

Cóż, to wcale nie jest magia, jeśli jesteś obeznany z Tagged template literals. W powyższym przykładzie, funkcja f jest znacznikiem literału szablonu. Tagi przed literałem szablonu umożliwiają analizowanie literałów szablonu za pomocą funkcji. Pierwszy argument funkcji znacznika zawiera tablicę wartości ciągów. Pozostałe argumenty są powiązane z wyrażeniami. Przykład:

function template(strings, ...keys) {
  // do something with strings and keys…
}

To jest magia z tyłu słynnej biblioteki o nazwie 💅 styled-components, która jest popularna w społeczności React.

Link do specyfikacji:

Call call call

Znalezione przez @cramforce

console.log.call.call.call.call.call.apply(a => a, [1, 2]);

💡 Wytłumaczenie:

Uwaga, może to popsuć ci umysł! Spróbuj odtworzyć ten kod w swojej głowie: stosujemy metodę call za pomocą metodyapply. Czytaj więcej:

Właściwość constructor

const c = "constructor";
c[c][c]('console.log("WTF?")')(); // > WTF?

💡 Wytłumaczenie:

Rozważmy ten przykład krok po kroku:

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

Object.prototype.constructor zwraca referencję do funkcji konstruktora Object który utworzył obiekt instancji. W przypadku łańcuchów jest to String, w przypadku liczb jest to Number i tak dalej.

Obiekt jako klucz właściwości obiektu

{ [{}]: {} } // -> { '[object Object]': {} }

💡 Wytłumaczenie:

Dlaczego to działa? Tutaj używamy Computed property name. Gdy przekazujesz obiekt między tymi nawiasami, wymusza on obiekt na ciąg, więc otrzymujemy klucz właściwości '[object Object]' i wartość {}.

Możemy zrobić "brackets hell" jak tutaj:

({ [{}]: { [{}]: {} } }[{}][{}]); // -> {}

// structure:
// {
//   '[object Object]': {
//     '[object Object]': {}
//   }
// }

Przeczytaj więcej na temat literałów obiektowych tutaj:

Dostęp do prototypów za pomocą __proto__

Jak wiemy, prymitywy nie mają prototypów. Jeśli jednak spróbujemy uzyskać wartość __proto__ dla prymitywów otrzymalibyśmy to:

(1).__proto__.__proto__.__proto__; // -> null

💡 Wytłumaczenie:

Dzieje się tak, ponieważ gdy coś nie ma prototypu, zostanie ono zawinięte w obiekt wrappera za pomocą metody ToObject. Więc krok po kroku:

(1)
  .__proto__(
    // -> [Number: 0]
    1
  )
  .__proto__.__proto__(
    // -> {}
    1
  ).__proto__.__proto__.__proto__; // -> null

Oto więcej informacji na temat __proto__:

`${{Object}}`

Jaki jest wynik poniższego wyrażenia?

`${{ Object }}`;

Odpowiedź to:

// -> '[object Object]'

💡 Wytłumaczenie:

Zdefiniowaliśmy obiekt z właściwością Object używając Shorthand property notation:

{
  Object: Object;
}

Następnie przekazaliśmy ten obiekt do literału szablonu, więc metoda toString wywołuje ten obiekt. Właśnie dlatego otrzymujemy string '[object Object]'.

Destrukturyzacja z wartościami domyślnymi

Rozważ ten przykład:

let x,
  { x: y = 1 } = { x };
y;

Powyższy przykład to świetne zadanie na rozmowę kwalifikacyjną. Jaka jest wartość y? Odpowiedź to:

// -> 1

💡 Wytłumaczenie:

let x,
  { x: y = 1 } = { x };
y;
//  ↑       ↑           ↑    ↑
//  1       3           2    4

W powyższym przykładzie:

  1. Deklarujemy x z brakiem wartości, więc jest undefined.
  2. Wtedy pakujemy wartość x we własność obiektu x.
  3. Następnie wyodrębniamy wartość x używając destrukturyzacji i chcemy to przypisać do y. Jeśli wartość nie zostanie zdefiniowana, wówczas użyjemy „1 jako wartości domyślnej.
  4. Zwróć wartość y .

Dots and spreading

Ciekawe przykłady można skomponować z rozmieszczaniem tablic. Rozważ to:

[...[..."..."]].length; // -> 3

💡 Wytłumaczenie:

Czemu 3? Kiedy korzystamy ze spread operatora, metoda @@iterator jest wywołana, a zwrócony iterator służy do uzyskania wartości do iteracji. Domyślny iterator łańcucha rozdziela łańcuch na znaki. Po rozłożeniu pakujemy te znaki do tablicy. Następnie rozkładamy tę tablicę ponownie i pakujemy z powrotem do tablicy.

String '...' składa się z trzech znaków ., więc długość wynikowej tablicy wynosi 3.

Teraz krok po kroku:

[...'...']             // -> [ '.', '.', '.' ]
[...[...'...']]        // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3

Oczywiście możemy rozkładać i wrapować elementy tablicy tyle razy, ile chcemy:

[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]
// and so on …

Etykiety

Niewielu programistów wie o etykietach w JavaScript. Są dość interesujące:

foo: {
  console.log("first");
  break foo;
  console.log("second");
}

// > first
// -> undefined

💡 Wytłumaczenie:

Instrukcja z etykietą jest używana z instrukcją break lub continue. Możesz użyć etykiety do zidentyfikowania pętli, a następnie użyć instrukcji break lub continue, aby wskazać, czy program powinien przerwać pętlę, czy kontynuować jej wykonywanie.

W powyższym przykładzie identyfikujemy etykietę foo. Po tym console.log ('first'); wykonuje, a następnie przerywamy wykonywanie.

Przeczytaj więcej o etykietach w JavaScript:

Zagnieżdżone etykiety

a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5

💡 Wytłumaczenie:

Podobnie jak w poprzednich przykładach, skorzystaj z poniższych linków:

Podstępny try..catch

Co zwróci to wyrażenie?? 2 czy 3?

(() => {
  try {
    return 2;
  } finally {
    return 3;
  }
})();

Odpowiedź to 3. Zaskoczony?

💡 Wytłumaczenie:

Czy to wielokrotne dziedziczenie?

Spójrz na poniższy przykład:

new class F extends (String, Array) {}(); // -> F []

Czy to wielokrotne dziedziczenie? Nie.

💡 Wytłumaczenie:

Interesującą częścią jest wartość klauzuli extends ((String, Array)). Operator grupowania zawsze zwraca ostatni argument, więc (String, Array) jest właściwie po prostu Array. Oznacza to, że właśnie stworzyliśmy klasę, która rozszerza 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 }

Jak widać, zwrócona wartość jest obiektem wraz z nią value równa do f. W takim przypadku możemy zrobić coś takiego:

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

💡 Wytłumaczenie:

Aby zrozumieć, dlaczego to działa w ten sposób, przeczytaj następujące sekcje specyfikacji:

Klasa klasy

Rozważ tę zaciemnioną składnię:

typeof new class {
  class() {}
}(); // -> 'object'

Wygląda na to, że deklarujemy klasę wewnątrz klasy. Powinien być jednak błąd, ale otrzymujemy ciąg 'object'.

💡 Wytłumaczenie:

Od ery ECMAScript 5 słowa kluczowe są dozwolone jako nazwy własności. Pomyśl o tym jako o tym prostym przykładzie obiektu:

const foo = {
  class: function() {}
};

I znormalizowane skróty definicji metod ES6. Ponadto klasy mogą być anonimowe. Więc jeśli opuścimy część :function, otrzymamy:

class {
  class() {}
}

Wynik domyślnej klasy jest zawsze prostym obiektem. I jego typ powinien zwrócić 'object'.

Przeczytaj więcej tutaj:

Non-coercible objects

Dzięki dobrze znanym symbolom można pozbyć się typu coercion. Spójrz:

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;
}

Teraz możemy użyć tego w następujący sposób:

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

💡 Wytłumaczenie:

Podstępne funkcje strzałkowe

Rozważ poniższy przykład:

let f = () => 10;
f(); // -> 10

Okej, w porządku, ale co z tym:

let f = () => {};
f(); // -> undefined

💡 Wytłumaczenie:

Możesz oczekiwać {} zamiast undefined. Wynika to z faktu, że nawiasy klamrowe są częścią składni funkcji strzałkowych, więc f zwróci niezdefiniowane. Możliwe jest jednak zwrócenie obiektu {} bezpośrednio z funkcji strzałkowej, poprzez umieszczenie wartości zwracanej w nawiasach.

let f = () => ({});
f(); // -> {}

Funkcje strzałkowe nie mogą być konstruktorami

Rozważ poniższy przykład:

let f = function() {
  this.a = 1;
};
new f(); // -> { 'a': 1 }

Teraz spróbuj zrobić to samo z funkcją strzałkową:

let f = () => {
  this.a = 1;
};
new f(); // -> TypeError: f is not a constructor

💡 Wytłumaczenie:

Funkcje strzałkowe nie mogą być używane jako konstruktory i będą zgłaszać błąd, gdy będą używane z nowym. Ponieważ ma leksykalne this i nie ma właściwości prototype, więc nie miałoby to większego sensu.

arguments i funkcje strzałkowe

Rozważ poniższy przykład:

let f = function() {
  return arguments;
};
f("a"); // -> { '0': 'a' }

Teraz spróbuj zrobić to samo z funkcją strzałkową:

let f = () => arguments;
f("a"); // -> Uncaught ReferenceError: arguments is not defined

💡 Wytłumaczenie:

Funkcje strzałkowe to lekka wersja zwykłych funkcji z naciskiem na bycie krótkim i leksykalnym this. Jednocześnie funkcje strzałkowe nie zapewniają wiązania dla obiektu arguments. Jako prawidłową alternatywę użyj rest parameters, aby osiągnąć ten sam wynik:

let f = (...args) => args;
f("a");

Podstępny return

Wyrażenie return jest również podstępne. Rozważ to:

(function() {
  return
  {
    b: 10;
  }
})(); // -> undefined

💡 Wytłumaczenie:

return i zwrócone wyrażenie musi znajdować się w tym samym wierszu:

(function() {
  return {
    b: 10
  };
})(); // -> { b: 10 }

Wynika to z koncepcji o nazwie Automatyczne wstawianie średników, która automatycznie wstawia średniki po większości nowych linii. W pierwszym przykładzie między wyrażeniem return a literałem obiektu wstawiono średnik, więc funkcja zwraca undefined, a literał obiektu nigdy nie jest oceniany.

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

Z prawej do lewej, {n: 2} jest przypisany do foo, a wynik tego przypisania {n: 2} jest do foo.x, i dlatego bar jest {n: 1, x: {n: 2}} jako bar jest referencją do foo. Ale czemu foo.x jest undefined podczas gdy bar.x nie jest ?

💡 Wytłumaczenie:

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.

Jest to równoważne z:

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}

Dostęp do właściwości obiektu za pomocą tablic

var obj = { property: 1 };
var array = ["property"];

obj[array]; // -> 1

Co z tablicami pseudo-wielowymiarowymi?

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

💡 Wytłumaczenie:

Operator nawiasów klamrowych [] konwertuje przekazane wyrażenie za pomocą toString. Konwersja tablicy jednoelementowej na ciąg znaków jest zbliżona do konwersji zawartego elementu na ciąg znaków:

["property"].toString(); // -> 'property'

Null and Relational Operators

null > 0; // false
null == 0; // false

null >= 0; // true

💡 Wytłumaczenie:

Long story short, if null is less than 0 is false, then null >= 0 is true. Read in-depth Wytłumaczenie for this here.

Number.toFixed() display different numbers

Number.toFixed() może zachowywać się trochę dziwnie w różnych przeglądarkach. Sprawdź ten przykład:

(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

💡 Wytłumaczenie:

Podczas gdy twoim pierwszym instynktem może być to, że IE11 jest poprawny, a Firefox / Chrome są w błędzie, w rzeczywistości Firefox / Chrome bardziej bezpośrednio przestrzegają standardów liczbowych (zmiennoprzecinkowy IEEE-754), podczas gdy IE11 nieznacznie ich nie przestrzega (prawdopodobnie), aby dać wyraźniejsze wyniki.

Możesz zobaczyć, dlaczego tak się dzieje po kilku szybkich testach:

// 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() mniej niż Math.min()

Math.min(1, 4, 7, 2); // -> 1
Math.max(1, 4, 7, 2); // -> 7
Math.min(); // -> Infinity
Math.max(); // -> -Infinity
Math.min() > Math.max(); // -> true

💡 Wytłumaczenie:

Comparing null to 0

Następujące wyrażenia wydają się wprowadzać w sprzeczność:

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

Jak null nie może być ani równy ani większy od 0, jeśli null>=0' jest w rzeczywistościtrue`? (Działa to również z mniej niż w ten sam sposób.)

💡 Wytłumaczenie:

Sposób oceny tych trzech wyrażeń jest różny i jest odpowiedzialny za wywołanie tego nieoczekiwanego zachowania.

Po pierwsze, abstrakcyjne porównanie równości null == 0. Zwykle, jeśli ten operator nie może poprawnie porównać wartości po obu stronach, konwertuje obie liczby na liczby i porównuje liczby. Następnie możesz spodziewać się następującego zachowania:

// This is not what happens
(null == 0 + null) == +0;
0 == 0;
true;

Jednak, zgodnie z dokładnym odczytaniem specyfikacji, konwersja liczb tak naprawdę nie zachodzi po stronie, która jest null lub undefined. Dlatego jeśli po jednej stronie znaku równości występuje null, druga strona musi być null lub undefined, aby wyrażenie mogło zwrócić true. Ponieważ tak nie jest, zwracane jest false.

Następnie relacyjne porównanie null> 0. Algorytm tutaj, w przeciwieństwie do abstrakcyjnego operatora równości, przekonwertuje null na liczbę. Dlatego otrzymujemy takie zachowanie:

null > 0
+null = +0
0 > 0
false

Wreszcie relacyjne porównanie null >= 0. Można argumentować, że to wyrażenie powinno być wynikiem null> 0 || null == 0; gdyby tak było, powyższe wyniki oznaczałyby, że byłoby to również false. Jednak operator > = w rzeczywistości działa w zupełnie inny sposób, co w zasadzie ma przeciwne działanie niż operator <. Ponieważ nasz przykład z operatorem większym niż powyżej odnosi się również do operatora mniejszego niż, oznacza to, że to wyrażenie jest w rzeczywistości oceniane tak:

null >= 0;
!(null < 0);
!(+null < +0);
!(0 < 0);
!false;
true;

Redeklaracja tej samej zmiennej

JS pozwala na ponowne zdefiniowanie zmiennych:

a;
a;
// This is also valid
a, a;

Działa również w trybie ścisłym:

var a, a, a;
var a;
var a;

💡 Wytłumaczenie:

Wszystkie definicje są scalone w jedną definicję.

Domyślne zachowanie Array.prototype.sort()

Wyobraź sobie, że musisz posortować tablicę liczb.

[ 10, 1, 3 ].sort() // -> [ 1, 10, 3 ]

💡 Wytłumaczenie:

Domyślna kolejność sortowania opiera się na konwersji elementów na ciągi, a następnie porównaniu ich sekwencji wartości jednostek kodu UTF-16.

Wskazówka

Przekaż comparefn jeśli spróbujesz posortować cokolwiek poza ciągiem znaków.

[ 10, 1, 3 ].sort((a, b) => a - b) // -> [ 1, 3, 10 ]

📚 Inne materiały

  • wtfjs.com — zbiór tych bardzo wyjątkowych nieprawidłowości, niespójności i po prostu bolesnie nieintuicyjnych momentów dla języka webowego.
  • Wat — Lightning talk od Gary Bernhardt z CodeMash 2012
  • What the... JavaScript? — Kyle Simpsons mówi dla Forward 2 o próbach "wyciągnięcia szaleństwa" z JavaScript. Chce pomóc ci w tworzeniu czystszego, bardziej eleganckiego, bardziej czytelnego kodu, a następnie zainspirować ludzi do współpracy w społeczności open source.

🎓 Licencja

CC 4.0

© Denys Dovhan

Wersja polska od @mbiesiad