9

コードは javascriptissexy.com に属しています 外部関数で指定された 2 番目のパラメーターは、常に js = 内部関数パラメーターに指定されていますか? 誰かが全体の概念を非常に詳細に説明できますか?

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
// this inner function has access to the outer function's variables, including the parameter
    function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

var mjName = celebrityName ("Michael");
    // At this juncture, the celebrityName outer function has returned.

// The closure (lastName) is called here after the outer function has returned above
// Yet, the closure still has access to the outer function's variables and parameter
mjName ("Jackson"); // This celebrity is Michael Jackson
4

3 に答える 3

18

OPは、全体のコンセプトの詳細な説明を求めました。ここでの試みは、閉鎖が発生するために必要なコア要素を説明することです。

javascriptissexy のような例との混乱の一部は、これらの関数の名前が、特に javascript や新しいコーディング全般に慣れていない人にとって、何をすべきかを明確に表していないことだと思います。

スコープについて話すことから始めましょう:

Javascript では、すべての関数が独自のローカル スコープまたはメモリ空間を作成します。これはレキシカルスコープと呼ばれます。このメモリ空間には、関数のパラメーターからのすべての変数と、宣言された変数および関数本体内 (中括弧内) の式が格納されます。

javascriptissexy の例に見られるように、関数をネストできます。各関数は独自のローカル スコープを作成するため、これらのスコープがどのように関連し、相互に作用するかを理解する必要があります。スコープが持つことができる関係には、3 つの異なるタイプがあります。

これらのコード スニペットはすべて、ブラウザーの開発コンソール内でテストすることをお勧めします。

子スコープは、親 (および祖父母、曾祖父母など) のスコープ変数にアクセスできます。

    function parent() {

        var parentAsset = 'The Minivan'

        function child() {

            //child has access to parent asset

            console.log(parentAsset);

        }

        // call child function

        child();
    }

    parent();  // 'The Minivan'

親スコープは、子のスコープ変数にアクセスできません

    function parent() {

        function child() {

            var childAsset = 'Mp3 Player'

        }

        //parent can't get childAsset

        console.log(childAsset);

    }

    parent();   // ReferenceError childAsset not defined

兄弟スコープは、互いのスコープ変数にアクセスできません

    function childOne() {

        var childOneAsset = 'Iphone'

    }

    function childTwo() {

        console.log(childOneAsset);

    }

    childTwo();  // ReferenceError childOneAsset not defined

さて、OPで言及された機能に戻ります。この関数をより良い名前で作り直してみましょう。ポイントを示すために、この最初のサンプル関数にもう 1 つの変数を追加しています。

getFirstName('Michael') 以下の例で呼び出すと、次の 4 つのことが起こります。

  1. この関数内で、変数firstNameは「Michael」に設定されます
  2. varnameIntroは値「この有名人は」に設定されています。
  3. varunusedStringは「この文字列はガベージ コレクションされます」という値に設定されます
  4. 関数introduceCelebrityが宣言されている
  5. 関数introduceCelebrityが返されます

    function getFirstName (firstName) {
    
        var nameIntro = "This celebrity is ";
        var unusedString = "This string will be garbage collected";
    
        function introduceCelebrity (lastName) {
            return nameIntro + firstName + " " + lastName;
        }
    
        return introduceCelebrity;
    }
    
    var mjName = getFirstName('Michael');
    

あなたはおそらくそれをすでに知っていました。

注目すべき興味深い点を次に示します。

  • このgetFirstName関数は、値を設定する以外には何もしfirstNameません。nameIntroだからそこに魔法はありません。
  • 子関数introduceCelebrity は、これら 2 つの変数を参照します。前述のように、子スコープは親スコープ変数にアクセスできるため、これが可能です。これは、閉鎖への最初の重要なステップです。
  • その後、introduceCelebrity関数が返されます (ただし、実行されません)。おそらく、後で呼び出すことができます。これは閉鎖への第 2 段階です。
  • introduceCelebrity親スコープ変数を参照し、関数全体を返すため、JavaScript ランタイムは、関数が戻った後でも、これらの変数へのポインターを維持しますgetFirstName
  • そのポインターが存在するため、ガベージ コレクターはそれらの変数をそのままにします。これらのポインターが存在しない場合、ガベージ コレクターがアクセスしてそれらのメモリ アドレスを消去し、それらの値にアクセスできなくなります。
  • 変数はunusedString子関数で参照されないため、ガベージ コレクションされて使用できなくなります。

それでは、コードをもう一度見てみましょう。

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

    function introduceCelebrity (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return introduceCelebrity;
}

var mjName = getFirstName('Michael');

このコードを実行すると、基本的に次のようになります。

var mjName = function(theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

これについて何が特別なのですか?閉鎖はどこですか?

関数が実行されたのでgetFirstName、そのローカル変数やアセットとともにすべてがなくなったと考えるかもしれません。 これは正しくありません

子関数内で親スコープ変数を参照し、子関数を返すことでクロージャを作成しました。実際、上記のコードの新しいスコープは、実際には次のようになります。

var nameIntro = "This celebrity is ";

var firstName = "Michael"

var mjName = function(theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

どのようnameIntrofirstName私たちに利用可能になりましたか? それは、閉鎖を作成したためです。

したがって、次のように呼び出しますmjName

mjName('Jackson');  // 'This celebrity is Michael Jackson'

そして、期待どおりの結果が得られます。

待って、最後にもう 1 つ!

要点を理解するために、私たちの例をわずかに変更した例と比較してみましょう。

元の関数がネストされていることに注意してください。 クロージャは、ネストされた関数でのみ発生します

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

    function introduceCelebrity (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return introduceCelebrity;
}

var mjName = getFirstName('Michael');

そのネストを削除してみましょう。

function getFirstName (firstName) { 
    var nameIntro = "This celebrity is ";
}

function introduceCelebrity (theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

var mjName = getFirstName('Michael');
introduceCelebrity('Jackson');  

// ReferenceError: nameIntro is not defined

これは機能しますか?

いいえ、そうではありません。兄弟スコープは互いの変数にアクセスできないためです。

それでは、どうすれば閉鎖せずにこれを機能させることができるでしょうか?

  1. getFirstName変数を含むオブジェクトまたは配列を返す必要があります
  2. getFirstName('Michael')グローバル変数に設定する必要がありますmjName
  3. を呼び出しintroduceCelebrity('Jackon')、値を渡しますmjName

    function getFirstName (firstName) { 
    
        var nameIntro = "This celebrity is ";
    
        return {
            firstName: firstName,
            nameIntro: nameIntro
        }
    }
    
    var mjName = getFirstName('Michael');  // returns our object
    
    function introduceCelebrity (theLastName, firstName, nameIntro) {
        return nameIntro + firstName + " " + theLastName;
    }
    
    introduceCelebrity('Jackson', mjName.firstName, mjName.nameIntro);  
    
    // 'This celebrity is Michael Jackson'
    
于 2015-04-17T20:44:34.570 に答える
14

関数は次のように評価されます。celebrityName ("Michael")("Jackson");

手順 :

  1. 有名人の名前 ("マイケル") は関数 lastName(theLastName) を返します
  2. ("Jackson") が関数 lastName に渡されます
  3. function lastName(theLastName) は、実行時に文字列を出力します

左から右への引数は、呼び出されたメソッドの外側から内側へと進みます。

于 2013-09-06T10:35:24.820 に答える