ブラウザ環境の概要

JavaScript はブラウザに組み込まれたスクリプト言語です。つまり、ブラウザには JavaScript エンジンが組み込まれており、JavaScript スクリプトでブラウザのさまざまな機能を制御できるようにさまざまなインターフェイスが提供されています。 Web ページに JavaScript スクリプトを埋め込むと、ブラウザが Web ページを読み込むときにスクリプトが実行され、ブラウザの動作目的を達成し、Web ページ上でさまざまな動的効果を実現します。

この章は、ブラウザによって提供されるさまざまな JavaScript インターフェイスを紹介することから始まります。まずはWebページにJavaScriptコードを埋め込む方法を紹介します。

Web ページにコードを埋め込む方法

Web ページに JavaScript コードを埋め込むには、主に 4 つの方法があります。

  • <script> 要素にはコードが直接埋め込まれます。
  • <script> タグは外部スクリプトをロードします
  • イベントのプロパティ
  • URLプロトコル

スクリプト要素の埋め込みコード

JavaScript コードは <script> 要素内に直接記述することができます。

<スクリプト>
  var x = 1 + 5;
  コンソール.ログ(x);
</script>

<script> タグには、スクリプトのタイプを指定するために使用される type 属性があります。 JavaScript スクリプトの場合、「type」属性は 2 つの値に設定できます。

  • text/javascript: これはデフォルト値であり、これまでに設定されてきた値です。 「type」属性を省略した場合、この値がデフォルトで使用されます。古いブラウザの場合は、この値を設定することをお勧めします。
  • application/javascript: 新しいブラウザの場合は、この値を設定することをお勧めします。
<script type="アプリケーション/javascript">
  console.log('Hello World');
</script>

<script> タグはデフォルトで JavaScript コードであるためです。したがって、JavaScript スクリプトを埋め込む場合は、type 属性を省略できます。

「type」属性の値がブラウザによって認識されない場合、その中のコードは実行されません。これを利用して、ブラウザが認識しない type 属性を追加するだけで、<script> タグに任意のテキスト コンテンツを埋め込むことができます。

<script id="mydata" type="x-custom-data">
  console.log('Hello World');
</script>

ブラウザは、その type 属性を認識しないため、上記のコードを実行せず、その内容も表示しません。ただし、この <script> ノードは DOM 内にまだ存在しており、その内容は <script> ノードの text 属性を使用して読み取ることができます。

document.getElementById('mydata').text
// console.log('Hello World');

script 要素は外部スクリプトをロードします

<script> タグでは、外部スクリプト ファイルの読み込みを指定することもできます。

<script src="https://www.example.com/script.js"></script>

スクリプト ファイルで英語以外の文字が使用されている場合は、文字エンコーディングにも注意する必要があります。

<script charset="utf-8" src="https://www.example.com/script.js"></script>

ロードされるスクリプトは、「HTML」コードや「」タグを含まない、純粋な JavaScript コードである必要があります。

外部スクリプトのロードとコード ブロックの直接追加を混在させることはできません。次のコードの console.log ステートメントは直接無視されます。

<script charset="utf-8" src="example.js">
  console.log('Hello World!');
</script>

攻撃者による外部スクリプトの改ざんを防ぐために、script タグを使用すると、integrity 属性を設定し、外部スクリプトのハッシュ署名を記述してスクリプトの一貫性を検証できます。

<script src="/assets/application.js"
  完全性 = "sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs=">
</script>

上記のコードでは、script タグに integrity 属性があり、外部スクリプト /assets/application.js の SHA256 署名を指定します。誰かがこのスクリプトを変更して SHA256 署名が一致しないようにすると、ブラウザはスクリプトのロードを拒否します。

イベントのプロパティ

Web ページ要素のイベント属性 (「onclick」 や「onmouseover」 など) は JavaScript コードで記述することができます。これらのコードは、指定されたイベントが発生したときに呼び出されます。

<button id="myBtn" onclick="console.log(this.id)">クリック</button>

上記のイベント属性コードにはステートメントが 1 つだけあります。複数のステートメントがある場合は、セミコロンを使用してそれらを区切ります。

URLプロトコル

URL は「javascript:」プロトコルをサポートしています。これは、URL の場所にコードを記述することを意味し、この URL が使用されると JavaScript コードが実行されます。

<a href="javascript:console.log('Hello')">クリック</a>

ブラウザのアドレス バーでも「javascript:」プロトコルを実行できます。アドレスバーに「javascript:console.log('Hello')」と入力し、Enter キーを押してこのコードを実行します。

JavaScript コードが文字列を返す場合、ブラウザは文字列の内容を表示する新しいドキュメントを作成し、元のドキュメントの内容は表示されなくなります。

<a href="javascript: new Date().toLocaleTimeString();">クリック</a>

上記のコードでは、ユーザーがリンクをクリックすると、現在の時間が表示された新しいドキュメントが開きます。

戻り値が文字列でない場合、ブラウザは新しいドキュメントを作成せず、ジャンプも行いません。

<a href="javascript: console.log(new Date().toLocaleTimeString())">クリック</a>

上記のコードでは、ユーザーがリンクをクリックした後、Web ページはジャンプせず、現在の時刻のみがコンソールに表示されます。

「javascript:」プロトコルの一般的な用途はブックマークレットです。ブラウザのブックマークには URL が保存されるため、「javascript:」 URL も保存できます。ユーザーがこのブックマークを選択すると、現在のページでスクリプトが実行されます。ブックマークが現在のドキュメントを置き換えないようにするには、スクリプトの前に void を追加するか、スクリプトの最後に void 0 を追加します。

<a href="javascript: void new Date().toLocaleTimeString();">クリック</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">クリック</a>

上記 2 つの書き方では、リンクをクリックした後、コードが実行されて Web ページにジャンプしません。

スクリプト要素

動作原理

ブラウザは、主に <script> 要素を通じて JavaScript スクリプトを読み込みます。通常の Web ページの読み込みプロセスは次のようになります。

  1. ブラウザは HTML Web ページをダウンロードし、解析を開始します。つまり、ダウンロードが完了するまで待たずに解析を開始します。
  2. 解析プロセス中にブラウザが <script> 要素を見つけると、解析を一時停止し、Web ページのレンダリングの制御を JavaScript エンジンに移します。
  3. <script> 要素が外部スクリプトを参照している場合は、スクリプトをダウンロードして実行します。それ以外の場合は、コードが直接実行されます。
  4. JavaScript エンジンの実行が完了すると、制御がレンダリング エンジンに戻り、HTML Web ページの解析が再開されます。

外部スクリプトを読み込む場合、ブラウザはページのレンダリングを一時停止し、スクリプトがダウンロードされて実行されるのを待ってからレンダリングを続行します。その理由は、JavaScript コードが DOM を変更する可能性があるため、DOM に制御を与える必要があり、そうしないと複雑なスレッド競合の問題が発生します。

外部スクリプトの読み込みに時間がかかる(ダウンロードが完了できない)場合、ブラウザはスクリプトのダウンロードを待ち続けることになり、Web ページが長時間応答しなくなり、ブラウザが「一時停止」状態になります。これを「ブロッキング効果」といいます。

この状況を回避するには、<script> タグをページの先頭ではなく下部に配置することをお勧めします。このようにして、たとえスクリプトが応答を失ったとしても、Web ページの本体のレンダリングは完了しており、ユーザーは空白のページに直面するのではなく、少なくともコンテンツを見ることができます。スクリプト コードが非常に重要であり、ページの先頭に配置する必要がある場合は、外部スクリプト ファイルに接続するのではなく、コードをページに直接書き込むのが最善です。これにより、読み込み時間を短縮できます。

スクリプト ファイルは Web ページの最後に読み込まれるので、これにはもう 1 つの利点があります。 DOM ノードは DOM 構造が生成される前に呼び出されるため、スクリプトが Web ページの最後に読み込まれる場合、その時点までに DOM が生成されているはずなので、この問題は発生しません。

<>
  <スクリプト>
    console.log(document.body.innerHTML);
  </script>
</head>
<本文>
</body>

この時点では document.body 要素がまだ生成されていないため、上記のコードを実行するとエラーが報告されます。

解決策の 1 つは、「DOMContentLoaded」イベントのコールバック関数を設定することです。

<>
  <スクリプト>
    document.addEventListener(
      'DOMContentLoaded',
      関数 (イベント) {
        console.log(document.body.innerHTML);
      }
    );
  </script>
</head>

上記のコードでは、指定された DOMContentLoaded イベントが発生した後にのみ、関連するコードの実行が開始されます。 DOMContentLoaded イベントは、DOM 構造が生成された後にのみトリガーされます。

別の解決策は、<script> タグの onload 属性を使用することです。 <script> タグで指定された外部スクリプト ファイルがダウンロードされて解析されると、load イベントがトリガーされ、実行されるコードはこのイベントのコールバック関数に配置できます。

<script src="jquery.min.js" onload="console.log(document.body.innerHTML)">
</script>

ただし、スクリプトをページの下部に配置する場合は、完全に通常の方法で記述でき、上記のいずれの方法も必要ありません。

<本文>
  <!-- その他のコード -->
  <スクリプト>
    console.log(document.body.innerHTML);
  </script>
</body>

以下のように「script」タグが複数ある場合。

<script src="a.js"></script>
<script src="b.js"></script>

ブラウザは a.jsb.js を同時にダウンロードしますが、実行時には、たとえ後者が最初にダウンロードされます。つまり、スクリプトの実行順序は、スクリプト間の依存関係が壊れないようにするため、ページ上に表示される順序によって決まります。もちろん、これら 2 つのスクリプトを読み込むと「ブロック効果」が発生し、両方が読み込まれるまでブラウザはページのレンダリングを続行しません。

CSS を解析して実行するとブロックも発生します。 Firefox ブラウザは、スクリプトの前にあるすべてのスタイル シートがダウンロードされ、解析されるまで待機してからスクリプトを実行します。Webkit は、スクリプトがスタイルを参照していることを検出すると、スクリプトの実行を一時停止し、スタイル シートがダウンロードされるまで待機します。解析されてから実行を再開します。

さらに、ブラウザには通常、スクリプト ファイル、スタイル シート ファイル、画像ファイルなど、同じドメイン名のリソースに制限があり、同時に最大 6 ~ 20 のリソースをダウンロードできます。同時に開くことができる TCP 接続の最大数を制限します。これは、サーバーに過度の負荷がかかるのを防ぐためです。リソースが異なるドメイン名に由来する場合、そのような制限はありません。したがって、ダウンロードを高速化するために、静的ファイルは通常、異なるドメイン名の下に配置されます。

遅延属性

スクリプト ファイルのダウンロードが Web ページのレンダリングをブロックするという問題を解決するための 1 つの方法は、defer 属性を <script> 要素に追加することです。その機能は、DOM がロードされて生成されるまでスクリプトの実行を遅らせてから、スクリプトを実行することです。

<script src="a.js" 遅延></script>
<script src="b.js" 遅延></script>

上記のコードでは、DOM がロードされるまで a.jsb.js は実行されません。

defer属性の操作手順は以下の通りです。

  1. ブラウザは HTML Web ページの解析を開始します。
  2. 解析プロセス中に、defer 属性を持つ <script> 要素が見つかりました。
  3. ブラウザは HTML Web ページの解析を続行し、<script> 要素によってロードされた外部スクリプトを並行してダウンロードします。
  4. ブラウザは HTML Web ページの解析を完了し、ダウンロードしたスクリプトの実行に戻ります。

「defer」属性を使用すると、ブラウザはスクリプト ファイルのダウンロード時にページのレンダリングをブロックしません。ダウンロードされたスクリプト ファイルは、DOMContentLoaded イベントがトリガーされる前 (つまり、</html> タグが読み取られた直後) に実行され、実行順序はページ上に表示される順序であることが保証されます。 。

defer 属性は、外部スクリプトをロードするのではなく組み込みの script タグ、および動的に生成される script タグには影響しません。さらに、defer を使用してロードされた外部スクリプトは、document.write メソッドを使用しないでください。

非同期属性

「ブロッキング効果」を解決するもう 1 つの方法は、async 属性を <script> 要素に追加することです。

<script src="a.js" 非同期></script>
<script src="b.js" 非同期></script>

「async」属性の目的は、ダウンロード中にレンダリングをブロックせずに、別のプロセスを使用してスクリプトをダウンロードすることです。

  1. ブラウザは HTML Web ページの解析を開始します。
  2. 解析プロセス中に、「async」属性を持つ「script」タグが見つかりました。
  3. ブラウザは HTML Web ページの解析を続行し、並行して <script> タグ内の外部スクリプトをダウンロードします。
  4. スクリプトのダウンロードが完了すると、ブラウザは HTML Web ページの解析を停止し、ダウンロードされたスクリプトの実行を開始します。
  5. スクリプトの実行後、ブラウザは HTML Web ページの解析を再開します。

「async」属性は、スクリプトのダウンロード中にブラウザがレンダリングを継続することを保証します。この属性を使用すると、スクリプトの実行順序は保証されないことに注意してください。最初にダウンロードされたスクリプトが最初に実行されます。さらに、「async」属性を使用するスクリプト ファイル内のコードでは、「document.write」メソッドを使用しないでください。

「defer」属性と「async」属性のうちどちらを使用する必要がありますか?

一般に、スクリプト間に依存関係がない場合は「async」属性を使用し、スクリプト間に依存関係がある場合は「defer」属性を使用します。 async 属性と defer 属性を一緒に使用した場合、後者は効果がなく、ブラウザーの動作は async 属性によって決まります。

スクリプトの動的ロード

<script> 要素を動的に生成し、生成後にページに挿入することもできるため、スクリプトの動的な読み込みを実現できます。

['a.js', 'b.js'].forEach(function(src) {
  var script = document.createElement('script');
  スクリプト.src = ソース;
  document.head.appendChild(スクリプト);
});

この方法の利点は、動的に生成された「script」タグがページのレンダリングをブロックせず、ブラウザがフリーズしないことです。ただし問題は、この方法では、最初にダウンロードされたスクリプト ファイルが最初に実行されることを保証できないことです。

この問題を回避したい場合は、async 属性を「false」に設定します。

['a.js', 'b.js'].forEach(function(src) {
  var script = document.createElement('script');
  スクリプト.src = ソース;
  script.async = false;
  document.head.appendChild(スクリプト);
});

上記のコードはページのレンダリングをブロックせず、確実に「a.js」の後に「b.js」が実行されるようにできます。ただし、このコードの後に​​ロードされたスクリプト ファイルは、b.js が完了するまで待ってから実行されることに注意してください。

動的に読み込まれるスクリプトに対してコールバック関数を指定したい場合は、以下の記述方法が利用できます。

関数loadScript(src、完了) {
  var js = document.createElement('script');
  js.src = src;
  js.onload = function() {
    終わり();
  };
  js.onerror = function() {
    Done(new Error('スクリプトのロードに失敗しました ' + src));
  };
  document.head.appendChild(js);
}

使用するプロトコルをロードします

プロトコルが指定されていない場合、ブラウザはデフォルトで HTTP プロトコルを使用してダウンロードします。

<script src="example.js"></script>

上記の「example.js」はデフォルトで HTTP プロトコルを使用してダウンロードされます。HTTPS プロトコルを使用してダウンロードする場合は、それを指定する必要があります。

<script src="https://example.js"></script>

ただし、ページ自体のプロトコルに基づいて読み込みプロトコルを決定したい場合もあります。この場合は、次の記述方法を使用できます。

<script src="//example.js"></script>

ブラウザの構成

ブラウザの中核は、レンダリング エンジンと JavaScript インタプリタ (JavaScript エンジンとも呼ばれる) の 2 つの部分で構成されます。

レンダリング エンジン

レンダリング エンジンの主な機能は、Web ページのコードをユーザーが視覚的に認識できるフラット ドキュメントにレンダリングすることです。

ブラウザーが異なれば、レンダリング エンジンも異なります。

  • Firefox: Gecko エンジン
  • Safari: WebKit エンジン
  • Chrome: ブリンクエンジン
  • IE: トライデントエンジン
  • エッジ: EdgeHTML エンジン

レンダリング エンジンは、通常 4 つの段階で Web ページを処理します。

  1. コードの解析: HTML コードは DOM に解析され、CSS コードは CSSOM (CSS オブジェクト モデル) に解析されます。
  2. オブジェクト合成: DOM と CSSOM を結合してレンダー ツリーを作成します。
  3. レイアウト: レンダリング ツリーのレイアウトを計算します。
  4. 描画: レンダリング ツリーを画面に描画します。

上記の 4 つのステップは厳密に順番に実行されるわけではありません。多くの場合、最初のステップが完了する前に 2 番目と 3 番目のステップが開始されます。したがって、Web ページの HTML コードはダウンロードされていないが、ブラウザーにはコンテンツがすでに表示されているという状況が表示されます。

リフローと再描画

レンダリング ツリーから Web ページ レイアウトへの変換は「レイアウト フロー」と呼ばれ、レイアウトをページに表示するプロセスは「ペイント」と呼ばれます。これらはすべてブロック効果があり、多くの時間とコンピューティング リソースを消費します。

ページ生成後、スクリプト操作やスタイルシート操作により「リフロー」や「再ペイント」が行われます。マウス ホバー (「a:hover」) 効果の設定、ページのスクロール、入力ボックスへのテキストの入力、ウィンドウ サイズの変更などのユーザー インタラクションによっても、リフローと再描画がトリガーされます。

リフローと再描画は必ずしも同時に発生するとは限りません。リフローは必然的に再描画につながり、再描画は必ずしもリフローを必要とするわけではありません。たとえば、要素の色を変更すると再描画のみが発生しますが、要素のレイアウトを変更すると再描画とリフローが発生します。

ほとんどの場合、ブラウザーは賢明な判断を下して、リフローと再描画を関連するサブツリーのみに制限し、Web ページをグローバルに再生成することなくコストを最小限に抑えます。

開発者は、再描画の回数とコストを削減するように努めるべきです。たとえば、高レベルの DOM 要素は変更せず、基礎となる DOM 要素への変更に置き換えるようにしてください。別の例として、「table」レイアウトと「flex」レイアウトの再描画は比較的コストがかかります。

var foo = document.getElementById('foobar');

foo.style.color = '青';
foo.style.marginTop = '30px';

ブラウザは DOM の変更を蓄積し、それをすべて一度に実行するため、上記のコードでは 1 回の再描画のみが発生します。

ここでは最適化のヒントをいくつか紹介します。

  • DOM の読み取りまたは DOM の書き込みを行います。それらを混合しないで、一緒に書き込んでください。 DOM ノードを読み取ってすぐに書き込み、その後再度 DOM ノードを読み取ることはしないでください。
  • DOM 情報のキャッシュ。
  • スタイルを 1 つずつ変更するのではなく、CSS クラスを使用してスタイルを一度に変更します。
  • DOM を操作するには documentFragment を使用します
  • アニメーションは「絶対」配置または「固定」配置を使用します。これにより、他の要素への影響を軽減できます。
  • 必要な場合にのみ非表示の要素を表示します。
  • ページの即時再描画を要求するのではなく、次の再描画までコードの実行を延期する window.requestAnimationFrame() を使用します。
  • 仮想 DOM (仮想 DOM) ライブラリを使用します。

以下は「window.requestAnimationFrame()」の効果の比較例です。

// リフローコストが高い
関数 doubleHeight(要素) {
  var currentHeight = element.clientHeight;
  element.style.height = (currentHeight * 2) + 'px';
}

all_my_elements.forEach(doubleHeight);

// 再描画コストが低い
関数 doubleHeight(要素) {
  var currentHeight = element.clientHeight;

  window.requestAnimationFrame(function() {
    element.style.height = (currentHeight * 2) + 'px';
  });
}

all_my_elements.forEach(doubleHeight);

上記の最初のコードは、DOM が読み取られるたびに新しい値を書き込みます。これにより、継続的な再配置とリフローが発生します。 2 番目のコードでは、すべての書き込み操作がまとめて蓄積されるため、DOM コード変更のコストが最小限に抑えられます。

JavaScript エンジン

JavaScript エンジンの主な機能は、Web ページ内の JavaScript コードを読み取り、処理して実行することです。

JavaScript はインタープリター型言語です。つまり、コンパイルは必要なく、インタープリターによってリアルタイムで実行されます。この利点は、実行と変更がより便利であり、ページを更新することで再解釈できることです。欠点は、実行するたびにインタープリタを呼び出す必要があるため、システムのオーバーヘッドが大きく、実行速度が遅いことです。コンパイルされた言語。

現在のブラウザでは、動作速度を向上させるために、JavaScriptをある程度コンパイルし、バイトコードに似た中間コードを生成して動作速度を向上させています。

初期のブラウザの JavaScript 内部処理は次のとおりでした。

  1. コードを読み取り、字句分析を実行し、コードをトークンに分解します。
  2. 単語要素の文法分析 (解析) を実行し、コードを「構文ツリー」に編成します。
  3. 「トランスレータ」を使用してコードをバイトコードに変換します。
  4. 「バイトコード インタプリタ」を使用して、バイトコードをマシンコードに変換します。

バイトコードをマシンコードに変換するための行ごとの解釈は非常に非効率的です。実行速度を向上させるために、最新のブラウザでは代わりに「ジャスト イン タイム コンパイラ」(JIT) が使用されます。つまり、バイトコードは実行時にのみコンパイルされ、使用された行がコンパイルされ、コンパイル結果がキャッシュされます (インライン キャッシュ)。 。通常、プログラム内のコードのごく一部のみが頻繁に使用されるため、コンパイル結果がキャッシュされると、プログラム全体の実行速度が大幅に向上します。

バイトコードは直接実行することはできませんが、仮想マシン (Virtual Machine) 上で実行されます。仮想マシンは一般に JavaScript エンジンとも呼ばれます。すべての JavaScript 仮想マシンが実行時にバイトコードを持つわけではありません。一部の JavaScript 仮想マシンはソース コードに基づいており、可能な限り、ソース コードは直接マシン コードにコンパイルされ、バイトコードを省略して JIT (ジャスト イン タイム) コンパイラを通じて実行されます。 。 ステップ。これは、仮想マシンを使用する他の言語 (Java など) とは異なります。この目的は、コードを最適化し、パフォーマンスを可能な限り向上させることです。以下は、現在利用可能な最も一般的な JavaScript 仮想マシンの一部です。

参考リンク


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0