Dalsze rozszerzenie poprzednich odpowiedzi ...
Z ogólnej perspektywy kompilatorów i pomijając optymalizacje specyficzne dla maszyn wirtualnych:
Najpierw przechodzimy przez fazę analizy leksykalnej, w której tokenizujemy kod.
Przykładowo można wyprodukować następujące tokeny:
[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)
Miejmy nadzieję, że powinno to zapewnić wystarczającą wizualizację, abyś mógł zrozumieć, o ile więcej (lub mniej) jest wymagane przetwarzanie.
Na podstawie powyższych tokenów wiemy, że jako fakt ARRAY_INIT zawsze tworzy tablicę. Dlatego po prostu tworzymy tablicę i wypełniamy ją. Jeśli chodzi o niejednoznaczność, na etapie analizy leksykalnej już odróżniono ARRAY_INIT od metody dostępu do właściwości obiektu (np. obj[foo]
) Lub nawiasów wewnątrz łańcuchów / literałów wyrażenia regularnego (np. „Foo [] bar” lub / [] /)
To jest malutkie, ale mamy też więcej żetonów z new Array
. Co więcej, nie jest jeszcze do końca jasne, że chcemy po prostu utworzyć tablicę. Widzimy „nowy” token, ale „nowy” co? Następnie widzimy token IDENTIFIER, który oznacza, że chcemy nowej „tablicy”, ale maszyny wirtualne JavaScript generalnie nie rozróżniają tokenu IDENTIFIER i tokenów dla „natywnych obiektów globalnych”. W związku z tym...
Za każdym razem, gdy napotkamy token IDENTIFIER, musimy sprawdzić łańcuch zasięgu. Maszyny wirtualne JavaScript zawierają „obiekt aktywacji” dla każdego kontekstu wykonania, który może zawierać obiekt „argumenty”, lokalnie zdefiniowane zmienne itp. Jeśli nie możemy go znaleźć w obiekcie Activation, zaczynamy przeszukiwać łańcuch zasięgu, aż osiągniemy zasięg globalny . Jeśli nic nie zostanie znalezione, rzucamy ReferenceError
.
Po zlokalizowaniu deklaracji zmiennej wywołujemy konstruktor. new Array
jest niejawnym wywołaniem funkcji, a ogólną zasadą jest to, że wywołania funkcji są wolniejsze podczas wykonywania (stąd dlaczego statyczne kompilatory C / C ++ pozwalają na „wstawianie funkcji” - które silniki JS JIT, takie jak SpiderMonkey, muszą wykonywać w locie)
Array
Konstruktor jest przeciążony. Konstruktor Array jest zaimplementowany jako kod natywny, więc zapewnia pewne ulepszenia wydajności, ale nadal musi sprawdzać długość argumentów i odpowiednio działać. Ponadto, w przypadku podania tylko jednego argumentu, musimy dodatkowo sprawdzić typ argumentu. new Array ("foo") produkuje ["foo"], gdzie jako new Array (1) daje [undefined]
A więc, upraszczając wszystko: dzięki literałom tablicowym maszyna wirtualna wie, że potrzebujemy tablicy; z new Array
, VM musi użyć dodatkowych cykli procesora, aby dowiedzieć się, co new Array
właściwie robi.