Giriş
Bu makalede, tarayıcıya nasıl JavaScript yükleyeceğinizi ve nasıl çalıştıracağınızı öğreneceksiniz.
Hayır, bekleyin, geri dönün. Bu işlemin sıradan ve basit göründüğünün farkındayız. Ancak bunun, teorik olarak basit olanın eski sürümlere dayalı bir tuhaflık çukuruna dönüştüğü tarayıcıda gerçekleştiğini unutmayın. Bu farklılıkları bilmek, komut dosyalarını yüklemenin en hızlı ve en az kesinti veren yolunu seçmenize olanak tanır. Zamanınız kısıtlıysa hızlı referans bölümüne atlayın.
Öncelikle, özelliğin bir komut dosyasının indirilip yürütülebileceği çeşitli yolları nasıl tanımladığını aşağıda bulabilirsiniz:

Tüm WHATWG spesifikasyonları gibi, başlangıçta bir Scrabble fabrikasında gerçekleşen bir bomba saldırısının ardından ortaya çıkan manzara gibi görünür. Ancak 5. kez okuyup gözünüzdeki kanları sildikten sonra aslında oldukça ilginç olduğunu göreceksiniz:
İlk komut dosyamda
<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>
Ah, ne kadar basit ve neşeli. Bu durumda tarayıcı, her iki komut dosyasını da paralel olarak indirir ve sıralarını koruyarak en kısa sürede yürütür. "2.js", "1.js" çalıştırılana (veya çalıştırılamayana) kadar çalıştırılmaz. "1.js", önceki komut dosyası veya stil sayfası çalıştırılana kadar çalıştırılmaz.
Maalesef tarayıcı, tüm bunlar gerçekleşirken sayfanın daha fazla oluşturulmasını engeller. Bunun nedeni, "web'in ilk çağı"ndan kalma DOM API'leridir. Bu API'ler, ayrıştırıcının işlediği içeriğe dizelerin eklenmesine olanak tanır (ör. document.write
). Yeni tarayıcılar, belgeyi arka planda taramaya veya ayrıştırmaya ve ihtiyaç duyabileceği harici içeriklerin (js, resimler, css vb.) indirilmesini tetiklemeye devam eder ancak oluşturma işlemi engellenir.
Bu nedenle, performans dünyasının önde gelenleri, mümkün olduğunca az içeriği engellediği için komut dosyası öğelerini belgenizin sonuna koymanızı önerir. Maalesef bu, tüm HTML'niz indirilene kadar komut dosyanızın tarayıcı tarafından görülmediği anlamına gelir. Bu noktada tarayıcı, CSS, resimler ve iFrame'ler gibi diğer içerikleri indirmeye başlar. Modern tarayıcılar, görüntülere kıyasla JavaScript'e öncelik verecek kadar akıllıdır ancak daha iyisini yapabiliriz.
Teşekkürler IE. (Hayır, alay etmiyorum.)
<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>
Microsoft bu performans sorunlarını fark etti ve Internet Explorer 4'e "erteleme" özelliğini ekledi. Bu temel olarak "document.write
gibi öğeleri kullanarak ayrıştırıcıya öğe eklemeyeceğime söz veriyorum. Bu sözü bozarsam beni uygun gördüğünüz şekilde cezalandırmanız serbesttir." Bu özellik HTML4'e eklendi ve diğer tarayıcılarda da göründü.
Yukarıdaki örnekte tarayıcı, her iki komut dosyasını da paralel olarak indirir ve sıralarını koruyarak DOMContentLoaded
tetiklenmeden hemen önce yürütür.
Bir koyun fabrikasındaki bomba gibi, "erteleme" de karmaşık bir duruma dönüştü. "src" ve "defer" özellikleri ile komut dosyası etiketleri ve dinamik olarak eklenen komut dosyaları arasında 6 komut dosyası ekleme yöntemi vardır. Elbette tarayıcılar, yürütecekleri sıra konusunda anlaşamadı. Mozilla, 2009'daki durumu ele alan harika bir makale yazdı.
WHATWG, "defer" özelliğinin dinamik olarak eklenen veya "src" içermeyen komut dosyaları üzerinde hiçbir etkisi olmadığını açıkça belirterek bu davranışı netleştirdi. Aksi takdirde, ertelenen komut dosyaları, doküman ayrıştırıldıktan sonra eklendikleri sırayla çalıştırılmalıdır.
Teşekkürler IE. (Tamam, şimdi alaycı bir üslupla konuşuyorum.)
Hem verir hem de alır. Maalesef IE4-9'da komut dosyalarının beklenmedik bir sırada yürütülmesine neden olabilecek kötü bir hata var. Süreç şu şekilde işler:
1.js
console.log('1');
document.getElementsByTagName('p')[0].innerHTML = 'Changing some content';
console.log('2');
2.js
console.log('3');
Sayfada bir paragraf olduğu varsayıldığında, günlüklerin beklenen sırası [1, 2, 3] olsa da IE9 ve önceki sürümlerde [1, 3, 2] olur. Belirli DOM işlemleri, IE'nin mevcut komut dosyası yürütmesini duraklatmasına ve devam etmeden önce bekleyen diğer komut dosyalarını yürütmesine neden olur.
Ancak IE10 ve diğer tarayıcılar gibi hatasız uygulamalarda bile komut dosyası yürütme, dokümanın tamamı indirilip ayrıştırılana kadar geciktirir. DOMContentLoaded
için zaten bekleyecekseniz bu yöntemi kullanabilirsiniz. Ancak performans konusunda gerçekten agresif olmak istiyorsanız dinleyici eklemeye ve daha erken başlatmaya başlayabilirsiniz…
HTML5'in yardımı
<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>
HTML5 bize document.write
kullanmayacağınızı varsaymasına rağmen belgenin ayrıştırılmasını beklemeden yürüten yeni bir özellik olan "async"i sundu. Tarayıcı, her iki komut dosyasını da paralel olarak indirir ve mümkün olan en kısa sürede yürütür.
Maalesef, mümkün olan en kısa sürede yürütülecekleri için "2.js", "1.js"den önce yürütülebilir. Bağımsızlarsa bu sorun olmaz. Belki de "1.js", "2.js" ile hiçbir ilgisi olmayan bir izleme komut dosyasıdır. Ancak "1.js", "2.js"nin bağlı olduğu jQuery'nin bir CDN kopyasıysa sayfanız, bir… bilmiyorum… bunun için aklıma bir şey gelmiyor.
İhtiyacımız olan şey bir JavaScript kitaplığı.
İdeal olan, oluşturmayı engellemeden bir dizi komut dosyasının hemen indirilmesi ve eklendikleri sırayla en kısa sürede yürütülmesidir. Maalesef HTML sizden nefret ediyor ve bunu yapmanıza izin vermiyor.
Sorun, JavaScript tarafından birkaç şekilde ele alındı. Bazıları, JavaScript'inizde değişiklik yapmanızı ve kitaplığın doğru sırada çağırdığı bir geri çağırma işlevine (ör. RequireJS) sarmalamanızı gerektiriyordu. Diğerleri, paralel olarak indirmek için XHR'yi, ardından doğru sırada eval()
'yi kullanırdı. Bu yöntem, CORS başlığı bulunmadığı ve tarayıcı tarafından desteklenmediği sürece başka bir alandaki komut dosyalarında çalışmazdı. Bazıları LabJS gibi süper sihirli kodlar bile kullandı.
Bu saldırılar, tarayıcının kaynağı indirmesi için kandırılmasını ve indirme işlemi tamamlandığında bir etkinliği tetikleyecek ancak etkinliğin yürütülmesini önleyecek şekilde indirme işlemini gerçekleştirmesini içeriyordu. LabJS'de komut dosyası, yanlış bir mime türüyle (ör. <script type="script/cache" src="...">
) eklenir. Tüm komut dosyaları indirildikten sonra, tarayıcının bunları doğrudan önbellekten alıp sırayla hemen yürütmesi umuduyla doğru türde tekrar eklenir. Bu, kullanışlı ancak belirtilmemiş bir davranışa bağlıydı ve HTML5'te tarayıcıların tanınmayan türde komut dosyaları indirmemesi gerektiği belirtildiğinde bozuldu. LabJS'nin bu değişikliklere uyum sağladığını ve artık bu makaledeki yöntemlerin bir kombinasyonunu kullandığını belirtmek isteriz.
Ancak komut dosyası yükleyicilerin kendi performans sorunları vardır. Yönettiği komut dosyalarının indirilmeye başlaması için kitaplığın JavaScript'inin indirilip ayrıştırılmasını beklemeniz gerekir. Ayrıca komut dosyası yükleyiciyi nasıl yükleyeceğiz? Komut dosyası yükleyiciye ne yükleyeceğini söyleyen komut dosyasını nasıl yükleyeceğiz? Kimler izleyicileri izliyor? Why am I naked? Bunların hepsi zor sorular.
Diğer komut dosyalarını indirmeyi düşünmeden önce ek bir komut dosyası indirmeniz gerekiyorsa performans savaşını kaybetmişsiniz demektir.
DOM'un yardımı
Yanıt aslında HTML5 spesifikasyonundadır ancak komut dosyası yükleme bölümünün en altında gizlidir.
Bunu "Dünyalı" olarak çevirelim:
[
'//other-domain.com/1.js',
'2.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
document.head.appendChild(script);
});
Dinamik olarak oluşturulan ve dokümana eklenen komut dosyaları varsayılan olarak eşzamansızdır. Bu komut dosyaları, oluşturmayı engellemez ve indirildikten hemen sonra yürütülür. Bu nedenle, yanlış sırada gösterilebilirler. Ancak bunları açıkça eşzamanlı olmayan olarak işaretleyebiliriz:
[
'//other-domain.com/1.js',
'2.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
Bu sayede komut dosyalarımız, düz HTML ile elde edilemeyecek bir davranış karışımı sunar. Açıkça asynkron olmadıkları için komut dosyaları, ilk düz HTML örneğimizde eklendikleri sıraya eklenir. Ancak dinamik olarak oluşturuldukları için belge ayrıştırma işleminin dışında çalıştırılırlar. Bu nedenle, indirilirken oluşturma işlemi engellenmez (asenkron olmayan komut dosyası yüklemeyi, hiçbir zaman iyi bir şey olmayan senkron XHR ile karıştırmayın).
Yukarıdaki komut dosyası, sayfaların başına satır içi olarak eklenmelidir. Bu komut dosyası, aşamalı oluşturmayı kesintiye uğratmadan komut dosyası indirme işlemlerini en kısa sürede sıraya koyar ve belirttiğiniz sırada en kısa sürede yürütür. "2.js", "1.js"den önce indirilebilir ancak "1.js" başarıyla indirilip çalıştırılana veya bu işlemlerden biri başarısız olana kadar yürütülmez. Yaşasın! Asynchron-download ancak ordered-execution!
Komut dosyalarının bu şekilde yüklenmesi, Safari 5.0 hariç async özelliğini destekleyen her şey tarafından desteklenir (5.1 desteklenir). Ayrıca, Firefox ve Opera'nın tüm sürümleri desteklenir. Çünkü bu sürümler, async özelliğini desteklemez ve dinamik olarak eklenen komut dosyalarını dokümana eklendikleri sırayla yürütür.
Komut dosyalarını yüklemenin en hızlı yolu bu, değil mi? Evet.
Hangi komut dosyalarının yükleneceğine dinamik olarak karar veriyorsanız evet, aksi takdirde muhtemelen hayır. Yukarıdaki örnekte, tarayıcının hangi komut dosyalarının indirileceğini bulmak için komut dosyasını ayrıştırması ve yürütmesi gerekir. Bu şekilde komut dosyalarınız önceden yükleme tarayıcılarından gizlenmiş olur. Tarayıcılar, bir sonraki ziyaret edeceğiniz sayfalardaki kaynakları keşfetmek veya ayrıştırıcı başka bir kaynak tarafından engellenirken sayfa kaynaklarını keşfetmek için bu tarayıcıları kullanır.
Aşağıdaki kodu dokümanın başına ekleyerek bulunabilirliği tekrar ekleyebiliriz:
<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">
Bu, tarayıcıya sayfanın 1.js ve 2.js dosyalarına ihtiyacı olduğunu bildirir. link[rel=subresource]
, link[rel=prefetch]
'a benzer ancak farklı anlamlara sahiptir. Ne yazık ki bu özellik şu anda yalnızca Chrome'da destekleniyor. Hangi komut dosyalarının yükleneceğini iki kez belirtmeniz gerekir: bir kez bağlantı öğeleri aracılığıyla, bir kez de komut dosyanızda.
Düzeltme: Bu bilgilerin önceden yükleme tarayıcı tarafından alındığını belirtmiştik. Ancak bu bilgiler normal ayrıştırıcı tarafından alınır. Ancak ön yükleme tarayıcı bu komut dosyalarını algılayabilir, ancak henüz bunu yapmıyor. Öte yandan, yürütülebilir kod tarafından dahil edilen komut dosyaları hiçbir zaman ön yüklenemez. Yorumlarda beni düzelten Yoav Weiss'e teşekkürler.
Bu makale beni üzüyor
Bu durum moral bozucu ve moralinizin bozulduğunu anlıyoruz. Yürütme sırasını kontrol ederken komut dosyalarını hızlı ve eşzamansız bir şekilde indirmenin yinelenmeyen ancak açıklayıcı bir yolu yoktur. HTTP2/SPDY ile istek yükü, komut dosyalarını tek tek önbelleğe alınabilen birden fazla küçük dosyada yayınlamanın en hızlı yol olabileceği noktaya kadar azaltılabilir. Aşağıdakileri düşünün:
<script src="dependencies.js"></script>
<script src="enhancement-1.js"></script>
<script src="enhancement-2.js"></script>
<script src="enhancement-3.js"></script>
…
<script src="enhancement-10.js"></script>
Her geliştirme komut dosyası belirli bir sayfa bileşeniyle ilgilenir ancak dependencies.js'deki yardımcı işlevleri gerektirir. İdeal olarak, tümünü eşzamansız olarak indirip geliştirme komut dosyalarını mümkün olan en kısa sürede, herhangi bir sırada ancak dependencies.js'den sonra yürütmek isteriz. Bu, progresif progresif geliştirmedir. Maalesef komut dosyalarının kendisi dependencies.js dosyasının yükleme durumunu izlemek için değiştirilmediği sürece bunu yapmanın açık bir yolu yoktur. enhancement-10.js dosyasının yürütülmesi 1-9 arasında engelleneceğinden, async=false bile bu sorunu çözmez. Aslında, bu işlemi herhangi bir hile olmadan mümkün kılan tek bir tarayıcı var…
IE'nin bir fikri var.
IE, komut dosyalarını diğer tarayıcılardan farklı şekilde yükler.
var script = document.createElement('script');
script.src = 'whatever.js';
IE, "whatever.js" dosyasını indirmeye başlar. Diğer tarayıcılar, komut dosyası dokümana eklenene kadar indirme işlemini başlatmaz. IE'de, yükleme ilerleme durumunu bize bildiren bir "readystatechange" etkinliği ve "readystate" özelliği de vardır. Bu, komut dosyalarının yüklenmesini ve yürütülmesini bağımsız olarak kontrol etmemize olanak tanıdığı için gerçekten çok kullanışlıdır.
var script = document.createElement('script');
script.onreadystatechange = function() {
if (script.readyState == 'loaded') {
// Our script has download, but hasn't executed.
// It won't execute until we do:
document.body.appendChild(script);
}
};
script.src = 'whatever.js';
Dokümana ne zaman komut dosyası ekleneceğini seçerek karmaşık bağımlılık modelleri oluşturabiliriz. IE, 6 sürümünden beri bu modeli desteklemektedir. Oldukça ilginç ancak async=false
ile aynı ön yükleyici bulunabilirlik sorunundan muzdarip.
Yeter! Komut dosyalarını nasıl yüklemeliyim?
Tamam, tamam. Komut dosyalarını oluşturmayı engellemeyecek, tekrar içermeyecek ve mükemmel tarayıcı desteği sunacak şekilde yüklemek istiyorsanız önerdiğimiz yöntemi aşağıda bulabilirsiniz:
<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>
Bu. body öğesinin sonunda. Evet, web geliştirici olmak Sisifos'a (boom! Yunan mitolojisi referansı için 100 hipster puanı!). HTML ve tarayıcılardaki sınırlamalar, çok daha iyi sonuçlar elde etmemizi engelliyor.
JavaScript modülleri, komut dosyalarını yüklemenin ve yürütme sırası üzerinde kontrol sağlamanın beyanla ifade edilen, engellenmeyen bir yolunu sunarak bizi kurtaracaktır. Bunun için komut dosyalarının modül olarak yazılması gerekir.
Eww, şu anda kullanabileceğimiz daha iyi bir şey olmalı.
Anlaşıldı. Performans konusunda gerçekten agresif olmak istiyorsanız ve biraz karmaşıklık ve tekrardan rahatsız olmuyorsanız yukarıdaki ipuçlarının birkaçını birleştirebilirsiniz.
Öncelikle, ön yükleyiciler için alt kaynak beyanını ekleriz:
<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">
Ardından, belgenin başlığında satır içi olarak komut dosyalarımızı JavaScript ile yükleriz. Bu işlem için async=false
kullanırız. IE'nin hazır duruma dayalı komut dosyası yükleme özelliğine ve ardından ertelemeye geri döneriz.
var scripts = [
'1.js',
'2.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];
// Watch scripts load in IE
function stateChange() {
// Execute as many scripts in order as we can
var pendingScript;
while (pendingScripts[0] && pendingScripts[0].readyState == 'loaded') {
pendingScript = pendingScripts.shift();
// avoid future loading events from this script (eg, if src changes)
pendingScript.onreadystatechange = null;
// can't just appendChild, old IE bug if element isn't closed
firstScript.parentNode.insertBefore(pendingScript, firstScript);
}
}
// loop through our script urls
while (src = scripts.shift()) {
if ('async' in firstScript) { // modern browsers
script = document.createElement('script');
script.async = false;
script.src = src;
document.head.appendChild(script);
}
else if (firstScript.readyState) { // IE<10
// create a script and add it to our todo pile
script = document.createElement('script');
pendingScripts.push(script);
// listen for state changes
script.onreadystatechange = stateChange;
// must set src AFTER adding onreadystatechange listener
// else we'll miss the loaded event for cached scripts
script.src = src;
}
else { // fall back to defer
document.write('<script src="' + src + '" defer></'+'script>');
}
}
Birkaç hile ve sıkıştırma işleminden sonra, komut dosyası 362 bayt ve komut dosyası URL'lerinizden oluşur:
!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
"//other-domain.com/1.js",
"2.js"
])
Basit bir komut dosyası dahil etmeye kıyasla ek baytlara değer mi? Komut dosyalarını koşullu olarak yüklemek için zaten JavaScript kullanıyorsanız (BBC gibi) bu indirmeleri daha erken tetiklemekten de yararlanabilirsiniz. Aksi takdirde, belki de değil, basit gövde sonu yöntemini kullanın.
WHATWG komut dosyası yükleme bölümünün neden bu kadar geniş olduğunu anladım. Bir içecek istiyorum.
Hızlı referans
Düz komut dosyası öğeleri
<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>
Spesifikasyonda: Birlikte indirin, bekleyen CSS'lerden sonra sırayla yürütün, tamamlanana kadar oluşturmayı engelleyin. Tarayıcılar: Evet efendim.
Ertele
<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>
Spesifikasyonda: Birlikte indirin, DOMContentLoaded'den hemen önce sırayla yürütün. "src" içermeyen komut dosyalarında "defer"i yoksay. IE 10'dan eski sürümler: 1.js'nin yarısında 2.js'yi yürütebilirim. Bu eğlenceli değil mi? Kırmızı renkli tarayıcılar: Bu "erteleme" şeyinin ne olduğundan haberim yok. Komut dosyalarını yokmuş gibi yükleyeceğim. Diğer tarayıcılar: Tamam, ancak "src" olmadan komut dosyalarında "defer"i yoksaymayabilirim.
Asenk.
<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>
Özellik: Birlikte indirin, indirildikleri sırayla yürütün. Kırmızı renkli tarayıcılar: "async" nedir? Komut dosyalarını bu özellik yokmuş gibi yükleyeceğim. Diğer tarayıcılarda: Evet, tamam.
Eş zamansız yanlış
[
'1.js',
'2.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
Spesifikasyonda: Birlikte indirin, tüm indirme işlemi tamamlanır tamamlanmaz sırayla yürütün. Firefox < 3.6, Opera: Bu "async" şeyin ne olduğundan haberim yok ama JS aracılığıyla eklenen komut dosyalarını eklendikleri sırayla yürütüyorum. Safari 5.0'ta: "async" ifadesini anlıyorum ancak JS ile "false" olarak ayarlanmasını anlamıyorum. Komut dosyalarınızı, geldiklerinde sırası fark etmeksizin yürüteceğim. IE 10'dan eski sürümler: "async" hakkında hiçbir fikrimiz yok ancak "onreadystatechange" kullanılarak bu sorun giderilebilir. Diğer kırmızı renkli tarayıcılar: Bu "async" işini anlamıyoruz. Komut dosyalarınızı, geldikleri anda herhangi bir sırayla yürüteceğiz. Diğer tüm mesajlar: Ben sizin dostunuzum, bu işi kurallara uygun şekilde yapacağız.