ładuj i wykonuj kolejność skryptów


265

Jest tak wiele różnych sposobów na włączenie JavaScript na stronie HTML. Wiem o następujących opcjach:

  • kod wewnętrzny lub ładowany z zewnętrznego identyfikatora URI
  • zawarte w znaczniku <head> lub <body> [ 1 , 2 ]
  • brak deferlub asyncatrybut (tylko zewnętrzne skrypty)
  • zawarty w źródle statycznym lub dodawany dynamicznie przez inne skrypty (w różnych stanach analizy, przy użyciu różnych metod)

Nie licząc skryptów przeglądarki z dysku twardego, javascript: URI i onEvent-attributes [ 3 ], istnieje już 16 alternatyw dla wykonania JS i jestem pewien, że coś zapomniałem.

Nie interesuje mnie szybkie (równoległe) ładowanie, bardziej jestem ciekawy kolejności wykonywania (która może zależeć od kolejności ładowania i kolejności dokumentów ). Czy istnieje dobre odniesienie (dla różnych przeglądarek), które obejmuje naprawdę wszystkie przypadki? Np. Http://www.websiteoptimization.com/speed/tweak/defer/ zajmuje się tylko 6 z nich i testuje głównie stare przeglądarki.

Ponieważ obawiam się, że nie ma, oto moje konkretne pytanie: Mam kilka (zewnętrznych) skryptów głównych do inicjalizacji i ładowania skryptów. Potem mam dwa statyczne, wbudowane skrypty na końcu ciała. Pierwszy pozwala modułowi ładującemu skrypt dynamicznie dołączać kolejny element skryptu (odwołując się do zewnętrznego js) do ciała. Drugi ze statycznych, wbudowanych skryptów chce użyć js z dodanego zewnętrznego skryptu. Czy może polegać na tym, że drugi został wykonany (i dlaczego :-)?


Czy patrzyłeś na Ładowanie skryptów bez blokowania przez Steve'a Soudersa? Jest to trochę przestarzałe, ale wciąż zawiera cenne informacje na temat zachowania przeglądarki, biorąc pod uwagę konkretną technikę ładowania skryptu.
Josh Habdas

Odpowiedzi:


331

Jeśli nie ładujesz dynamicznie skryptów ani nie zaznaczasz ich jako deferlub async, wówczas skrypty są ładowane w kolejności podanej na stronie. Nie ma znaczenia, czy jest to skrypt zewnętrzny czy skrypt wbudowany - są one wykonywane w kolejności, w jakiej występują na stronie. Skrypty wbudowane, które pojawiają się po skryptach zewnętrznych, są wstrzymywane do momentu załadowania i uruchomienia wszystkich skryptów zewnętrznych poprzedzających je.

Skrypty asynchroniczne (niezależnie od tego, jak są określone jako asynchroniczne) ładują się i działają w nieprzewidywalnej kolejności. Przeglądarka ładuje je równolegle i można je uruchamiać w dowolnej kolejności.

Nie ma przewidywalnego porządku wśród wielu rzeczy asynchronicznych. Gdyby ktoś potrzebował przewidywalnego porządku, musiałby zostać zakodowany poprzez rejestrację powiadomień o ładowaniu ze skryptów asynchronicznych i ręczne sekwencjonowanie wywołań javascript, gdy odpowiednie rzeczy zostaną załadowane.

Gdy znacznik skryptu jest wstawiany dynamicznie, sposób zachowania kolejności wykonywania zależeć będzie od przeglądarki. W tym artykule referencyjnym możesz zobaczyć, jak zachowuje się Firefox . Krótko mówiąc, nowsze wersje Firefoksa domyślnie domyślnie dodają dynamicznie znacznik skryptu do asynchronizacji, chyba że znacznik skryptu został ustawiony inaczej.

Tag skryptu z asyncmożna uruchomić natychmiast po załadowaniu. W rzeczywistości przeglądarka może zatrzymać analizator składni od wszystkiego, co robił i uruchomić ten skrypt. Tak naprawdę może działać prawie w dowolnym momencie. Jeśli skrypt został buforowany, może zostać uruchomiony niemal natychmiast. Jeśli skrypt ładuje się chwilę, może działać po zakończeniu parsera. Jedyną rzeczą, o której należy pamiętać, asyncjest to, że może ona działać w dowolnym momencie i że czas ten nie jest przewidywalny.

Tag skryptu z deferoczekiwaniem na zakończenie całego parsera, a następnie uruchamia wszystkie skrypty oznaczone deferw kolejności ich napotkania. Umożliwia to oznaczenie kilku skryptów, które zależą od siebie jako defer. Wszystkie zostaną odłożone na później, aż do zakończenia parsera dokumentów, ale wykonają je w kolejności, w jakiej napotkano, zachowując swoje zależności. Myślę, że deferskrypty są upuszczane w kolejce, która zostanie przetworzona po zakończeniu parsera. Technicznie rzecz biorąc, przeglądarka może pobierać skrypty w tle w dowolnym momencie, ale nie wykonają ani nie zablokują parsera, dopóki parser nie zakończy parsowania strony oraz parsowania i uruchamiania wszelkich wbudowanych skryptów, które nie są oznaczone deferlub async.

Oto cytat z tego artykułu:

skrypty wstawione w skrypcie wykonują się asynchronicznie w IE i WebKit, ale synchronicznie w Operze i Firefoksie wcześniejszym niż 4.0.

Odpowiednia część specyfikacji HTML5 (dla nowszych zgodnych przeglądarek) znajduje się tutaj . Wiele się tam pisze o zachowaniu asynchronicznym. Oczywiście ta specyfikacja nie dotyczy starszych przeglądarek (lub źle dostosowanych przeglądarek), których zachowanie prawdopodobnie musiałbyś przetestować w celu ustalenia.

Cytat ze specyfikacji HTML5:

Następnie należy zastosować pierwszą z następujących opcji opisujących sytuację:

Jeśli element ma atrybut src, a element ma atrybut odroczenia, a element został oflagowany jako „wstawiony przez analizator składni”, a element nie ma atrybutu asynchronicznego, element należy dodać na końcu listy skrypty, które zostaną wykonane, gdy dokument zakończy parsowanie powiązane z dokumentem analizatora składni, który utworzył element.

Zadanie, które źródło zadania sieciowego umieszcza w kolejce zadań po zakończeniu algorytmu pobierania, musi ustawić flagę elementu „gotowe do wykonania przez analizator składni” elementu. Analizator składni obsłuży wykonywanie skryptu.

Jeśli element ma atrybut src, a element został oflagowany jako „wstawiony przez analizator składni”, a element nie ma atrybutu asynchronicznego, to element jest oczekującym skryptem blokującym parsowanie dokumentu analizatora składni, który utworzył element. (W danym dokumencie może istnieć tylko jeden taki skrypt).

Zadanie, które źródło zadania sieciowego umieszcza w kolejce zadań po zakończeniu algorytmu pobierania, musi ustawić flagę elementu „gotowe do wykonania przez analizator składni” elementu. Analizator składni obsłuży wykonywanie skryptu.

Jeśli element nie ma atrybutu src, a element został oflagowany jako „wstawiony przez analizator składni”, a dokument analizatora składni HTML lub analizatora składni XML, który utworzył element skryptu, ma arkusz stylów blokujący skrypty Element jest oczekujący skrypt blokujący parsowanie dokumentu parsera, który utworzył element. (W danym dokumencie może istnieć tylko jeden taki skrypt).

Ustaw flagę elementu „gotowy do wykonania przez analizator składni”. Analizator składni obsłuży wykonywanie skryptu.

Jeśli element ma atrybut src, nie ma atrybutu asynchronicznego i nie ma ustawionej flagi „force-async” Element należy dodać na końcu listy skryptów, które zostaną wykonane w kolejności, jak najszybciej powiązane z Dokumentem elementu skryptu w momencie przygotowania algorytmu skryptu.

Zadanie, które źródło zadania sieciowego umieszcza w kolejce zadań po zakończeniu algorytmu pobierania, musi uruchomić następujące kroki:

Jeśli element nie jest teraz pierwszym elementem na liście skryptów, które wykonają się w jak najkrótszym czasie, do którego został dodany powyżej, oznacz element jako gotowy, ale przerwij te kroki, nie wykonując jeszcze skryptu.

Wykonanie: Wykonaj blok skryptu odpowiadający pierwszemu elementowi skryptu na tej liście skryptów, które zostaną wykonane w kolejności tak szybko, jak to możliwe.

Usuń pierwszy element z tej listy skryptów, które będą wykonywane w kolejności tak szybko, jak to możliwe.

Jeśli ta lista skryptów, które będą uruchamiane w kolejności tak szybko, jak to możliwe, nadal nie jest pusta, a pierwszy wpis został już oznaczony jako gotowy, wróć do wykonania oznaczonego krokiem.

Jeśli element ma atrybut src Element należy dodać do zestawu skryptów, które wykonają dokument Dokumentu elementu skryptowego w możliwie najkrótszym czasie w momencie przygotowania algorytmu skryptowego.

Zadanie, które źródło zadania sieciowego umieszcza w kolejce zadań po zakończeniu algorytmu pobierania, musi wykonać blok skryptu, a następnie usunąć element ze zbioru skryptów, który zostanie wykonany jak najszybciej.

W przeciwnym razie Agent użytkownika musi natychmiast wykonać blok skryptu, nawet jeśli inne skrypty już działają.


Co ze skryptami modułów JavaScript type="module"?

JavaScript obsługuje teraz ładowanie modułów o takiej składni:

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

Lub z srcatrybutem:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

Wszystkie skrypty z type="module"automatycznie otrzymują deferatrybut. Spowoduje to pobranie ich równolegle (jeśli nie w linii) z innym ładowaniem strony, a następnie uruchomi je w kolejności, ale po zakończeniu parsera.

Skrypty modułowe mogą również otrzymać asyncatrybut, który będzie uruchamiał wbudowane skrypty modułowe tak szybko, jak to możliwe, nie czekając na zakończenie parsera i nie czekając na uruchomienie asyncskryptu w określonej kolejności w stosunku do innych skryptów.

Istnieje całkiem przydatny wykres na osi czasu, który pokazuje pobieranie i wykonywanie różnych kombinacji skryptów, w tym skryptów modułowych w tym artykule: Ładowanie modułu JavaScript .


Dzięki za odpowiedź, ale problem polega na tym, że skrypt jest dynamicznie dodawany do strony, co oznacza, że jest uważany za asynchroniczny . Czy to działa tylko w <head>? A z mojego doświadczenia wynika, że ​​są one wykonywane w kolejności dokumentów?
Bergi,

@Bergi - Jeśli jest dodawany dynamicznie, to jest asynchroniczny, a kolejność wykonywania jest nieokreślona, ​​chyba że napiszesz kod do jego kontroli.
jfriend00

Po prostu Kolink twierdzi coś przeciwnego ...
Bergi,

@ Bergi - OK, zmodyfikowałem swoją odpowiedź, aby powiedzieć, że skrypty asynchroniczne ładują się w nieokreślonej kolejności. Można je ładować w dowolnej kolejności. Gdybym był tobą, nie liczyłbym, że obserwacja Kolinka będzie taka, jaka jest zawsze. Nie znam żadnego standardu, który mówi, że dynamicznie dodawany skrypt musi być uruchamiany natychmiast i musi blokować uruchamianie innych skryptów, dopóki nie zostanie załadowany. Spodziewałbym się, że będzie to zależało od przeglądarki, a może być może także od czynników środowiskowych (czy skrypt jest buforowany itp.).
jfriend00

1
@RuudLenders - To zależy od implementacji przeglądarki. Wystąpienie znacznika skryptu wcześniej w dokumencie, ale oznaczone symbolem, deferdaje parserowi możliwość rozpoczęcia pobierania wcześniej, jednocześnie odraczając jego wykonanie. Pamiętaj, że jeśli masz wiele skryptów z tego samego hosta, to rozpoczęcie pobierania wcześniej może faktycznie spowolnić pobieranie innych z tego samego hosta (ponieważ rywalizują o przepustowość), na który czeka Twoja strona (które nie są defer), więc może to być obosieczny miecz.
jfriend00

13

Przeglądarka wykona skrypty w kolejności, w jakiej je znajdzie. Wywołanie zewnętrznego skryptu spowoduje zablokowanie strony, dopóki skrypt nie zostanie załadowany i wykonany.

Aby przetestować ten fakt:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

Dynamicznie dodawane skrypty są wykonywane, gdy tylko zostaną dołączone do dokumentu.

Aby przetestować ten fakt:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

Kolejność alertów to „dołączone” -> „cześć!” -> „ostateczny”

Jeśli w skrypcie próbujesz uzyskać dostęp do elementu, który nie został jeszcze osiągnięty (przykład <script>do something with #blah</script><div id="blah"></div>:), pojawi się błąd.

Ogólnie rzecz biorąc, tak, możesz dołączyć zewnętrzne skrypty, a następnie uzyskać dostęp do ich funkcji i zmiennych, ale tylko wtedy, gdy wyjdziesz z bieżącego <script>znacznika i uruchomisz nowy.


Mogę potwierdzić to zachowanie. Na naszych stronach z opiniami znajdują się jednak wskazówki, że może to działać tylko wtedy, gdy test.php jest buforowany. Czy znasz jakieś linki do specyfikacji / referencji na ten temat?
Bergi,

4
link.js nie blokuje. Użyj skryptu podobnego do php, aby zasymulować długi czas pobierania.
1983

14
Ta odpowiedź jest niepoprawna. Nie zawsze jest tak, że „dynamicznie dodawane skrypty są wykonywane, gdy tylko zostaną dołączone do dokumentu”. Czasami jest to prawda (np. W przypadku starych wersji Firefoksa), ale zazwyczaj tak nie jest. Kolejność wykonywania, jak wspomniano w odpowiedzi jfriend00, nie jest określona.
Fabio Beltramini

1
Nie ma sensu, aby skrypty były wykonywane w kolejności, w jakiej pojawiają się na stronie, niezależnie od tego, czy są wbudowane, czy nie. Dlaczego więc fragment kodu menedżera tagów Google i wiele innych, które widziałem, mają kod do wstawiania nowego skryptu ponad wszystkimi innymi tagami skryptu na stronie? Nie ma sensu tego robić, jeśli powyższe skrypty zostały już z pewnością załadowane? czy coś mi brakuje.
user3094826,


2

Po przetestowaniu wielu opcji odkryłem, że następujące proste rozwiązanie polega na ładowaniu dynamicznie ładowanych skryptów w kolejności, w jakiej są one dodawane we wszystkich nowoczesnych przeglądarkach

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.