analizând exemplele JavaScript din discuția „Wat” a lui Gary Bernhardt

această postare este un omagiu adus fantasticului discurs „Wat” al lui Gary Bernhardt în care subliniază particularitățile unor construcții lingvistice în Ruby și JavaScript. Dacă nu ați urmărit încă discuția, vă recomand cu tărie să vă faceți timp și să faceți exact asta! Este doar aproximativ 4 minute lungi și extrem de distractiv, promit.

în discursul său, Gary arată aceste patru fragmente de cod JavaScript:

particularități în JavaScript

vedem o mulțime de paranteze, bretele și semne plus. Iată la ce evaluează aceste fragmente:

  • + == ""
  • + {} == ""
  • {} + == 0
  • {} + {} == NaN

când am văzut aceste exemple pentru prima dată, m-am gândit: „Uau, asta pare dezordonat!”Rezultatele pot părea inconsistente sau chiar arbitrare, dar aveți cu mine aici. Toate aceste exemple sunt de fapt foarte consistente și nu atât de rele pe cât arată!

#Fragment #1: +

să începem cu primul fragment:

 + // ""

după cum putem vedea, aplicarea operatorului + la două matrice goale are ca rezultat un șir gol. Acest lucru se datorează faptului că reprezentarea șirului unei matrice este reprezentarea șirului tuturor elementelor sale, concatenate împreună cu virgule:

.toString()// "1,2,3".toString()// "1,2".toString()// "1".toString()// ""

o matrice goală Nu conține niciun element, deci reprezentarea șirului său este un șir gol. Prin urmare, concatenarea a două șiruri goale este doar un alt șir gol.

#Fragment #2: + {}

până acum, totul e bine. Să examinăm acum al doilea fragment:

 + {}// ""

rețineți că, deoarece nu avem de-a face cu două numere, operatorul + efectuează din nou concatenarea șirului, mai degrabă decât adăugarea a două valori numerice.

în secțiunea anterioară, am văzut deja că reprezentarea șirului unei matrice goale este un șir gol. Reprezentarea string a obiectului gol literal aici este valoarea implicită "". Prefixarea unui șir gol nu modifică valoarea, deci "" este rezultatul final.

în JavaScript, obiectele pot implementa o metodă specială numită toString() care returnează o reprezentare șir personalizată a obiectului pe care metoda este apelată. Obiectul nostru gol literal nu implementează o astfel de metodă, așa că ne întoarcem la implementarea implicită a prototipului Object.

#Fragment #3: {} +

aș susține că până acum, rezultatele nu au fost prea neașteptate. Pur și simplu au respectat regulile de constrângere de tip și reprezentările implicite ale șirurilor în JavaScript.

cu toate acestea, {} + este locul în care dezvoltatorii încep să se confunde:

{} + // 0

de ce vedem 0 (numărul zero) dacă tastăm linia de mai sus într-un REPL JavaScript ca consola browserului? Nu ar trebui ca rezultatul să fie un șir, la fel cum a fost + {}?

înainte de a rezolva enigma, luați în considerare cele trei moduri diferite în care operatorul + poate fi utilizat:

// 1) Addition of two numeric values2 + 2 == 4// 2) String concatenation of two values"2" + "2" == "22"// 3) Conversion of a value to a number+2 == 2+"2" == 2

în primele două cazuri, operatorul + este un operator binar deoarece are doi operanzi (în stânga și în dreapta). În al treilea caz, operatorul + este un operator unar, deoarece are doar un singur operand (în dreapta).

luați în considerare și cele două semnificații posibile ale {} în JavaScript. De obicei, scriem {} pentru a însemna un obiect gol literal, dar dacă suntem în poziția de declarație, gramatica JavaScript specifică {} pentru a însemna un bloc gol. Următoarea bucată de cod definește două blocuri goale, dintre care nici unul nu este un obiect literal:

{}// Empty block{ // Empty block}

să aruncăm o privire la fragmentul nostru din nou:

{} + 

permiteți-mi să schimb puțin spațiul alb pentru a clarifica modul în care motorul JavaScript vede codul:

{ // Empty block}+;

acum putem vedea clar ce se întâmplă aici. Avem o declarație bloc urmată de o altă declarație care conține o expresie unară + care funcționează pe o matrice goală. Punct și virgulă la sfârșit se introduce automat în conformitate cu regulile ASI (inserare automată punct și virgulă).

puteți verifica cu ușurință în consola browserului că +evaluează la 0. Matricea goală are un șir gol ca reprezentare a șirului, care la rândul său este convertit la numărul zero de către operatorul +. În cele din urmă, valoarea ultimei instrucțiuni (+, în acest caz) este raportată de consola browserului.

alternativ, puteți alimenta ambele fragmente de cod la un parser JavaScript, cum ar fi Esprima și puteți compara arborii de sintaxă abstractă rezultați. Iată AST pentru + {}:

{ "type": "Program", "body": }, "right": { "type": "ObjectExpression", "properties": } } } ], "sourceType": "script"}

și aici este AST pentru {} + :

{ "type": "Program", "body": }, { "type": "ExpressionStatement", "expression": { "type": "UnaryExpression", "operator": "+", "argument": { "type": "ArrayExpression", "elements": }, "prefix": true } } ], "sourceType": "script"}

confuzia provine dintr-o nuanță a gramaticii JavaScript care folosește bretele atât pentru literali de obiecte, cât și pentru blocuri. În poziția declarație, o bretele de deschidere începe un bloc, în timp ce în poziția expresie o bretele de deschidere începe un obiect literal.

#Fragment #4: {} + {}

în cele din urmă, să aruncăm rapid o privire la ultimul nostru fragment {} + {}:

{} + {}// NaN

Ei bine, adăugarea a doi literali obiect este literalmente „nu un număr” – dar adăugăm aici doi literali obiect? Nu lăsați bretele păcăli din nou! Aceasta este ceea ce se întâmplă:

{ // Empty block}+{};

este destul de mult aceeași afacere ca în exemplul anterior. Cu toate acestea, acum aplicăm operatorul unary plus la un obiect gol literal. Aceasta este practic aceeași cu a face Number({}), ceea ce duce la NaN deoarece obiectul nostru literal nu poate fi convertit într-un număr.

dacă doriți ca motorul JavaScript să analizeze codul ca două litere de obiect goale, înfășurați primul (sau întreaga bucată de cod) între paranteze. Acum ar trebui să vedeți rezultatul așteptat:

({}) + {}// ""({} + {})// ""

paranteza de deschidere face ca parserul să încerce să recunoască o expresie, motiv pentru care nu tratează {} ca un bloc (care ar fi o declarație).

#rezumat

ar trebui să vedeți acum de ce cele patru fragmente de cod evaluează modul în care o fac. Nu este deloc arbitrar sau aleatoriu; Regulile de constrângere de tip sunt aplicate exact așa cum sunt prevăzute în caietul de sarcini și gramatica limbii.

doar ține cont de faptul că, dacă o deschidere bretele este primul caracter să apară într-o declarație, acesta va fi interpretat ca începutul unui bloc, mai degrabă decât un obiect literal.

Lasă un răspuns

Adresa ta de email nu va fi publicată.