स्क्रिप्ट लोड होने के दौरान, गंदे पानी में डूबी रहें

परिचय

इस लेख में, हम आपको ब्राउज़र में कुछ JavaScript लोड करने और उसे लागू करने का तरीका बताएंगे.

नहीं, रुकिए, वापस आएं! हम जानते हैं कि यह आसान और सामान्य लगता है, लेकिन याद रखें कि यह ब्राउज़र में हो रहा है. यहां सिद्धांत के हिसाब से आसान काम, लेगसी की वजह से मुश्किल हो जाता है. इन समस्याओं के बारे में जानने से, स्क्रिप्ट लोड करने का सबसे तेज़ और कम रुकावट वाला तरीका चुना जा सकता है. अगर आपका शेड्यूल तय है, तो 'झटपट जानकारी' पर जाएं.

शुरुआत करने वालों के लिए, यहां बताया गया है कि स्पेसिफ़िकेशन में, स्क्रिप्ट को डाउनलोड और लागू करने के अलग-अलग तरीकों के बारे में कैसे बताया गया है:

स्क्रिप्ट लोड करने के बारे में WHATWG
स्क्रिप्ट लोड करने के बारे में WHATWG

WHATWG के सभी स्पेसिफ़िकेशन की तरह, यह शुरुआत में स्क्रैबल फ़ैक्ट्री में क्लस्टर बम के बाद की स्थिति जैसा दिखता है. हालांकि, इसे पांचवीं बार पढ़ने और अपनी आंखों से खून पोंछने के बाद, यह वाकई में काफ़ी दिलचस्प लगता है:

मेरी पहली स्क्रिप्ट में

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

वाह, कितनी आसानी से हो गया. यहां ब्राउज़र, दोनों स्क्रिप्ट को एक साथ डाउनलोड करेगा और उन्हें जल्द से जल्द लागू करेगा. साथ ही, उनके क्रम को भी बनाए रखेगा. “2.js” तब तक लागू नहीं होगा, जब तक “1.js” लागू नहीं हो जाता (या लागू नहीं हो पाता). इसी तरह, “1.js” तब तक लागू नहीं होगा, जब तक पिछली स्क्रिप्ट या स्टाइलशीट लागू नहीं हो जाती.

माफ़ करें, ऐसा होने पर ब्राउज़र पेज को रेंडर करने से रोक देता है. ऐसा “वेब के शुरुआती दौर” के DOM API की वजह से होता है. इनकी मदद से, उस कॉन्टेंट में स्ट्रिंग जोड़ी जा सकती है जिसे पार्स किया जा रहा है. जैसे, document.write. नए ब्राउज़र, बैकग्राउंड में दस्तावेज़ को स्कैन या पार्स करते रहेंगे. साथ ही, ज़रूरी बाहरी कॉन्टेंट (जैसे, JavaScript, इमेज, सीएसएस वगैरह) के लिए डाउनलोड ट्रिगर करेंगे. हालांकि, रेंडरिंग अब भी ब्लॉक रहेगी.

यही वजह है कि परफ़ॉर्मेंस की दुनिया के दिग्गज, आपके दस्तावेज़ के आखिर में स्क्रिप्ट एलिमेंट डालने का सुझाव देते हैं. ऐसा इसलिए, क्योंकि इससे कम से कम कॉन्टेंट ब्लॉक होता है. माफ़ करें, इसका मतलब है कि ब्राउज़र तब तक आपकी स्क्रिप्ट नहीं देखता, जब तक वह आपका पूरा एचटीएमएल डाउनलोड नहीं कर लेता. इस दौरान, वह सीएसएस, इमेज, और iframe जैसे अन्य कॉन्टेंट को डाउनलोड करना शुरू कर देता है. आधुनिक ब्राउज़र, इमेज के बजाय JavaScript को प्राथमिकता देने के लिए स्मार्ट हैं, लेकिन हम इससे बेहतर कर सकते हैं.

धन्यवाद IE! (नहीं, मैं व्यंग्य नहीं कर रहा/रही)

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

Microsoft ने परफ़ॉर्मेंस से जुड़ी इन समस्याओं को पहचाना और Internet Explorer 4 में “देर से लोड करें” सुविधा को लॉन्च किया. इसका मतलब है कि “मैं वादा करता/करती हूं कि document.write जैसी चीज़ों का इस्तेमाल करके, पार्स करने वाले टूल में कोई चीज़ इंजेक्ट नहीं की जाएगी. अगर मैं यह वादा नहीं निभाता, तो मुझे अपनी पसंद के मुताबिक सज़ा दी जा सकती है”. इस एट्रिब्यूट को एचटीएमएल 4 में शामिल किया गया और यह अन्य ब्राउज़र में भी दिखने लगा.

ऊपर दिए गए उदाहरण में, ब्राउज़र दोनों स्क्रिप्ट को एक साथ डाउनलोड करेगा और DOMContentLoaded ट्रिगर होने से ठीक पहले उन्हें चलाएगा. साथ ही, उनके क्रम को बनाए रखेगा.

भेड़ की फ़ैक्ट्री में क्लस्टर-बम की तरह, “देर” शब्द का इस्तेमाल गलत जगहों पर होने लगा. “src” और “defer” एट्रिब्यूट के बीच, और स्क्रिप्ट टैग बनाम डाइनैमिक तौर पर जोड़ी गई स्क्रिप्ट के बीच, हमारे पास स्क्रिप्ट जोड़ने के छह पैटर्न हैं. हालांकि, ब्राउज़र इस बात पर सहमत नहीं थे कि उन्हें किस क्रम में निर्देशों को लागू करना चाहिए. Mozilla ने इस समस्या के बारे में एक बेहतरीन लेख लिखा है, जो 2009 में लिखा गया था.

WHATWG ने इस व्यवहार के बारे में साफ़ तौर पर बताया है. इसमें कहा गया है कि “defer” का उन स्क्रिप्ट पर कोई असर नहीं पड़ता जिन्हें डाइनैमिक तौर पर जोड़ा गया है या जिनमें “src” मौजूद नहीं है. अगर ऐसा नहीं होता है, तो दस्तावेज़ को पार्स करने के बाद, उन स्क्रिप्ट को उसी क्रम में चलाया जाना चाहिए जिस क्रम में उन्हें जोड़ा गया था.

धन्यवाद IE! (ठीक है, अब मैं व्यंग्य कर रहा हूं)

यह देता है, यह लेता है. माफ़ करें, IE4-9 में एक गड़बड़ी है, जिसकी वजह से स्क्रिप्ट अनचाहे क्रम में चल सकती हैं. ऐसा करने पर, यह होता है:

1.js

console.log('1');
document.getElementsByTagName('p')[0].innerHTML = 'Changing some content';
console.log('2');

2.js

console.log('3');

मान लें कि पेज पर एक पैराग्राफ है, तो लॉग का क्रम [1, 2, 3] होना चाहिए. हालांकि, IE9 और उससे पहले के वर्शन में आपको [1, 3, 2] दिखेगा. डीओएम के कुछ खास ऑपरेशन की वजह से, IE मौजूदा स्क्रिप्ट के चलने को रोक देता है. साथ ही, आगे बढ़ने से पहले, बाकी स्क्रिप्ट को चला देता है.

हालांकि, IE10 और अन्य ब्राउज़र जैसे बिना गड़बड़ी वाले वर्शन में भी, स्क्रिप्ट को तब तक लागू नहीं किया जा सकता, जब तक कि पूरा दस्तावेज़ डाउनलोड और पार्स नहीं हो जाता. अगर आपको DOMContentLoaded के रिलीज़ होने का इंतज़ार करना है, तो यह सुविधा आपके लिए काम की हो सकती है. हालांकि, अगर आपको परफ़ॉर्मेंस को बेहतर बनाना है, तो जल्द से जल्द लिसनर जोड़ें और बूटस्ट्रैप करें…

HTML5 की मदद से

<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

HTML5 ने हमें एक नया एट्रिब्यूट, “async” दिया है. इससे यह माना जाता है कि आप document.write का इस्तेमाल नहीं करेंगे. हालांकि, यह तब तक इंतज़ार नहीं करता, जब तक दस्तावेज़ को पार्स करके उसे लागू नहीं कर दिया जाता. ब्राउज़र, दोनों स्क्रिप्ट को एक साथ डाउनलोड करेगा और उन्हें जल्द से जल्द लागू करेगा.

माफ़ करें, ये स्क्रिप्ट जल्द से जल्द लागू होंगी. इसलिए, हो सकता है कि “2.js” “1.js” से पहले लागू हो जाए. अगर ये स्क्रिप्ट अलग-अलग हैं, तो कोई समस्या नहीं है. हो सकता है कि “1.js” एक ट्रैकिंग स्क्रिप्ट हो, जिसका “2.js” से कोई लेना-देना न हो. हालांकि, अगर आपका “1.js” jQuery की सीडीएन कॉपी है और “2.js” उस पर निर्भर है, तो आपके पेज पर गड़बड़ियां दिखेंगी. जैसे, किसी क्लस्टर-बम में… मुझे नहीं पता… मेरे पास इसके लिए कुछ नहीं है.

मुझे पता है कि हमें क्या चाहिए, एक JavaScript लाइब्रेरी!

सबसे बेहतर बात यह है कि रेंडरिंग को ब्लॉक किए बिना, स्क्रिप्ट का सेट तुरंत डाउनलोड हो जाए और जोड़े गए क्रम में, जल्द से जल्द लागू हो जाए. माफ़ करें, एचटीएमएल आपसे नफ़रत करता है और आपको ऐसा करने की अनुमति नहीं देगा.

इस समस्या को JavaScript ने कुछ तरीकों से हल किया. कुछ मामलों में, आपको अपने JavaScript में बदलाव करने पड़ते थे. इसके लिए, आपको उसे किसी ऐसे कॉलबैक में रैप करना होता था जिसे लाइब्रेरी सही क्रम में कॉल करती है. जैसे, RequireJS. कुछ लोग, एक साथ डाउनलोड करने के लिए XHR का इस्तेमाल करते थे. इसके बाद, सही क्रम में eval() का इस्तेमाल करते थे. यह तरीका, किसी दूसरे डोमेन की स्क्रिप्ट के लिए तब तक काम नहीं करता, जब तक उनमें सीओआरएस हेडर न हो और ब्राउज़र उससे काम न करता हो. कुछ लोगों ने LabJS जैसी सुपर-मैजिक हैक का भी इस्तेमाल किया.

इन हैक में, ब्राउज़र को धोखा देकर संसाधन को इस तरह से डाउनलोड किया जाता था कि वह पूरा होने पर इवेंट को ट्रिगर कर दे, लेकिन उसे लागू न करे. LabJS में, स्क्रिप्ट को गलत माइम टाइप के साथ जोड़ा जाएगा, जैसे कि <script type="script/cache" src="...">. सभी स्क्रिप्ट डाउनलोड होने के बाद, उन्हें सही टाइप के साथ फिर से जोड़ दिया जाएगा. ऐसा करने से, ब्राउज़र उन्हें सीधे कैश मेमोरी से पा सकेगा और उन्हें क्रम से तुरंत लागू कर सकेगा. यह सुविधा, सुविधाजनक थी, लेकिन इसके काम करने का तरीका साफ़ तौर पर नहीं बताया गया था. यह सुविधा तब काम नहीं करती, जब HTML5 ने ब्राउज़र को ऐसे टाइप की स्क्रिप्ट डाउनलोड करने से रोका था जिन्हें पहचाना नहीं जा सकता. ध्यान दें कि LabJS ने इन बदलावों को अपना लिया है और अब इस लेख में बताए गए तरीकों का इस्तेमाल करता है.

हालांकि, स्क्रिप्ट लोडर की परफ़ॉर्मेंस में भी समस्याएं आती हैं. लाइब्रेरी के JavaScript के डाउनलोड और पार्स होने का इंतज़ार करने के बाद ही, उसमें मैनेज की जा रही किसी भी स्क्रिप्ट को डाउनलोड किया जा सकता है. इसके अलावा, हम स्क्रिप्ट लोडर को कैसे लोड करेंगे? हम उस स्क्रिप्ट को कैसे लोड करेंगे जो स्क्रिप्ट लोडर को बताती है कि क्या लोड करना है? वॉचमैन को कौन देखता है? मुझे नग्न क्यों दिखाया गया है? ये सभी मुश्किल सवाल हैं.

अगर आपको अन्य स्क्रिप्ट डाउनलोड करने से पहले, कोई अतिरिक्त स्क्रिप्ट फ़ाइल डाउनलोड करनी पड़ती है, तो इसका मतलब है कि आपने परफ़ॉर्मेंस की लड़ाई पहले ही हार दी है.

डीओएम की मदद से

इसका जवाब, असल में HTML5 स्पेसिफ़िकेशन में है. हालांकि, यह स्क्रिप्ट-लोडिंग सेक्शन में सबसे नीचे छिपा हुआ है.

चलिए, इसका अनुवाद “Earthling” में करते हैं:

[
  '//other-domain.com/1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  document.head.appendChild(script);
});

डाइनैमिक तरीके से बनाई गई और दस्तावेज़ में जोड़ी गई स्क्रिप्ट, डिफ़ॉल्ट रूप से असाइन नहीं होतीं. ये रेंडरिंग को ब्लॉक नहीं करतीं और डाउनलोड होते ही लागू हो जाती हैं. इसका मतलब है कि ये गलत क्रम में दिख सकती हैं. हालांकि, हम उन्हें साफ़ तौर पर 'असाइनमेंट सिंक नहीं होने वाला' के तौर पर मार्क कर सकते हैं:

[
  '//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);
});

इससे हमारी स्क्रिप्ट को अलग-अलग तरह के काम करने की सुविधा मिलती है. यह सुविधा, सादे एचटीएमएल से नहीं मिल सकती. साफ़ तौर पर असाइन नहीं किए जाने पर, स्क्रिप्ट को एक क्रम में चलाने के लिए जोड़ा जाता है. यह वही क्रम है जिसमें हमारे पहले सादे एचटीएमएल उदाहरण में उन्हें जोड़ा गया है. हालांकि, डाइनैमिक तौर पर बनने की वजह से, इन्हें दस्तावेज़ को पार्स करने के बाहर चलाया जाता है. इसलिए, इन्हें डाउनलोड करने के दौरान रेंडरिंग ब्लॉक नहीं होती. सिंक XHR के साथ, नॉन-ऐसिंक स्क्रिप्ट लोडिंग को न जोड़ें. यह कभी भी अच्छी बात नहीं होती.

ऊपर दी गई स्क्रिप्ट को पेजों के हेडर में इनलाइन शामिल किया जाना चाहिए. इससे, प्रोग्रेसिव रेंडरिंग में रुकावट आए बिना, स्क्रिप्ट डाउनलोड की सूची में जितनी जल्दी हो सके उतनी जल्दी शामिल हो जाती है. साथ ही, आपके बताए गए क्रम में जितनी जल्दी हो सके उतनी जल्दी लागू हो जाती है. “1.js” से पहले “2.js” को मुफ़्त में डाउनलोड किया जा सकता है. हालांकि, इसे तब तक लागू नहीं किया जाएगा, जब तक “1.js” डाउनलोड और लागू नहीं हो जाता या दोनों में से कोई भी काम नहीं करता. वाह! एक साथ काम न करने वाले टास्क के तौर पर डाउनलोड किया जा रहा है, लेकिन क्रम से लागू किया जा रहा है!

इस तरह से स्क्रिप्ट लोड करने की सुविधा, ऐसे सभी ब्राउज़र पर काम करती है जिनमें async एट्रिब्यूट काम करता है. हालांकि, यह सुविधा Safari 5.0 पर काम नहीं करती. Safari 5.1 पर यह सुविधा काम करती है. इसके अलावा, Firefox और Opera के सभी वर्शन पर, ऐसे वर्शन काम करते हैं जो async एट्रिब्यूट के साथ काम नहीं करते. ये वर्शन, डाइनैमिक तौर पर जोड़ी गई स्क्रिप्ट को उसी क्रम में आसानी से लागू करते हैं जिस क्रम में उन्हें दस्तावेज़ में जोड़ा गया है.

क्या स्क्रिप्ट लोड करने का यह सबसे तेज़ तरीका है? ठीक है?

अगर डाइनैमिक तौर पर यह तय किया जा रहा है कि कौनसी स्क्रिप्ट लोड करनी हैं, तो हां. अगर ऐसा नहीं है, तो शायद नहीं. ऊपर दिए गए उदाहरण में, ब्राउज़र को यह पता लगाने के लिए स्क्रिप्ट को पार्स और लागू करना होगा कि कौनसी स्क्रिप्ट डाउनलोड करनी हैं. इससे, आपकी स्क्रिप्ट, पहले से लोड होने वाले स्कैनर से छिप जाती हैं. ब्राउज़र इन स्कैनर का इस्तेमाल करके, उन पेजों के रिसॉर्स ढूंढते हैं जिन पर आपके अगले विज़िट करने की संभावना है. इसके अलावा, जब किसी दूसरे रिसॉर्स की वजह से पार्सर ब्लॉक हो जाता है, तब भी ब्राउज़र पेज के रिसॉर्स ढूंढते हैं.

दस्तावेज़ के हेड में यह डालकर, खोजे जाने की सुविधा को फिर से जोड़ा जा सकता है:

<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">

इससे ब्राउज़र को पता चलता है कि पेज को 1.js और 2.js की ज़रूरत है. link[rel=subresource], link[rel=prefetch] से मिलता-जुलता है, लेकिन इसमें अलग सेमेटिक हैं. फ़िलहाल, यह सुविधा सिर्फ़ Chrome पर काम करती है. साथ ही, आपको यह बताना होगा कि कौनसी स्क्रिप्ट दो बार लोड होनी चाहिए. एक बार लिंक एलिमेंट के ज़रिए और दूसरी बार अपनी स्क्रिप्ट में.

सुधार: मैंने पहले बताया था कि इन्हें प्रीलोड स्कैनर ने पिक किया था. ऐसा नहीं है. इन्हें रेगुलर पार्सर ने पिक किया है. हालांकि, प्रीलोड स्कैनर इन स्क्रिप्ट को पिक अप कर सकता है, लेकिन फ़िलहाल ऐसा नहीं किया जा रहा है. वहीं, एक्सीक्यूटेबल कोड में शामिल स्क्रिप्ट को कभी भी प्रीलोड नहीं किया जा सकता. टिप्पणियों में मुझे सही जानकारी देने वाले Yoav Weiss का धन्यवाद.

मुझे यह लेख उदास करने वाला लगा

स्थिति बहुत खराब है और आपको परेशान होना चाहिए. स्क्रिप्ट को तेज़ी से और अलग-अलग क्रम में डाउनलोड करने का कोई ऐसा तरीका नहीं है जो दोहराए जाने वाले निर्देशों के बिना काम करे. HTTP2/SPDY की मदद से, अनुरोध के ओवरहेड को इस हद तक कम किया जा सकता है कि अलग-अलग कैश मेमोरी में सेव की जा सकने वाली कई छोटी फ़ाइलों में स्क्रिप्ट डिलीवर करना सबसे तेज़ तरीका हो सकता है. कल्पना करें:

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

हर बेहतर बनाने वाली स्क्रिप्ट, किसी खास पेज कॉम्पोनेंट से जुड़ी होती है. हालांकि, इसके लिए dependencies.js में यूटिलिटी फ़ंक्शन की ज़रूरत होती है. हमारा सुझाव है कि सभी स्क्रिप्ट को एसिंक्रोनस तौर पर डाउनलोड करें. इसके बाद, बेहतर बनाने वाली स्क्रिप्ट को जल्द से जल्द किसी भी क्रम में, लेकिन dependencies.js के बाद लागू करें. इसे प्रोग्रेसिव प्रोग्रेसिव एन्हैंसमेंट कहते हैं! माफ़ करें, ऐसा करने का कोई तरीका नहीं है. ऐसा तब तक नहीं किया जा सकता, जब तक dependencies.js की लोडिंग स्थिति को ट्रैक करने के लिए, स्क्रिप्ट में बदलाव न किया जाए. async=false से भी यह समस्या हल नहीं होती, क्योंकि enhancement-10.js को लागू करने पर, 1 से 9 तक के फ़ंक्शन ब्लॉक हो जाएंगे. असल में, सिर्फ़ एक ब्राउज़र है जो हैक के बिना ऐसा करने की सुविधा देता है…

IE को एक आइडिया आया है!

IE, स्क्रिप्ट को दूसरे ब्राउज़र से अलग तरीके से लोड करता है.

var script = document.createElement('script');
script.src = 'whatever.js';

IE, “whatever.js” को डाउनलोड करना शुरू कर देता है. हालांकि, अन्य ब्राउज़र तब तक डाउनलोड नहीं करते, जब तक दस्तावेज़ में स्क्रिप्ट नहीं जोड़ी जाती. IE में एक इवेंट, “readystatechange” और प्रॉपर्टी, “readystate” भी होती है. इससे हमें लोडिंग की प्रोग्रेस के बारे में पता चलता है. यह सुविधा वाकई बहुत काम की है, क्योंकि इससे हमें स्क्रिप्ट को लोड करने और उन्हें अलग-अलग तरीके से लागू करने का कंट्रोल मिलता है.

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';

दस्तावेज़ में स्क्रिप्ट जोड़ने का समय चुनकर, हम जटिल डिपेंडेंसी मॉडल बना सकते हैं. IE के 6 वर्शन से, इस मॉडल का इस्तेमाल किया जा सकता है. यह काफ़ी दिलचस्प है, लेकिन इसमें अब भी async=false की तरह ही, प्रीलोडर को खोजने से जुड़ी समस्या है.

बस! मुझे स्क्रिप्ट कैसे लोड करनी चाहिए?

ठीक है. अगर आपको स्क्रिप्ट को इस तरह से लोड करना है कि रेंडरिंग ब्लॉक न हो, बार-बार लोड न हो, और ब्राउज़र पर बेहतर तरीके से काम करे, तो हमारा सुझाव है कि:

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

वह. बॉडी एलिमेंट के आखिर में. हां, वेब डेवलपर होना, किंग सिसिफ़स (बूम! यूनानी पौराणिक कथाओं के रेफ़रंस के लिए 100 हिपस्टर पॉइंट!). एचटीएमएल और ब्राउज़र की सीमाओं की वजह से, हम ज़्यादा बेहतर तरीके से काम नहीं कर पाते.

मुझे उम्मीद है कि JavaScript मॉड्यूल, स्क्रिप्ट लोड करने के लिए एक ऐसा तरीका उपलब्ध कराएंगे जिससे स्क्रिप्ट लोड होने में कोई रुकावट नहीं आएगी. साथ ही, स्क्रिप्ट को लागू करने के क्रम को कंट्रोल किया जा सकेगा. हालांकि, इसके लिए स्क्रिप्ट को मॉड्यूल के तौर पर लिखना ज़रूरी है.

काश, अब कुछ बेहतर इस्तेमाल किया जा सकता हो?

अगर आपको परफ़ॉर्मेंस को बेहतर बनाने के लिए, ज़्यादा मेहनत करनी है और आपको थोड़ी जटिलता और दोहराव से कोई परेशानी नहीं है, तो ऊपर बताई गई कुछ तरकीबों को आज़माएं.

सबसे पहले, हम प्रीलोडर के लिए सब-रिसॉर्स का एलान जोड़ते हैं:

<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">

इसके बाद, हम दस्तावेज़ के हेड में इनलाइन, async=false का इस्तेमाल करके JavaScript के साथ अपनी स्क्रिप्ट लोड करते हैं. इसके बाद, IE की readystate पर आधारित स्क्रिप्ट लोड करने की सुविधा का इस्तेमाल किया जाता है. इसके बाद, स्क्रिप्ट लोड होने में लगने वाले समय को बढ़ाया जाता है.

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>');
  }
}

कुछ ट्रिक और छोटा करने के बाद, यह 362 बाइट + आपकी स्क्रिप्ट के यूआरएल हो जाता है:

!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"
])

क्या यह किसी सामान्य स्क्रिप्ट को शामिल करने की तुलना में, ज़्यादा बाइट के लायक है? अगर बीबीसी की तरह, शर्तों के हिसाब से स्क्रिप्ट लोड करने के लिए, पहले से ही JavaScript का इस्तेमाल किया जा रहा है, तो उन डाउनलोड को पहले ट्रिगर करने से भी आपको फ़ायदा मिल सकता है. अगर ऐसा नहीं है, तो हो सकता है कि आपके लिए यह तरीका काम न करे. ऐसे में, बॉडी के आखिर में एट्रिब्यूट जोड़ने का आसान तरीका अपनाएं.

अब मुझे पता है कि WHATWG स्क्रिप्ट लोडिंग सेक्शन इतना बड़ा क्यों है. मुझे एक ड्रिंक चाहिए.

एक नज़र में जानकारी

सामान्य स्क्रिप्ट एलिमेंट

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

स्पेसिफ़िकेशन के मुताबिक: सभी को एक साथ डाउनलोड करें. किसी भी सीएसएस के इंतज़ार में होने पर, उसे क्रम से चलाएं. साथ ही, पूरा होने तक रेंडरिंग को ब्लॉक करें. ब्राउज़र का जवाब: हां सर!

टालना

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

स्पेसिफ़िकेशन के मुताबिक: एक साथ डाउनलोड करें और DOMContentLoaded से ठीक पहले क्रम में लागू करें. “src” के बिना स्क्रिप्ट पर “defer” को अनदेखा करें. IE < 10 का कहना है: 1.js के आधे हिस्से को लागू करने के बाद, 2.js को लागू किया जा सकता है. क्या यह मज़ेदार नहीं है? लाल रंग में दिखाए गए ब्राउज़र के हिसाब से: मुझे नहीं पता कि “देर से लोड होने की सुविधा” क्या है. मैं स्क्रिप्ट को ऐसे लोड करूंगा जैसे कि यह सुविधा मौजूद ही न हो. अन्य ब्राउज़र कहते हैं: ठीक है, लेकिन हो सकता है कि मैं “src” के बिना स्क्रिप्ट पर “defer” को अनदेखा न करूं.

Async

<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

स्पेसिफ़िकेशन के मुताबिक: एक साथ डाउनलोड करें और जिस क्रम में डाउनलोड किए गए हैं उसी क्रम में चलाएं. लाल रंग में दिख रहे ब्राउज़र के मुताबिक: ‘async’ क्या है? मैं स्क्रिप्ट को ऐसे लोड करूंगा जैसे कि यह मौजूद ही नहीं है. अन्य ब्राउज़र के लिए: हां, ठीक है.

Async false

[
  '1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

स्पेसिफ़िकेशन के मुताबिक: सभी को एक साथ डाउनलोड करें और डाउनलोड होने के बाद, क्रम से चलाएं. Firefox < 3.6, Opera का कहना है: मुझे नहीं पता कि यह “async” क्या है, लेकिन ऐसा होता है कि JS के ज़रिए जोड़ी गई स्क्रिप्ट, जोड़े जाने के क्रम में ही चलती हैं. Safari 5.0 का कहना है: मुझे “async” के बारे में पता है, लेकिन JS की मदद से इसे “false” पर सेट करने के बारे में नहीं पता. आपकी स्क्रिप्ट मिलने के बाद, हम उन्हें किसी भी क्रम में लागू कर देंगे. IE 10 से पहले के वर्शन के लिए: “async” के बारे में कोई जानकारी नहीं है. हालांकि, “onreadystatechange” का इस्तेमाल करके, इस समस्या को हल किया जा सकता है. दूसरे लाल रंग में दिखाए गए ब्राउज़र के लिए: मुझे “async” के बारे में कोई जानकारी नहीं है. आपकी स्क्रिप्ट आने के तुरंत बाद, उन्हें किसी भी क्रम में लागू कर दिया जाएगा. अन्य सभी चीज़ें बताती हैं: मैं आपका दोस्त हूं, हम इसे नियमों के मुताबिक करेंगे.