JavaScriptで定義する前に関数を使用できるのはなぜですか?


167

このコードは、異なるブラウザーでも常に機能します。

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

しかし、なぜそれが機能するのかについての単一の参照を見つけることができませんでした。John Resigのプレゼンテーションノートでこれを最初に見ましたが、言及されただけです。そのことについてはそこやどこにも説明はありません。

誰かが私を啓発してくれませんか?


3
Firefoxの新しいバージョンでは、コードがtry / catchにある場合、これは機能しません。このフィドルを参照してください:jsfiddle.net/qzzc1evt
Joshua Smith

回答:


217

function宣言は魔法であり、その識別子は*が実行され、そのコードブロックには何も前に拘束されることになります。

これはfunction、通常のトップダウンの順序で評価される式を使用した代入とは異なります。

例を次のように変更した場合:

var internalFoo = function() { return true; };

機能しなくなります。

関数宣言は、構文的には関数式とはまったく異なりますが、ほとんど同じに見え、場合によってはあいまいになる場合があります。

これはECMAScript標準のセクション10.1.3に記載されています。残念ながらECMA-262は、標準規格でさえ、あまり読みやすい文書ではありません!

*:含まれている関数、ブロック、モジュール、またはスクリプト。


私はそれが本当に判読できないと思います。私はあなたが10.1.3で指摘したセクションを読んだだけで、そこにある規定がこの動作を引き起こす理由がわかりませんでした。情報のおかげで。
Edu Felipe

2
@bobinceわかりました。このページで「巻き上げ」という用語の言及が1つも見つからなかったので、自分自身を疑い始めました。うまくいけば、これらのコメントには、適切に設定するのに十分なGoogle Juice™が含まれています:)
Mathias Bynens

2
これは人気のある質問と回答の組み合わせです。ES5注釈付き仕様へのリンク/抜粋で更新することを検討してください。(どちらがよりアクセス可能か。)

1
この記事にはいくつかの例があります:JavaScriptスコープおよび巻き上げ
Lombas 2015

私はかなりの数のライブラリが定義の前に関数を使用していることを発見しました。ハスケル。正直なところ、場合によってはもう少し表現力を高めることができるので、これは悪いことではないかもしれません。
windmaomao

27

これはホイストと呼ばれ、定義される前に関数を呼び出す(呼び出す)ものです。

記述したい関数には、次の2種類があります。

式関数と宣言関数

  1. 式関数:

    関数式は変数に格納できるため、関数名は必要ありません。それらは無名関数(名前のない関数)としても名前が付けられます。

    これらの関数を呼び出す(呼び出す)には、常に変数名が必要です。この種の関数は、定義される前に呼び出された場合は機能しません。つまり、ホイストはここでは発生しません。常に最初に式関数を定義してから呼び出す必要があります。

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");

    これは、ECMAScript 6で記述できる方法です。

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. 宣言関数:

    次の構文で宣言された関数はすぐには実行されません。これらは「後で使用するために保存され」、呼び出された(呼び出された)ときに実行されます。このタイプの関数は、が定義されている場所のBEFOREまたはAFTERで呼び出すと機能します。定義する前に宣言関数を呼び出すと、巻き上げが正しく機能します。

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");

    巻き上げ例:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }

let fun = theFunction; fun(); function theFunction() {}動作します(ノードとブラウザ)
fider

14

ブラウザは最初から最後までHTMLを読み取り、それが読み取られて実行可能なチャンク(変数宣言、関数定義など)に解析されるときにHTMLを実行できますが、どの時点でも、その時点より前のスクリプトで定義されているものしか使用できません。

これは、すべてのソースコードを処理(コンパイル)し、参照を解決するために必要なライブラリとリンクして、実行が開始される実行可能モジュールを構築する他のプログラミングコンテキストとは異なります。

コードは、さらに定義された名前付きオブジェクト(変数、他の関数など)を参照できますが、すべてのピースが利用可能になるまで参照コードを実行することはできません。

JavaScriptに慣れると、適切な順序で物事を書く必要があることに親しみを感じるようになります。

改訂:受け入れられた回答(上記)を確認するには、Firebugを使用してWebページのスクリプトセクションをステップ実行します。関数から関数へスキップし、実際にコードを実行する前に、最初の行だけを表示します。


3

一部の言語では、使用前に識別子を定義する必要があるという要件があります。これは、コンパイラーがソースコードに対して単一のパスを使用するためです。

しかし、複数のパスがある場合(または一部のチェックが延期される場合)は、その要件がなくても完全に機能します。この場合、コードはおそらく最初に読み取られ(そして解釈され)、次にリンクが設定されます。


2

私はJavaScriptを少しだけ使用しました。これが役立つかどうかはわかりませんが、それはあなたが話していることと非常に似ており、いくつかの洞察を与える可能性があります:

http://www.dustindiaz.com/javascript-function-declaration-ambiguity/


リンクはもはや死んでいない。
mwclarke

1
リンクが切れています。
Jerome Indefenzo 2017

これはarchive.orgからのスナップショットです。このブログの投稿がそのカテゴリに該当するのではなく、コンテンツが古いために作成者 Webサイト全体を削除したようです。
jkmartindale

1

関数「internalFoo」の本体は解析時にどこかに移動する必要があるため、JSインタープリターによってコードが読み取られる(解析)と、関数のデータ構造が作成され、名前が割り当てられます。

後でのみ、コードが実行され、JavaScriptは実際に「internalFoo」が存在するかどうか、それが何であるか、およびそれを呼び出すことができるかどうかなどを調べようとします。


-4

同じ理由で、以下は常にfooグローバル名前空間に配置されます。

if (test condition) {
    var foo;
}

8
実際には、それは非常に異なる理由によるものです。if一方、ブロックは、スコープを作成していないfunction()ブロックは常に1を作成します。本当の理由は、グローバルJavaScript名の定義がコンパイル段階で行われるため、コードが実行されなくても名前が定義されるためです。(コメントするのに時間がかかりすぎて申し訳ありません)
Edu Felipe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.