memuat dan mengeksekusi urutan skrip

265

Ada begitu banyak cara untuk memasukkan JavaScript ke halaman html. Saya tahu tentang opsi berikut:

  • kode sebaris atau diambil dari URI eksternal
  • termasuk dalam tag <head> atau <body> [ 1 , 2 ]
  • tidak memiliki, deferatau asyncatribut (hanya skrip eksternal)
  • termasuk dalam sumber statis atau ditambahkan secara dinamis oleh skrip lain (pada kondisi parse yang berbeda, dengan metode yang berbeda)

Tidak menghitung browserscripts dari harddisk, javascript: URI dan onEvent-attributes [ 3 ], ada yang sudah 16 alternatif untuk mendapatkan JS dieksekusi dan aku yakin aku lupa sesuatu.

Saya tidak begitu peduli dengan pemuatan cepat (paralel), saya lebih ingin tahu tentang urutan eksekusi (yang mungkin tergantung pada urutan pemuatan dan urutan dokumen ). Apakah ada referensi (lintas browser) yang bagus yang benar-benar mencakup semua kasus? Misalnya http://www.websiteoptimization.com/speed/tweak/defer/ hanya berurusan dengan 6 dari mereka, dan menguji sebagian besar browser lama.

Karena saya khawatir tidak ada, inilah pertanyaan spesifik saya: Saya punya beberapa skrip kepala (eksternal) untuk inisialisasi dan pemuatan skrip. Lalu saya punya dua skrip inline statis di ujung badan. Yang pertama memungkinkan pemuat skrip secara dinamis menambahkan elemen skrip lain (merujuk js eksternal) ke tubuh. Skrip inline statis kedua ingin menggunakan js dari skrip eksternal yang ditambahkan. Bisakah ini bergantung pada yang lain yang telah dieksekusi (dan mengapa :-)?

Bergi
sumber
Pernahkah Anda melihat Memuat Skrip Tanpa Memblokir oleh Steve Souders? Agak ketinggalan zaman sekarang, tetapi masih mengandung beberapa wawasan berharga tentang perilaku browser yang diberikan teknik pemuatan skrip tertentu.
Josh Habdas

Jawaban:

331

Jika Anda tidak secara dinamis memuat skrip atau menandainya sebagai deferatau async, maka skrip dimuat dalam urutan yang ditemukan di halaman. Tidak masalah apakah itu skrip eksternal atau skrip inline - skrip dieksekusi sesuai urutan yang ditemukan pada halaman. Skrip sebaris yang datang setelah skrip eksternal ditahan hingga semua skrip eksternal yang datang sebelum mereka dimuat dan dijalankan.

Skrip Async (terlepas dari bagaimana mereka ditentukan sebagai async) memuat dan berjalan dalam urutan yang tidak terduga. Peramban memuatnya secara paralel dan bebas menjalankannya dalam urutan apa pun yang diinginkan.

Tidak ada urutan yang dapat diprediksi di antara banyak hal async. Jika seseorang membutuhkan pesanan yang dapat diprediksi, maka itu harus dikodekan dengan mendaftar untuk memuat notifikasi dari skrip async dan secara manual mengurutkan panggilan javascript ketika hal-hal yang sesuai dimuat.

Ketika tag skrip dimasukkan secara dinamis, bagaimana perilaku eksekusi akan tergantung pada browser. Anda dapat melihat bagaimana Firefox berperilaku dalam artikel referensi ini . Singkatnya, versi Firefox yang lebih baru default tag tag ditambahkan secara dinamis ke async kecuali tag script telah ditetapkan sebaliknya.

Tag skrip dengan asyncdapat dijalankan segera setelah dimuat. Faktanya, browser dapat menghentikan parser dari apa pun yang sedang dilakukan dan menjalankan skrip itu. Jadi, itu benar-benar dapat berjalan hampir kapan saja. Jika skrip di-cache, mungkin akan segera berjalan. Jika skrip membutuhkan waktu untuk memuat, skrip mungkin berjalan setelah parser selesai. Satu hal yang perlu diingat asyncadalah bahwa ia dapat berjalan kapan saja dan waktu itu tidak dapat diprediksi.

Tag skrip dengan defermenunggu hingga seluruh pengurai selesai dan kemudian menjalankan semua skrip yang ditandai dengan deferurutan mereka temui. Ini memungkinkan Anda untuk menandai beberapa skrip yang bergantung satu sama lain sebagai defer. Mereka semua akan ditunda sampai setelah parser dokumen selesai, tetapi mereka akan mengeksekusi dalam urutan yang mereka temui menjaga dependensi mereka. Saya pikir deferseperti skrip dijatuhkan ke dalam antrian yang akan diproses setelah parser selesai. Secara teknis, browser dapat mengunduh skrip di latar belakang kapan saja, tetapi mereka tidak akan menjalankan atau memblokir parser sampai setelah parser selesai mengurai halaman dan parsing serta menjalankan skrip inline apa pun yang tidak ditandai deferatau async.

Berikut kutipan dari artikel itu:

skrip yang disisipkan dieksekusi secara asinkron di IE dan WebKit, tetapi secara sinkron di Opera dan Firefox 4.0-sebelumnya.

Bagian yang relevan dari spesifikasi HTML5 (untuk peramban yang lebih baru) ada di sini . Ada banyak tulisan di sana tentang perilaku async. Jelas, spesifikasi ini tidak berlaku untuk browser yang lebih lama (atau browser yang tidak sesuai) yang perilakunya Anda mungkin harus menguji untuk menentukan.

Kutipan dari spesifikasi HTML5:

Kemudian, yang pertama dari opsi berikut yang menggambarkan situasi harus diikuti:

Jika elemen memiliki atribut src, dan elemen memiliki atribut penangguhan, dan elemen telah ditandai sebagai "parser-insert", dan elemen tersebut tidak memiliki atribut async . Elemen harus ditambahkan ke akhir daftar skrip yang akan mengeksekusi ketika dokumen telah selesai parsing yang terkait dengan Dokumen parser yang membuat elemen.

Tugas yang ditempatkan oleh sumber tugas jaringan pada antrian tugas setelah algoritma pengambilan selesai harus mengatur flag elemen "ready to be parser-dieksekusi". Parser akan menangani mengeksekusi skrip.

Jika elemen memiliki atribut src, dan elemen tersebut telah ditandai sebagai "parser-insert", dan elemen tersebut tidak memiliki atribut async . Elemen tersebut adalah skrip parsing-blocking yang tertunda dari Dokumen parser yang membuat elemen. (Hanya ada satu script per Dokumen pada satu waktu.)

Tugas yang ditempatkan oleh sumber tugas jaringan pada antrian tugas setelah algoritma pengambilan selesai harus mengatur flag elemen "ready to be parser-dieksekusi". Parser akan menangani mengeksekusi skrip.

Jika elemen tidak memiliki atribut src, dan elemen tersebut telah ditandai sebagai "parser-insert", dan Dokumen parser HTML atau parser XML yang membuat elemen skrip memiliki style sheet yang memblokir skrip . Elemen adalah skrip parsing-blocking tertunda dari Dokumen parser yang membuat elemen. (Hanya ada satu script per Dokumen pada satu waktu.)

Setel elemen "siap untuk dieksekusi parser". Parser akan menangani mengeksekusi skrip.

Jika elemen memiliki atribut src, tidak memiliki atribut async, dan tidak memiliki set flag "force-async" Elemen harus ditambahkan ke akhir daftar skrip yang akan dieksekusi dalam urutan secepat mungkin terkait dengan Dokumen elemen skrip pada saat mempersiapkan algoritma skrip dimulai.

Tugas yang ditempatkan oleh sumber tugas jaringan pada antrian tugas setelah algoritma pengambilan selesai harus menjalankan langkah-langkah berikut:

Jika elemen bukan sekarang elemen pertama dalam daftar skrip yang akan dieksekusi dalam urutan sesegera mungkin yang ditambahkan di atas, maka tandai elemen tersebut sebagai siap tetapi batalkan langkah-langkah ini tanpa menjalankan skrip belum.

Eksekusi: Jalankan blok skrip yang sesuai dengan elemen skrip pertama dalam daftar skrip yang akan dieksekusi dalam urutan sesegera mungkin.

Hapus elemen pertama dari daftar skrip ini yang akan dieksekusi dalam urutan sesegera mungkin.

Jika daftar skrip ini yang akan dieksekusi dalam urutan sesegera mungkin masih belum kosong dan entri pertama telah ditandai sebagai siap, kemudian melompat kembali ke langkah berlabel eksekusi.

Jika elemen memiliki atribut src, elemen harus ditambahkan ke set skrip yang akan mengeksekusi sesegera mungkin dari dokumen elemen skrip pada saat persiapan algoritma skrip dimulai.

Tugas yang ditempatkan oleh sumber tugas jaringan pada antrian tugas setelah algoritma pengambilan selesai harus menjalankan blok skrip dan kemudian menghapus elemen dari set skrip yang akan mengeksekusi sesegera mungkin.

Jika tidak, agen pengguna harus segera menjalankan blok skrip, bahkan jika skrip lain sudah dijalankan.


Bagaimana dengan skrip modul Javascript type="module",?

Javascript sekarang memiliki dukungan untuk memuat modul dengan sintaks seperti ini:

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

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

Atau, dengan srcatribut:

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

Semua skrip dengan type="module"secara otomatis diberi deferatribut. Ini mengunduhnya secara paralel (jika tidak sebaris) dengan memuat halaman lain dan kemudian menjalankannya secara berurutan, tetapi setelah pengurai selesai.

Modul skrip juga dapat diberikan asyncatribut yang akan menjalankan skrip modul inline sesegera mungkin, tidak menunggu hingga parser selesai dan tidak menunggu untuk menjalankan asyncskrip dalam urutan tertentu relatif terhadap skrip lain.

Ada bagan waktu yang cukup berguna yang menunjukkan pengambilan dan pelaksanaan berbagai kombinasi skrip, termasuk skrip modul di sini dalam artikel ini: Javascript Module Loading .

pacar00
sumber
Terima kasih untuk jawabannya, tapi masalahnya adalah script yang dinamis ditambahkan ke halaman, yang berarti itu dianggap async . Atau apakah itu hanya berfungsi di <head>? Dan pengalaman saya juga bahwa mereka dieksekusi dalam urutan dokumen?
Bergi
@Bergi - Jika ditambahkan secara dinamis, maka async dan urutan eksekusi tidak ditentukan kecuali Anda menulis kode untuk mengendalikannya.
jfriend00
Hanya, Kolink menyatakan yang sebaliknya ...
Bergi
@Bergi - OK, saya sudah memodifikasi jawaban saya untuk mengatakan bahwa skrip async memuat dalam urutan yang tidak ditentukan. Mereka dapat dimuat dalam urutan apa pun. Jika saya jadi Anda, saya tidak akan mengandalkan pengamatan Kolink seperti biasanya. Saya tahu tidak ada standar yang mengatakan bahwa skrip yang ditambahkan secara dinamis harus segera dijalankan dan harus memblokir skrip lain agar tidak berjalan hingga dimuat. Saya berharap itu tergantung pada browser dan mungkin juga tergantung pada faktor lingkungan (apakah skrip di-cache, dll ...).
jfriend00
1
@RuudLenders - Terserah implementasi browser. Menemukan tag skrip sebelumnya dalam dokumen, tetapi ditandai dengan defermemberi parser kesempatan untuk memulai unduhannya lebih cepat sambil tetap menunda pelaksanaannya. Perhatikan bahwa jika Anda memiliki banyak skrip dari host yang sama, maka memulai pengunduhan lebih awal sebenarnya dapat memperlambat pengunduhan orang lain dari host yang sama (karena mereka bersaing untuk bandwidth) yang sedang ditunggu halaman Anda (yang tidak defer) jadi ini bisa menjadi pedang bermata dua.
jfriend00
13

Browser akan menjalankan skrip sesuai urutan yang ditemukannya. Jika Anda memanggil skrip eksternal, skrip akan memblokir halaman hingga skrip dimuat dan dieksekusi.

Untuk menguji fakta ini:

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

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

Script yang ditambahkan secara dinamis dieksekusi segera setelah ditambahkan ke dokumen.

Untuk menguji fakta ini:

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

Urutan peringatan "ditambahkan" -> "halo!" -> "final"

Jika dalam skrip Anda mencoba mengakses elemen yang belum tercapai (contoh <script>do something with #blah</script><div id="blah"></div>:) maka Anda akan mendapatkan kesalahan.

Secara keseluruhan, ya Anda dapat memasukkan skrip eksternal dan kemudian mengakses fungsi dan variabelnya, tetapi hanya jika Anda keluar dari <script>tag saat ini dan mulai yang baru.

Niet the Dark Absol
sumber
Saya dapat mengkonfirmasi perilaku itu. Tetapi ada petunjuk di halaman umpan balik kami, bahwa itu mungkin hanya berfungsi ketika test.php di-cache. Apakah Anda tahu tautan spec / referensi tentang ini?
Bergi
4
link.js tidak memblokir. Gunakan skrip yang mirip dengan php Anda untuk mensimulasikan waktu pengunduhan yang lama.
1983
14
Jawaban ini salah. Ini tidak selalu terjadi bahwa "skrip yang ditambahkan secara dinamis dieksekusi segera setelah mereka ditambahkan ke dokumen". Terkadang ini benar (misalnya untuk versi lama Firefox), tetapi biasanya tidak. Perintah eksekusi, sebagaimana disebutkan dalam jawaban jfriend00, tidak ditentukan.
Fabio Beltramini
1
Tidak masuk akal bahwa skrip dieksekusi dalam urutan yang muncul pada halaman terlepas dari apakah mereka inline atau tidak. Lalu mengapa potongan tag manajer Google dan banyak lainnya yang saya lihat, memiliki kode untuk memasukkan skrip baru di atas semua tag skrip lain di halaman? Tidak masuk akal untuk melakukan ini, jika skrip di atas sudah dimuat pasti ?? atau saya melewatkan sesuatu.
user3094826
2

Setelah menguji banyak opsi, saya telah menemukan bahwa solusi sederhana berikut ini memuat skrip yang dimuat secara dinamis dalam urutan yang ditambahkan di semua browser modern.

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'])
Flion
sumber