25

JavaScriptは解釈され、コンパイルされないことを理解しています。問題ありません。ただし、JavaScriptは「オンザフライ」で実行され、行は一度に1つずつ読み取られることをここで読み続けています。このアイデアは、次の例になるとかなり混乱します。

writeToConsole();

function writeToConsole() {
    console.log("This line was reached.");
}

ちなみに、このコードはコンソールに問題なく書き込まれます。exampleFunction()それでも、ブラウザがまだ機能に到達していない場合、ブラウザはどのようにしてその存在を知るのでしょうか。

言い換えれば、この関数が最初に解釈されるのはいつですか

4

6 に答える 6

60

まず、あなたは間違った仮定をしています。最新の JavaScript はコンパイルされています。V8SpiderMonkey 、および Nitroなどのエンジンは、JS ソースをホスト プラットフォームのネイティブマシン コードにコンパイルします。

古いエンジンでも、JavaScript は解釈されません。これらはソース コードをバイトコードに変換し、エンジンの仮想マシンが実行します。

Java 言語と .NET 言語の実際の動作は次のとおりです。アプリケーションを「コンパイル」すると、実際にはソース コードがプラットフォームのバイトコード、Java バイトコード、およびCILにそれぞれ変換されます。次に、実行時にJIT コンパイラがバイトコードをマシン コードにコンパイルします。

解釈は非常に遅いため、実際に JavaScript ソース コードを解釈するのは、非常に古くて単純な JS エンジンだけです。

では、JS コンパイルはどのように機能するのでしょうか。最初のフェーズでは、ソース テキストが抽象構文木 (AST)に変換されます。これは、マシンが処理できる形式でコードを表すデータ構造です。概念的には、これは HTML テキストがDOM表現に変換される方法によく似ています。これは、コードが実際に動作するものです。

AST を生成するために、エンジンは raw バイトの入力を処理する必要があります。これは通常、字句解析器によって行われます。lexer は実際にはファイルを「1 行ずつ」読み取るわけではありません。むしろ、言語の構文の規則を使用してソース テキストをトークンに変換し、バイト単位で読み取ります。次にレクサーは、トークンのストリームをパーサーに渡します。パーサーは実際に AST を構築します。パーサーは、トークンが有効なシーケンスを形成することを確認します。

これで、構文エラーによってコードがまったく機能しない理由が明確にわかるはずです。ソース テキストに予期しない文字が含まれていると、エンジンは完全な AST を生成できず、次のフェーズに進むことができません。

エンジンに AST があると、次のようになります。

  • インタープリターは、AST から直接命令の実行を開始するだけかもしれません。これは非常に遅いです。
  • JS VM 実装は、AST を使用してバイトコードを生成し、バイトコードの実行を開始します。
  • コンパイラは AST を使用して、CPU が実行するマシン コードを生成します。

少なくとも、JS の実行は 2 つのフェーズで行われることがわかります。

ただし、実行のフェーズは、例が機能する理由に実際には影響しません。JavaScript プログラムがどのように評価され実行されるかを定義するルールがあるため、これは機能します。ルールは、エンジン自体が実際にソースコードを解釈/コンパイルする方法に影響を与えずに、例が機能しないような方法で簡単に記述できます。

具体的には、JavaScript には一般にホイストと呼ばれる機能があります。巻き上げを理解するには、関数宣言関数式の違いを理解する必要があります。

簡単に言うと、関数宣言とは、別の場所で呼び出される新しい関数を宣言することです。

function foo() {

}

関数式は、変数の割り当てや引数など、functionが必要な場所でキーワードを使用する場合です。

var foo = function() { };

$.get('/something', function() { /* callback */ });

JavaScriptでは、宣言が (コンテキストの) ソース テキストのどこにあるかに関係なく、実行コンテキストの先頭で関数宣言(最初のタイプ) を変数名に割り当てることが義務付けられています。実行コンテキストは、スコープとほぼ同等です。簡単に言えば、関数内のコード、または関数内でない場合はスクリプトの一番上です。

これにより、非常に奇妙な動作が発生する可能性があります。

var foo = function() { console.log('bar'); };

function foo() { console.log('baz'); }

foo();

コンソールに何が記録されると思いますか? コードを単純に直線的に読むと、 と思うかもしれませんbazbarただし、の宣言が にfoo代入する式の上に持ち上げられているため、実際には がログに記録されfooます。

結論として:

  • JS ソース コードは、行ごとに「読み取られる」ことはありません。
  • JS ソース コードは、最新のブラウザーで実際に (本当の意味で) コンパイルされます。
  • エンジンは複数のパスでコードをコンパイルします。
  • あなたの例の動作は、JavaScript 言語のルールの副産物であり、コンパイルや解釈の方法ではありません。
于 2013-03-13T21:16:08.993 に答える
11

すべての関数は、コードが実行される前に、ブラウザによって最初に検査されます。

でも、

var foo = function(){};

これは検査されないため、以下はTypeError: undefined is not a function

foo();
var foo = function(){};
于 2013-03-13T20:05:55.873 に答える
10

2パスかかります。最初のパスは構文ツリーを解析し、その一部は巻き上げを実行しています。その巻き上げは、投稿されたコードを機能させるものです。巻き上げは、var関数宣言または名前付き関数宣言function fn(){}(関数式は除くfn = function(){}) を、それらが表示される関数の先頭に移動します。

2 番目のパスは、解析され、ホイストされ、一部のエンジンではコンパイルされたソース コード ツリーを実行します。

この例を確認してください。これは、構文エラーが最初のパスでレンチを投げることによってスクリプトのすべての実行を妨げ、2 番目のパス (実際のコードの実行) が発生しないようにする方法を示しています。

var validCode = function() {
  alert('valid code ran!');
};
validCode();

// on purpose syntax error after valid code that could run
syntax(Error(

http://jsfiddle.net/Z86rj/

alert()ここでは発生しません。最初のパスの解析は失敗し、コードは実行されません。

于 2013-03-13T20:09:33.383 に答える
3

スクリプトは最初に解析され、次に解釈され、実行されます。最初のステートメント ( writeToConsole();) が実行されるとき、関数宣言は既に解釈されています。

すべての変数と関数の宣言は現在のスコープ (あなたの場合はグローバル スクリプト スコープ) で巻き上げられるため、以下で宣言された関数を呼び出すことができます。

于 2013-03-13T20:07:58.107 に答える
1

JavaScript は実際には 1 行ずつ解釈されます。しかし、それが実行される前に、コンパイラーによって行われる最初のパスがあり、特定のものを読み取ります (かなりこっけいな人は、これを見てください: https://www.youtube.com/watch?v=UJPdhx5zTaw本当に興味がある)。

要点は、JavaScript は、定義済みの関数を格納するコンパイラによって最初に「読み取られる」ということfunction foo(){...}です。それらを同じスコープまたは下位スコープから呼び出す場合は、スクリプト内の任意の時点でそれらを呼び出すことができます。最近のコンパイラもオブジェクトの事前割り当てを行っているため、副作用として、パフォーマンスの問題のために変数を厳密に型指定することは理にかなっています。

var foo = function(){...}JavaScript は緩く型付けされており、実行時に変数の型が変化する可能性があるため、コンパイラによって格納されません。

于 2013-03-13T20:14:56.790 に答える