読み蟌みスクリプトの暗い海に浞る

画像
ほんの数時間前、ペヌゞぞのスクリプトのロヌドに関する珟状に぀いおのすばらしい蚘事がHTML5 Rocksに掲茉されたした。 その翻蚳をお芋せしたす。 修正はプラむベヌトメッセヌゞに送信できたす。

はじめに


この蚘事では、JavaScriptをブラりザヌにロヌドしお実行する方法を説明したす。

埅っお、戻っおきお 私はこれが普通で単玔に聞こえるこずを知っおいたすが、これはブラりザで起こるこずを芚えおおいおください。 これらの癖を知るこずは、スクリプトをロヌドするための最速で砎壊的な方法を遞択するのに圹立ちたす。 お急ぎの堎合は、蚘事の最埌にあるクむックリファレンスガむドに盎接進んでください。

たず第䞀に、これは、 仕様がスクリプトをロヌドおよび実行するさたざたな方法を定矩する方法です。

スクリプロトのロヌドに関するWHATWGの蚘述

すべおのWHATWG仕様ず同様に、この仕様は䞀芋、スクラブルファクトリヌのクラスタヌ爆匟の圱響のように芋えたす。 しかし、それを5回読んで、目から血を拭いた埌、あなたはそれを非垞に面癜いず感じ始めたす

私の最初のスクリプト接続


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

ああ、至犏のシンプルさ。 この堎合、ブラりザヌは䞡方のスクリプトを䞊行しおダりンロヌドし、指定された順序を保持しお、できるだけ早くそれらを実行したす。 「2.js」は「1.js」が実行されるたで実行されたせんたたは実行できたせん。「1.js」は前のスクリプトたたはスタむルが実行されるたで実行されたせん。 など

残念ながら、ブラりザはこれ以䞊のペヌゞレンダリングをブロックしたすが、これはすべお起こりたす。 「Webの1䞖玀」以来、これはDOM APIによるものであり、パヌサヌが䜿甚するコンテンツに、たずえばdocument.write䜿甚しお、文字列を远加できたす。 最新のブラりザは匕き続きドキュメントをバックグラりンドでスキャンおよび解析し、必芁なサヌドパヌティコンテンツjs、写真、cssなどを読み蟌みたすが、レンダリングは匕き続きブロックされたす。

これが、教祖ずパフォヌマンスの専門家がスクリプト芁玠をドキュメントの最埌に配眮するこずを掚奚しおいる理由です。 残念ながら、これは、すべおのHTMLがダりンロヌドされ、CSS、画像、およびiframeが既に読み蟌たれるたで、スクリプトがブラりザに衚瀺されないこずを意味したす。 最新のブラりザは、芖芚的な郚分よりもJavaScriptを優先するのに十分賢いですが、もっずうたくやるこずができたす。

IEに感謝したす いいえ、私は皮肉がありたせん


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

Microsoftはこれらのパフォヌマンスの問題を発芋し、Internet Explorer 4に「遅延」を導入したした。基本的に、次のように述べおいたす。 この玄束を砎ったら、あなたに合った方法で私を眰するこずができたす。」 この属性はHTML4で導入され 、他のブラりザヌでも登堎したした。

䞊蚘の䟋では、ブラりザヌはDOMContentLoadedがDOMContentLoaded盎前に䞡方のスクリプトを同時にダりンロヌドしお実行し、順序が保持されたす。

矊の工堎のクラスタヌ爆匟のように、延期は毛むくじゃらの混乱になりたした。 「src」ず「defer」に加えお、スクリプトタグず動的にロヌドされるスクリプトに加えお、スクリプトを远加するための6぀のパタヌンがありたす。 圓然、ブラりザは実行する順序に同意したせんでした。 Mozilla は、この問題に぀いお 2009幎に玠晎らしい説明をしたした 。

WHATWGは、deferが動的に远加されたスクリプトやsrcを持たないスクリプトに圱響を䞎えないず宣蚀するこずにより、この動䜜を明瀺的にしたした。 それ以倖の堎合、ドキュメントを解析した埌、「defer」を含むスクリプトを指定された順序で実行する必芁がありたす。

IEに感謝したす 倧䞈倫、今皮肉ず

1぀は䞎えた-別のものが撮圱されたした。 残念ながら、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]になりたす。 䞀郚のDOM操䜜では、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」が「2.js」に䟝存するjQueryのCDNコピヌである堎合、クラスタヌ爆匟のような...あなたのペヌゞぱラヌで芆われたす...わかりたせん...ここでは䜕も思い぀きたせんでした。

JavaScriptラむブラリが必芁なこずは知っおいたす


Holy Grailには、ペヌゞのレンダリングをブロックせずにすぐにロヌドし、远加した順にできるだけ早く実行する䞀連のスクリプトが含たれおいたす。 残念ながら、HTMLはあなたを嫌っおおり、これを蚱可したせん。

この問題は、JavaScriptをさたざたな方法で䜿甚しお解決されたした。 䞀郚のメ゜ッドでは、すべおをコヌルバックでラップするためにJavaScriptを倉曎する必芁がありたした。コヌルバックは、ラむブラリが正しい順序で呌び出したすたずえば、 RequireJS 。 他のナヌザヌは、䞊列読み蟌みにXHRを䜿甚し、次に正しい順序でeval()を䜿甚したした。これは、ブラりザヌにCORSヘッダヌずサポヌトがない限り、別のドメむンのスクリプトでは機胜したせん。 最新のLabJSで行われたように、スヌパヌマゞックハックを䜿甚したものもありたした。

ハッキングはあらゆる方法でブラりザをtrickしおリ゜ヌスをロヌドさせ、ダりンロヌドの最埌にむベントを発生させたしたが、実行を開始したせんでした。 LabJSでは、スクリプトは最初に誀ったMIMEタむプで远加されたした。たずえば
 .      ,   ,     mime-,    ,             .     ,   ,  ,  HTML5 ,         . 

, , JavaScript- , . , ? , ? ? ? .

DOM !
, HTML5, .
The async IDL attribute controls whether the element will execute asynchronously or not. If the element's "force-async" flag is set, then, on getting, the async IDL attribute must return true, and on setting, the "force-async" flag must first be unset

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

. , , mime-, , . , , , HTML5 , .

, , JavaScript- , . , ? , ? ? ? .

DOM !
, HTML5, .
The async IDL attribute controls whether the element will execute asynchronously or not. If the element's "force-async" flag is set, then, on getting, the async IDL attribute must return true, and on setting, the "force-async" flag must first be unset

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

. , , mime-, , . , , , HTML5 , .

, , JavaScript- , . , ? , ? ? ? .

DOM !
, HTML5, .
The async IDL attribute controls whether the element will execute asynchronously or not. If the element's "force-async" flag is set, then, on getting, the async IDL attribute must return true, and on setting, the "force-async" flag must first be unset

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

, , JavaScript- , . , ? , ? ? ? .

DOM !
, HTML5, .
The async IDL attribute controls whether the element will execute asynchronously or not. If the element's "force-async" flag is set, then, on getting, the async IDL attribute must return true, and on setting, the "force-async" flag must first be unset

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

, , JavaScript- , . , ? , ? ? ? .

DOM !
, HTML5, .
The async IDL attribute controls whether the element will execute asynchronously or not. If the element's "force-async" flag is set, then, on getting, the async IDL attribute must return true, and on setting, the "force-async" flag must first be unset

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

これにより、スクリプトに、玔粋なHTMLでは実珟できない動䜜ずの組み合わせが提䟛されたす。 非同期のスクリプトによっお明瀺的に蚭定されたスクリプトは、玔粋なHTMLの最初の䟋ず同じように、実行キュヌに远加されたす。 ただし、動的に䜜成されるず、ドキュメントの解析倖で実行され、読み蟌み䞭にレンダリングをブロックしたせん非同期スクリプトの読み蟌みず同期XHRを混同しないでください。

䞊蚘のスクリプトはペヌゞの先頭に埋め蟌たれ、段階的なレンダリングを䞭断するこずなくダりンロヌドキュヌをできるだけ早く開始し、指定した順序でできるだけ早く実行を開始する必芁がありたす。 「2.js」は「1.js」に自由にダりンロヌドできたすが、「1.js」が正垞にダりンロヌドおよび実行されるか、これを実行できないたで実行されたせん。 やった 非同期読み蟌みが実行されたす

このメ゜ッドを䜿甚したスクリプトの読み蟌みは、async属性がサポヌトされおいるすべおの堎所でサポヌトされたすが、 Safari 5.05.1ではすべお問題ありたせんは䟋倖です。 さらに、async属性をサポヌトしないFirefoxおよびOperaのすべおのバヌゞョンは、動的に远加されたスクリプトを正しい順序で実行したす。

これは、スクリプトをロヌドする最速の方法ですよね だから


ロヌドするスクリプトを動的に決定する堎合-はい、そうでない堎合-いいえ可胜です。 䞊蚘の䟋では、ブラりザはスクリプトを解析しおロヌドし、ロヌドするスクリプトを決定する必芁がありたす。 これにより、スクリプトがプリロヌドスキャナヌから隠されたす。 ブラりザはこれらのスキャナヌを䜿甚しお、次にアクセスする可胜性が高いリ゜ヌスを怜出し、パヌサヌが別のリ゜ヌスによっおブロックされおいる間にペヌゞリ゜ヌスを芋぀けたす。

これをドキュメントの先頭に配眮するこずにより、怜出可胜性を远加できたす。
 <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でのみサポヌトされおおり、スクリプトを2回ダりンロヌドするように宣蚀する必芁がありたす。1぀目はリンク芁玠に、2぀目はスクリプトにありたす。

この蚘事は私を萜ち蟌たせたす


状況は憂鬱であり、憂鬱に感じるはずです。 スクリプトを迅速か぀非同期にロヌドし、同時に実行順序を制埡する繰り返しのない宣蚀的な方法はただありたせん。

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の読み蟌みステヌタスを監芖する堎合にのみ、これを達成するための宣蚀的な方法はありたせん。 Enhancement-10.jsの実装は1-9でブロックされるため、async = falseでもこの問題は解決したせん。 実際、ハッキングせずにこれを達成できるブラりザは1぀だけです...

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ず同じ欠陥がありasync=false 。

十分だ スクリプトをダりンロヌドするにはどうすればよいですか


わかった、わかった。 レンダリングをブロックせず、耇補を必芁ずせず、優れたブラりザサポヌトを備えた方法でスクリプトをロヌドする堎合は、これをお勧めしたす。
 <script src="//other-domain.com/1.js"></script> <script src="2.js"></script> 

これです。 body芁玠の最埌。 はい、りェブ開発者であるずいうこずは、シシフスの王のようなものですギリシャ神話に蚀及するための100の流行に敏感なポむント。 HTMLずブラりザの制限により、これ以䞊の改善はできたせん。

JavaScriptモゞュヌルは、スクリプトをモゞュヌル圢匏で蚘述する必芁がある堎合でも、スクリプトをロヌドし、実行順序を制埡するための宣蚀的なノンブロッキングな方法を提䟛するこずで私たちを救うこずを願っおいたす。

矩wu、私たちが今䜿えるものがもっずあるに違いない


ボヌナスポむントのために、パフォヌマンスに぀いお真剣に考え、耇雑さや重耇を恐れないのであれば、いく぀かの考慮されたトリックを組み合わせるこずができたす。

最初に、プリロヌダヌのサブリ゜ヌス宣蚀を远加したす。
 <link rel="subresource" href="//other-domain.com/1.js"> <link rel="subresource" href="2.js"> 

次に、ドキュメントの冒頭で、 async=falseを䜿甚しおJavaScriptを䜿甚しおスクリプトを読み蟌み、readystateに基づいおIEのスクリプトに眮き換えたす。
 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バむト+スクリプトのURLです
 !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" ]) 

スクリプトを接続するだけの堎合ず比べお、䜙分なバむトが必芁ですか BBCが行うように、JavaScriptを䜿甚しお条件付きでスクリプトを既にロヌドしおいる堎合は 、これらのダりンロヌドを早期に実行するこずも圹立ちたす。 それ以倖の堎合、おそらくそうではありたせんが、本䜓の端で接続するずいう単玔な方法に固執したす。

これで、WHATWGスクリプトの読み蟌みセクションが非垞に倧きい理由がわかりたした。 飲み物が必芁です。

クむックリファレンス


単玔なスクリプト芁玠

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

仕様には 、䞀緒にダりンロヌドし、埅機しおいるCSSの埌に順番に実行し、完了するたでレンダリングをブロックする
ブラりザヌからの返信はい。

延期する

 <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」を無芖しない可胜性がありたす

非同期

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

仕様には 、「䞀緒にダりンロヌドし、ダりンロヌドした順序で実行する」ずありたす。
レッドゟヌンブラりザヌ回答 「非同期」ずは䜕ですか スクリプトをダりンロヌドしなかったかのようにダりンロヌドしたす。
残りのブラりザヌは応答したすはい、いいです。

非同期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からの返信 「非同期」ずは䜕なのかわかりたせんが、JSを介しお远加されたスクリプトを远加さ​​れた順に実行するこずがありたした。
Safari 5.0の返信 「非同期」は理解しおいたすが、JSでfalseに蚭定する方法がわかりたせん。 スクリプトが到着したら、任意の順序でスクリプトを実行したす。
IE <10の返信 「非同期」に぀いおはわかりたせんが、 「onreadystatechange」を䜿甚する回避策がありたす。
他のレッドゟヌンブラりザヌが応答したす。 「非同期」がわかりたせん。スクリプトが到着したずきに、任意の順序で実行したす。
他の回答私はあなたの友達です、私たちは教科曞のようにそれをしたす。

Source: https://habr.com/ru/post/J182310/


All Articles