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 つのことが起こります。
- この関数内で、変数
firstName
は「Michael」に設定されます
- var
nameIntro
は値「この有名人は」に設定されています。
- var
unusedString
は「この文字列はガベージ コレクションされます」という値に設定されます
- 関数
introduceCelebrity
が宣言されている
関数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;
}
どのようnameIntro
にfirstName
私たちに利用可能になりましたか? それは、閉鎖を作成したためです。
したがって、次のように呼び出します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
これは機能しますか?
いいえ、そうではありません。兄弟スコープは互いの変数にアクセスできないためです。
それでは、どうすれば閉鎖せずにこれを機能させることができるでしょうか?
getFirstName
変数を含むオブジェクトまたは配列を返す必要があります
getFirstName('Michael')
グローバル変数に設定する必要がありますmjName
を呼び出し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'