Bir web sayfasında JavaScript betiklerinin yüklenme ve yürütme sırası nedir?
Bir HTML sayfasına JavaScript eklemenin çok farklı yolları vardır. Şu seçenekleri biliyorum:
- satır içi kod veya dış URI'den yüklenmiş
- <head> veya <body> etiketi içinde dahil edildi [1,2]
- hiçbiri yok,
deferveyaasyncözniteliği (sadece harici betikler) - statik kaynağa dahil edilmiş veya diğer betikler tarafından dinamik olarak eklenmiş (farklı ayrıştırma durumlarında, farklı yöntemlerle)
Sabit diskten gelen tarayıcı betiklerini, javascript:URI’larını ve onEvent-özelliklerini [3] saymazsak, JS’nin çalıştırılmasını sağlayacak zaten 16 alternatif var ve bir şeyi unuttuğumdan eminim.
Hızlı (paralel) yükleme konusunda çok kaygılı değilim, daha çok yürütme sırası ile ilgileniyorum (bu, yükleme sırasına ve belge sırasına bağlı olabilir). Gerçekten tüm durumları kapsayan (tarayıcılar arası) iyi bir referans var mı? Örneğin http://www.websiteoptimization.com/speed/tweak/defer/ yalnızca 6 durumu ele alıyor ve testler çoğunlukla eski tarayıcılarda yapılmış.
Korkarım yok, işte benim spesifik sorum: Bazı (harici) başlık betiklerim var, başlatma ve betik yükleme için. Sonra gövdenin sonunda iki statik, satır içi betik var. Birincisi, betik yükleyicinin başka bir betik öğesini (harici js’ye referans vererek) gövdeye dinamik olarak eklemesine izin veriyor. İkinci statik, satır içi betik ise eklenen harici betikten js kullanmak istiyor. İlk betiğin yürütülmüş olmasına güvenebilir mi (ve neden :-)?
Cevap #1
Eğer betikleri dinamik olarak yüklemiyor veya onları defer ya da async olarak işaretlemiyorsanız, betikler sayfada karşılaştıkları sırayla yüklenir. Bunun harici bir betik olup olmadığı ya da satır içi bir betik olması fark etmez - betikler sayfada karşılaştıkları sırayla çalıştırılır. Harici betiklerden sonra gelen satır içi betikler, kendilerinden önce gelen tüm harici betikler yüklenip çalıştırılana kadar bekletilir.
Asenkron betikler (asenkron olarak nasıl belirtildiklerine bakılmaksızın) önceden tahmin edilemeyen bir sırayla yüklenir ve çalıştırılır. Tarayıcı bunları paralel olarak yükler ve bunları istediği sırayla çalıştırmakta serbesttir.
Birden fazla eşzamansız şey arasında öngörülebilir bir sıra yoktur. Eğer öngörülebilir bir sıraya ihtiyaç varsa, bu, eşzamansız betiklerden yükleme bildirimleri alarak ve uygun şeyler yüklendiğinde JavaScript çağrılarını manuel olarak sıralayarak kodlanmak zorunda olur.
Bir script etiketi dinamik olarak eklendiğinde, yürütme sırasının nasıl davrandığı tarayıcıya bağlıdır. Firefox'un nasıl davrandığını bu referans makalede görebilirsiniz. Özetle, Firefox'un daha yeni sürümleri, script etiketi başka şekilde ayarlanmamışsa, dinamik olarak eklenen bir script etiketini varsayılan olarak async olarak ayarlar.
async ile bir script etiketi, yüklendiği anda çalıştırılabilir. Aslında, tarayıcı, parser'ın o anda yaptığı diğer işleri durdurup bu scripti çalıştırabilir. Yani, gerçekten neredeyse her an çalışabilir. Eğer script önbelleğe alınmışsa, neredeyse hemen çalışabilir. Eğer scriptin yüklenmesi biraz zaman alıyorsa, parser işini bitirdikten sonra çalışabilir. async ile ilgili hatırlanması gereken tek şey, herhangi bir zamanda çalışabileceği ve bu zamanın öngörülemez olduğudur.
defer ile bir script etiketi, tüm ayrıştırıcı bitene kadar bekler ve ardından defer ile işaretlenmiş tüm scriptleri karşılaştıkları sırayla çalıştırır. Bu, birbirine bağımlı birkaç scripti defer olarak işaretlemenize olanak tanır. Hepsi, belge ayrıştırıcısı tamamlandıktan sonra ertelenecek, ancak karşılaştıkları sırayla çalıştırılarak bağımlılıkları korunacaktır. Ben deferi, scriptlerin ayrıştırıcı işini bitirdikten sonra işlenecek bir kuyruğa atılması gibi düşünüyorum. Teknik olarak, tarayıcı scriptleri herhangi bir zamanda arka planda indiriyor olabilir, ancak ayrıştırıcı sayfayı ayrıştırmayı ve defer veya async ile işaretlenmemiş herhangi bir satır içi scripti çalıştırmayı bitirene kadar çalıştırmaz veya ayrıştırıcıyı engellemez.
İşte o makaleden bir alıntı:
script-inserted betikleri IE ve WebKit'te eşzamanlı olmayan şekilde çalışır, ancak Opera ve 4.0 öncesi Firefox'ta eşzamanlı olarak çalışır.
İlgili HTML5 standardı bölümü (daha yeni uyumlu tarayıcılar için) burada. Asenkron davranış hakkında çok şey yazılmıştır. Açıkçası, bu standart eski tarayıcılara (veya standartlara uymayan tarayıcılara) uygulanmaz; bu tarayıcıların davranışını belirlemek için muhtemelen test etmeniz gerekecektir.
HTML5 spesifikasyonundan bir alıntı:
O zaman, durumu tanımlayan aşağıdaki seçeneklerden ilki izlenmelidir:
Öğe bir src özelliğine sahipse, ve öğe bir defer özelliğine sahipse, ve öğe "parser-inserted" olarak işaretlenmişse, ve öğe bir async özelliğine sahip değilse Öğe, elementi oluşturan ayrıştırıcının Belgesi ile ilişkili olarak belgenin ayrıştırılması tamamlandığında çalışacak olan betikler listesine eklenmelidir.
Ağ görev kaynağının, getirme algoritması tamamlandıktan sonra görev kuyruğuna yerleştirdiği görev, öğenin "parsers tarafından yürütülmeye hazır" bayrağını ayarlamalıdır. Parser, betiği yürütmeyi halledecektir.
Element bir src özniteliğine sahipse, ve element "parser-inserted" olarak işaretlenmişse, ve element bir async özniteliğine sahip değilse Element, elementi oluşturan ayrıştırıcının Belgesinin bekleyen ayrıştırmayı engelleyen betiğidir. (Her Belge için aynı anda yalnızca bir böyle betiğin olması mümkündür.)
Ağ görev kaynağının, getirme algoritması tamamlandıktan sonra görev kuyruğuna yerleştirdiği görev, öğenin "parsers tarafından yürütülmeye hazır" bayrağını ayarlamalıdır. Ayrıştırıcı (parser) betiği yürütmeyi ele alacaktır.
Öğe bir src özniteliğine sahip değilse ve öğe "parser-inserted" olarak işaretlenmişse ve script öğesini oluşturan HTML veya XML ayrıştırıcısının Belgesi, betikleri engelleyen bir stil sayfasına sahipse Öğe, öğeyi oluşturan ayrıştırıcının Belgesinin bekleyen ayrıştırmayı engelleyen betiğidir. (Belge başına aynı anda yalnızca bir böyle betik olabilir.)
Ögenin "parçacık tarafından yürütülmeye hazır" bayrağını ayarlayın. Ayrıştırıcı scripti yürütmeyi halledecektir.
Öğe bir src özniteliğine sahipse, async özniteliğine sahip değilse ve "force-async" bayrağı ayarlı değilse Öğe, bir betik algoritması başlatıldığında betik öğesinin belgesiyle ilişkili olarak mümkün olan en kısa sürede sırayla çalışacak betikler listesine eklenmelidir.
Ağ görev kaynağının, alma algoritması tamamlandıktan sonra görev kuyruğuna yerleştirdiği görev, aşağıdaki adımları çalıştırmalıdır:
Element, eklenmiş olduğu ve mümkün olan en kısa sürede sırayla çalışacak olan scriptler listesinin şu anda ilk elementi değilse, o zaman elementi hazır olarak işaretle, ancak scripti henüz çalıştırmadan bu adımları iptal et.
Yürütme: Bu listede bulunan ve sırasıyla mümkün olan en kısa sürede yürütülecek betiklerin ilk betik öğesine karşılık gelen betik bloğunu yürütün.
Bu listede yer alan ve mümkün olan en kısa sürede sırayla çalışacak betiklerden ilk öğeyi kaldırın.
Bu, mümkün olan en kısa sürede sırayla çalışacak betiklerin listesi hâlâ boş değilse ve ilk giriş zaten hazır olarak işaretlendiyse, o zaman yürütme olarak etiketlenmiş adıma geri dön.
Öğe bir src özniteliğine sahipse Öğe, bir script algoritmasının hazırlanma zamanı başladığında script öğesinin Belgesi'nin mümkün olan en kısa sürede çalıştırılacak scriptler setine eklenmelidir.
Ağ görev kaynağının, getirme algoritması tamamlandıktan sonra görev kuyruğuna yerleştirdiği görev, betik bloğunu çalıştırmalı ve ardından mümkün olan en kısa sürede çalışacak betik kümesinden öğeyi kaldırmalıdır.
Diğer durumda Kullanıcı aracısı, diğer betikler halihazırda çalışıyor olsa bile, betik bloğunu derhal yürütmelidir.
Javascript modül scriptleri, type="module" hakkında ne düşünüyorsunuz?
Javascript artık bu şekilde bir sözdizimi ile modül yüklemeyi destekliyor:
<script type="module">
import {addTextToBody} from './utils.mjs';
addTextToBody('Modules are pretty cool.');
</script>
Veya src özniteliği ile:
<script type="module" src="http://somedomain.com/somescript.mjs">
</script>
type="module" olan tüm betiklere otomatik olarak defer özelliği verilir. Bu, onları sayfanın diğer yüklemeleriyle paralel olarak (inline değilse) indirir ve ardından sıralı olarak çalıştırır, ancak parser tamamlandıktan sonra.
Modül betiklerine ayrıca async özniteliği verilebilir; bu, satır içi modül betiklerinin mümkün olan en kısa sürede çalışmasını sağlar, ayrıştırıcının bitmesini beklemeden ve diğer betiklere göre async betiğini belirli bir sırada çalıştırmayı beklemeden.
Bu makalede, Javascript Modül Yükleme, farklı betik kombinasyonlarının, modül betikleri de dahil olmak üzere, getirilmesini ve yürütülmesini gösteren oldukça kullanışlı bir zaman çizelgesi grafiği var.
Cevap #2
@addyosmani tarafından harika bir özet:
[https://addyosmani.com/blog/script-priorities/ adresinden kopyalanmıştır]
Cevap #3
Tarayıcı, betikleri bulduğu sırayla çalıştıracaktır. Bir harici betik çağırırsanız, betik yüklenip çalıştırılana kadar sayfa engellenecektir.
Bu gerçeği test etmek için:
// file: test.php
sleep(10);
die("alert('Done!');");
// HTML file:
<script type="text/javascript" src="test.php"></script>
Dinamik olarak eklenen betikler, belgeye eklendikleri anda çalıştırılır.
Bu gerçeği test etmek için:
<!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>
Uyarıların sıralaması "eklenmiş" -> "merhaba!" -> "son"
Eğer bir betikte henüz erişilmemiş bir elemana erişmeye çalışırsanız (örnek: <script>#blah ile bir şey yap</script><div id="blah"></div>), bir hata alırsınız.
Genel olarak, evet, harici betikleri dahil edebilir ve ardından bunların fonksiyonlarına ve değişkenlerine erişebilirsiniz, ancak yalnızca mevcut <script> etiketinden çıkıp yeni bir tane başlatırsanız.
Cevap #4
Birçok seçeneği test ettikten sonra, aşağıdaki basit çözümün tüm modern tarayıcılarda dinamik olarak yüklenen betikleri eklenme sırasına göre yüklediğini buldum
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'])
