ez a bejegyzés tisztelgés Gary Bernhardt fantasztikus” Wat ” talk-jában, amelyben rámutat a Ruby és a JavaScript egyes nyelvi konstrukcióinak sajátosságaira. Ha még nem nézte meg a beszélgetést, erősen ajánlom, hogy szánjon rá időt, és pontosan ezt tegye! Csak körülbelül 4 perc hosszú és nagyon szórakoztató, ígérem.
előadásában Gary bemutatja a JavaScript kód négy töredékét:
sok zárójelet, zárójelet és pluszjelet látunk. Itt van, amit ezek a töredékek értékelik:
+ == ""
+ {} == ""
{} + == 0
{} + {} == NaN
amikor először láttam ezeket a példákat, azt gondoltam: “Wow, ez rendetlennek tűnik!”Az eredmények ellentmondásosnak vagy akár önkényesnek tűnhetnek,de itt viseljem el. Ezek a példák valójában nagyon következetesek, és nem olyan rosszak, mint amilyennek látszanak!
# töredék #1: +
kezdjük az első töredékkel:
+ // ""
mint láthatjuk, a +
operátor két üres tömbre történő alkalmazása üres karakterláncot eredményez. A tömb karakterlánc-ábrázolása ugyanis az összes elemének karakterlánc-ábrázolása, vesszővel összefűzve:
.toString()// "1,2,3".toString()// "1,2".toString()// "1".toString()// ""
az üres tömb nem tartalmaz elemeket, így a karakterlánc-ábrázolása üres karakterlánc. Ezért két üres karakterlánc összefűzése csak egy újabb üres karakterlánc.
# töredék #2: + {}
eddig minden rendben. Most vizsgáljuk meg a második töredéket:
+ {}// ""
ne feledje, hogy mivel nem két számmal foglalkozunk, a +
operátor ismét végrehajtja a karakterlánc-összefűzést két numerikus érték hozzáadása helyett.
az előző szakaszban már láttuk, hogy egy üres tömb karakterlánc-ábrázolása üres karakterlánc. Az üres objektum literál karakterlánc-ábrázolása itt az alapértelmezett ""
érték. Az üres karakterlánc előreírása nem változtatja meg az értéket, így ""
a végeredmény.
a JavaScriptben az objektumok megvalósíthatnak egy toString()
nevű speciális módszert, amely a metódust meghívó objektum egyéni karakterlánc-ábrázolását adja vissza. Az üres objektum literál nem valósít meg ilyen módszert, ezért visszatérünk a Object
prototípus alapértelmezett megvalósításához.
# töredék #3: {} +
azt állítom, hogy eddig az eredmények nem voltak túl váratlanok. Egyszerűen követik a kényszerítés és az alapértelmezett karakterlánc-reprezentációk szabályait a JavaScript-ben.
azonban {} +
ahol a fejlesztők kezdenek összezavarodni:
{} + // 0
miért látjuk 0
(a szám nulla), ha beírjuk a fenti sort egy JavaScript REPL, mint a böngésző konzol? Nem kellene az eredménynek karakterláncnak lennie, mint + {}
volt?
mielőtt megoldanánk a rejtvényt, fontolja meg a +
operátor három különböző módját:
// 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
az első két esetben a +
operátor bináris operátor, mert két operandusa van (bal és jobb oldalon). A harmadik esetben a +
operátor unáris operátor, mert csak egyetlen operandusa van (a jobb oldalon).
fontolja meg a {}
két lehetséges jelentését is a JavaScript-ben. Általában {}
– et írunk, hogy üres objektum literált jelentsünk, de ha utasításpozícióban vagyunk, a JavaScript nyelvtan meghatározza {}
hogy üres blokkot jelentsen. A következő kóddarab két üres blokkot határoz meg, amelyek közül egyik sem objektum literál:
{}// Empty block{ // Empty block}
vessünk egy pillantást újra a töredékünkre:
{} +
hadd változtassam meg egy kicsit a szóközt, hogy világosabbá tegyem, hogy a JavaScript motor hogyan látja a kódot:
{ // Empty block}+;
most már tisztán látjuk, mi történik itt. Van egy blokk utasításunk, amelyet egy másik utasítás követ, amely egy üres tömbön működő unary +
kifejezést tartalmaz. A záró pontosvessző automatikusan beillesztésre kerül az ASI (automatikus pontosvessző beillesztése) szabályai szerint.
böngészőkonzoljában könnyen ellenőrizheti, hogy a +
értéke 0
– ra változik-e. Az üres tömb karakterlánc-ábrázolásaként egy üres karakterlánc van, amelyet viszont a +
operátor nullára konvertál. Végül az utolsó utasítás értékét (ebben az esetben+
) a böngészőkonzol jelenti.
Alternatív megoldásként mindkét kódrészletet betáplálhatja egy JavaScript-elemzőbe, például az Esprima-ba, és összehasonlíthatja a kapott absztrakt szintaxisfákat. Itt van az AST + {}
:
{ "type": "Program", "body": }, "right": { "type": "ObjectExpression", "properties": } } } ], "sourceType": "script"}
és itt van az AST {} +
:
{ "type": "Program", "body": }, { "type": "ExpressionStatement", "expression": { "type": "UnaryExpression", "operator": "+", "argument": { "type": "ArrayExpression", "elements": }, "prefix": true } } ], "sourceType": "script"}
a zavart a JavaScript nyelvtan árnyalata okozza, amely zárójeleket használ mind az objektum literálokhoz, mind a blokkokhoz. Utasítás helyzetben egy nyitó zárójel blokkot indít, míg kifejezési helyzetben egy nyitó zárójel indítja az objektum literálját.
# töredék #4: {} + {}
végül vessünk egy pillantást az utolsó töredékünkre {} + {}
:
{} + {}// NaN
nos, két objektum literál hozzáadása szó szerint “nem szám” — de itt két objektum literált adunk hozzá? Ne hagyja, hogy a fogszabályzó újra becsapjon! Ez történik:
{ // Empty block}+{};
nagyjából ugyanaz az üzlet, mint az előző példában. Most azonban az unary plus operátort alkalmazzuk egy üres objektum literálra. Ez alapvetően ugyanaz, mint a Number({})
, ami NaN
– ot eredményez, mert az objektum literálunk nem konvertálható számra.
ha azt szeretné, hogy a JavaScript motor két üres objektum literálként értelmezze a kódot, tekerje zárójelbe az elsőt (vagy a teljes kóddarabot). Most látnia kell a várt eredményt:
({}) + {}// ""({} + {})// ""
a nyitó zárójel arra készteti az elemzőt, hogy megpróbálja felismerni egy kifejezést, ezért nem kezeli a {}
blokkot (ami állítás lenne).
#Összegzés
most látnod kell, hogy a négy kódtöredék miért értékeli úgy, ahogy. Ez egyáltalán nem önkényes vagy véletlenszerű; A típusú kényszerítés szabályait pontosan a specifikációban és a nyelvtanban lefektetett módon alkalmazzák.
csak ne feledje, hogy ha egy nyitó zárójel az első karakter, amely megjelenik egy utasításban, akkor azt egy blokk kezdeteként értelmezik, nem pedig objektum literálként.