プロトタイプによる継承
null
と undefined
を除き、各プリミティブ データ型にはプロトタイプがあります。これは、値を操作するためのメソッドを提供する対応するオブジェクト ラッパーです。プリミティブでメソッドまたはプロパティ検索が呼び出されると、JavaScript はプリミティブを非表示でラップし、代わりにラッパー オブジェクトでメソッドを呼び出すか、プロパティ検索を実行します。
たとえば、文字列リテラルには独自のメソッドはありませんが、対応する String
オブジェクト ラッパーにより、.toUpperCase()
メソッドを呼び出すことができます。
"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL
これはプロトタイプ継承と呼ばれ、値に対応するコンストラクタからプロパティとメソッドを継承します。
Number.prototype
> Number { 0 }
> constructor: function Number()
> toExponential: function toExponential()
> toFixed: function toFixed()
> toLocaleString: function toLocaleString()
> toPrecision: function toPrecision()
> toString: function toString()
> valueOf: function valueOf()
> <prototype>: Object { … }
プリミティブは、値で定義するだけでなく、これらのコンストラクタを使用して作成することもできます。たとえば、String
コンストラクタを使用すると、文字列リテラルではなく文字列オブジェクトが作成されます。このオブジェクトには、文字列値だけでなく、コンストラクタから継承されたすべてのプロパティとメソッドが含まれます。
const myString = new String( "I'm a string." );
myString;
> String { "I'm a string." }
typeof myString;
> "object"
myString.valueOf();
> "I'm a string."
ほとんどの場合、生成されたオブジェクトは、定義に使用した値と同じように動作します。たとえば、new Number
コンストラクタを使用して数値を定義すると、Number
プロトタイプのすべてのメソッドとプロパティを含むオブジェクトが作成されますが、数値リテラルの場合と同様に、これらのオブジェクトに対して数学演算子を使用できます。
const numberOne = new Number(1);
const numberTwo = new Number(2);
numberOne;
> Number { 1 }
typeof numberOne;
> "object"
numberTwo;
> Number { 2 }
typeof numberTwo;
> "object"
numberOne + numberTwo;
> 3
JavaScript にはプロトタイプによる継承が組み込まれているため、これらのコンストラクタを使用する必要はほとんどありません。コンストラクタを使用してプリミティブを作成すると、結果が単純なリテラルではなくオブジェクトになるため、予期しない結果になることもあります。
let stringLiteral = "String literal."
typeof stringLiteral;
> "string"
let stringObject = new String( "String object." );
stringObject
> "object"
これにより、厳密な比較演算子の使用が複雑になる可能性があります。
const myStringLiteral = "My string";
const myStringObject = new String( "My string" );
myStringLiteral === "My string";
> true
myStringObject === "My string";
> false
セミコロンの自動挿入(ASI)
スクリプトを解析するときに、JavaScript インタープリタは自動セミコロン挿入(ASI)と呼ばれる機能を使用して、省略されたセミコロンのインスタンスを修正しようとします。JavaScript パーサーが許可されていないトークンに遭遇した場合、次の条件の 1 つ以上が満たされている限り、そのトークンの前にセミコロンを追加して、潜在的な構文エラーを修正しようとします。
- そのトークンは、前のトークンと改行で区切られています。
- このトークンは
}
です。 - 前のトークンは
)
で、挿入されたセミコロンはdo
...while
ステートメントの終了セミコロンになります。
詳しくは、ASI ルールをご覧ください。
たとえば、次のステートメントの後にセミコロンを省略しても、ASI により構文エラーは発生しません。
const myVariable = 2
myVariable + 3
> 5
ただし、ASI では同じ行に複数のステートメントを記述することはできません。同じ行に複数のステートメントを記述する場合は、セミコロンで区切ってください。
const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier
const myVariable = 2; myVariable + 3;
> 5
ASI はエラー修正の試みであり、JavaScript に組み込まれた構文の柔軟性ではありません。正しいコードを生成するためにセミコロンに依存しないように、必要に応じてセミコロンを使用します。
厳格モード
JavaScript の記述方法を規定する標準は、言語の初期設計時に考慮されていたものから大きく進化しています。JavaScript の想定される動作に対する新しい変更は、古いウェブサイトでエラーが発生しないようにする必要があります。
ES5 では、スクリプト全体または個々の関数に対して、より制限の厳しい一連の言語ルールを有効にする方法である「厳格モード」を導入することで、既存の実装を破壊することなく、JavaScript のセマンティクスに関する長年の課題に対処しています。厳格モードを有効にするには、スクリプトまたは関数の最初の行で文字列リテラル "use strict"
に続いてセミコロンを指定します。
"use strict";
function myFunction() {
"use strict";
}
厳格モードでは、特定の「安全でない」アクションや非推奨の機能がブロックされ、一般的な「サイレント」エラーの代わりに明示的なエラーがスローされます。また、将来の言語機能と競合する可能性がある構文の使用が禁止されます。たとえば、変数スコープに関する初期の設計上の決定により、デベロッパーは、var
キーワードを省略して、変数を宣言するときに、その変数を含むコンテキストに関係なく、グローバル スコープを誤って「汚染」する可能性が高くなりました。
(function() {
mySloppyGlobal = true;
}());
mySloppyGlobal;
> true
最新の JavaScript ランタイムでは、この動作を修正すると、誤ってまたは意図的に、この動作に依存するウェブサイトが破損するリスクがあります。代わりに、最新の JavaScript では、デベロッパーが新しい作業で厳格モードを有効にできるようにし、従来の実装を破壊しない新しい言語機能のコンテキストでのみ、デフォルトで厳格モードを有効にすることで、この問題を回避しています。
(function() {
"use strict";
mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal
"use strict"
は文字列リテラルとして記述する必要があります。テンプレート文字列(use strict
)は機能しません。また、目的のコンテキストで実行可能なコードの前に "use strict"
を含める必要があります。それ以外の場合、インタープリタは無視します。
(function() {
"use strict";
let myVariable = "String.";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal
(function() {
let myVariable = "String.";
"use strict";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope
参照渡し、値渡し
オブジェクトのプロパティ、関数パラメータ、配列、セット、マップ内の要素など、任意の変数にはプリミティブ値または参照値を含めることができます。
プリミティブ値が変数間で代入されると、JavaScript エンジンはその値のコピーを作成し、変数に代入します。
オブジェクト(クラス インスタンス、配列、関数)を変数に割り当てると、そのオブジェクトの新しいコピーを作成する代わりに、変数にオブジェクトがメモリに保存されている位置への参照が含まれます。このため、変数によって参照されるオブジェクトを変更すると、その変数に含まれる値だけでなく、参照されるオブジェクトも変更されます。たとえば、オブジェクト参照を含む変数で新しい変数を初期化し、その新しい変数を使用してそのオブジェクトにプロパティを追加すると、プロパティとその値が元のオブジェクトに追加されます。
const myObject = {};
const myObjectReference = myObject;
myObjectReference.myProperty = true;
myObject;
> Object { myProperty: true }
これは、オブジェクトの変更だけでなく、厳密な比較を行う場合にも重要です。オブジェクト間の厳密な等価性では、両方の変数が同じオブジェクトを参照して true
と評価される必要があるためです。構造が同じであっても、異なるオブジェクトを参照することはできません。
const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};
myObject === myNewObject;
> false
myObject === myReferencedObject;
> true
メモリ割り当て
JavaScript は自動メモリ管理を使用します。つまり、開発中にメモリの割り当てや割り当て解除を明示的に行う必要はありません。JavaScript エンジンのメモリ管理方法の詳細は、このモジュールの範囲外ですが、メモリの割り当て方法を理解することは、参照値を操作する際に役立ちます。
メモリには、「スタック」と「ヒープ」の 2 つの「領域」があります。スタックには静的データ(プリミティブ値とオブジェクトへの参照)が保存されます。これは、このデータを保存するために必要な固定量のスペースを、スクリプトの実行前に割り当てることができるためです。ヒープにはオブジェクトが格納されます。オブジェクトのサイズは実行中に変更される可能性があるため、動的に割り振られたスペースが必要です。メモリは「ガベージ コレクション」と呼ばれるプロセスによって解放されます。このプロセスでは、参照のないオブジェクトがメモリから削除されます。
メインスレッド
JavaScript は基本的に「同期」実行モデルを備えたシングル スレッド言語です。つまり、一度に実行できるタスクは 1 つだけです。この順序実行コンテキストはメインスレッドと呼ばれます。
メインスレッドは、HTML の解析、ページの一部のレンダリングと再レンダリング、CSS アニメーションの実行、シンプルなもの(テキストのハイライト表示など)から複雑なもの(フォーム要素の操作など)まで、他のブラウザタスクと共有されます。ブラウザ ベンダーは、メインスレッドによって実行されるタスクを最適化する方法を見つけましたが、複雑なスクリプトでは、メインスレッドのリソースを過度に使用し、ページ全体のパフォーマンスに影響する可能性があります。
一部のタスクは、ウェブワーカーと呼ばれるバックグラウンド スレッドで実行できますが、制限があります。
- ワーカー スレッドは、スタンドアロンの JavaScript ファイルに対してのみ動作できます。
- ブラウザ ウィンドウと UI へのアクセスが大幅に制限されているか、アクセスできない。
- メインスレッドとの通信方法が制限されます。
これらの制限により、メインスレッドを占有する可能性のある、リソースを大量に消費するタスクに適しています。
コールスタック
「実行コンテキスト」(アクティブに実行されているコード)の管理に使用されるデータ構造は、呼び出しスタック(多くの場合単に「スタック」)と呼ばれるリストです。スクリプトが初めて実行されると、JavaScript インタープリタは「グローバル実行コンテキスト」を作成し、それを呼び出しスタックにプッシュします。このグローバル コンテキスト内のステートメントは、上から下へ 1 つずつ実行されます。インタープリタがグローバル コンテキストの実行中に関数呼び出しに遭遇すると、その呼び出しの「関数実行コンテキスト」をスタックの一番上にプッシュし、グローバル実行コンテキストを一時停止して、関数実行コンテキストを実行します。
関数が呼び出されるたびに、その呼び出しの関数実行コンテキストがスタックの最上部(現在の実行コンテキストのすぐ上)にプッシュされます。呼び出しスタックは「後入れ先出し」方式で動作します。つまり、スタック内の最上位にある最近の関数呼び出しが実行され、解決するまで続行されます。その関数が完了すると、インタープリタはそれをコールスタックから削除し、その関数呼び出しを含む実行コンテキストがスタック内の最上位の項目になり、実行が再開されます。
これらの実行コンテキストは、実行に必要な値をキャプチャします。また、親コンテキストに基づいて関数のスコープ内で使用可能な変数と関数を確立し、関数のコンテキストで this
キーワードの値を決定して設定します。
イベントループとコールバック キュー
この順序実行により、コールバック関数を含む非同期タスク(サーバーからのデータの取得、ユーザー操作への応答、setTimeout
または setInterval
で設定されたタイマーの待機など)は、タスクが完了するまでメインスレッドをブロックするか、コールバック関数の実行コンテキストがスタックに追加された瞬間に現在の実行コンテキストを予期せず中断します。JavaScript では、この問題に対処するために、「イベントループ」と「コールバック キュー」(「メッセージ キュー」とも呼ばれる)で構成されるイベント ドリブンの「同時実行モデル」を使用して非同期タスクを管理します。
非同期タスクがメインスレッドで実行されると、コールバック関数の実行コンテキストは、コールスタックの上ではなく、コールバック キューに配置されます。イベントループは、リアクション システムとも呼ばれるパターンで、コールスタックとコールバック キューのステータスを継続的にポーリングします。コールバックキューにタスクがあり、イベントループがコールスタックが空であると判断した場合、コールバックキューからタスクが 1 つずつスタックにプッシュされて実行されます。