JavaScriptのクロージャを構成する概念(たとえば、関数、変数など)についての知識を持っている人にJavaScriptクロージャをどのように説明しますか。しかし、クロージャ自体は理解していません。
Schemeの例 をウィキペディアで見たことがありますが、残念ながら役に立ちませんでした。
2006年2月21日10:19、火曜日にMorrisにより提出。コミュニティ編集以来。
このページでは、プログラマが作業JavaScriptコードを使用してクロージャを理解できるようにクロージャについて説明します。達人や機能プログラマ向けではありません。
クロージャーは、コアの概念がまとまれば理解するのは難しくありません。ただし、理論的または学問的な説明を読んで理解することは不可能です!
この記事は、主流の言語である程度のプログラミング経験があり、次のJavaScript関数を読むことができるプログラマーを対象としています。
function sayHello(name) {
var text = 'Hello ' + name;
var say = function() { console.log(text); }
say();
}
sayHello('Joe');
関数(foo
)が他の関数(barおよびbaz)を宣言する場合、foo
で作成されたローカル変数のファミリーはnot destroy関数が終了したとき。変数は、外の世界から見えなくなります。したがって、foo
は関数bar
およびbaz
を巧妙に返すことができ、この閉じた変数ファミリー(「クロージャー」)を介して相互に読み取り、書き込み、および通信を継続できます。将来、foo
を再度呼び出す人でさえ、他の誰も干渉することはできません。
クロージャは first-class functions ;をサポートする1つの方法です。スコープ内の変数を参照できる式(最初に宣言されたとき)、変数に割り当てられる、関数への引数として渡される、または関数の結果として返される式です。
次のコードは、関数への参照を返します。
function sayHello2(name) {
var text = 'Hello ' + name; // Local variable
var say = function() { console.log(text); }
return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"
ほとんどのJavaScriptプログラマーは、上記のコードで関数への参照が変数(say2
)にどのように返されるかを理解します。そうでない場合は、クロージャを学習する前にそれを確認する必要があります。 Cを使用するプログラマーは、関数を関数へのポインターを返すと考え、変数say
とsay2
はそれぞれ関数へのポインターであると考えます。
関数へのCポインターと関数へのJavaScript参照には重大な違いがあります。 JavaScriptでは、関数参照変数は、関数へのポインターとクロージャーへの隠しポインターの両方を持つと考えることができます。
上記のコードは、匿名関数function() { console.log(text); }
がinside別の関数、この例ではsayHello2()
として宣言されているため、クロージャーを持っています。 JavaScriptでは、別の関数内でfunction
キーワードを使用すると、クロージャーが作成されます。
Cおよび他のほとんどの一般的な言語では、after関数が戻ると、スタックフレームが破棄されるため、すべてのローカル変数にアクセスできなくなります。
JavaScriptでは、別の関数内で関数を宣言した場合、外側の関数のローカル変数は、そこから戻った後もアクセス可能なままになります。 say2()
から戻った後に関数sayHello2()
を呼び出すため、これは上記に示されています。呼び出すコードは、関数sayHello2()
のローカル変数である変数text
を参照していることに注意してください。
function() { console.log(text); } // Output of say2.toString();
say2.toString()
の出力を見ると、コードが変数text
を参照していることがわかります。匿名関数は、sayHello2()
のローカル変数がクロージャー内で秘密に保持されているため、値'Hello Bob'
を保持するtext
を参照できます。
天才は、JavaScriptでは関数参照にもそれが作成されたクロージャーへの秘密参照があることです。デリゲートがメソッドポインターとオブジェクトへの秘密参照であるのと同様です。
何らかの理由で、クロージャーを読んで理解するのは本当に難しいように見えますが、いくつかの例を見ると、クロージャーがどのように機能するかが明らかになります(しばらく時間がかかりました)。例がどのように機能するかを理解するまで、例をよく検討することをお勧めします。それらがどのように機能するかを完全に理解せずにクロージャの使用を開始すると、すぐにいくつかの非常に奇妙なバグが作成されます!
この例は、ローカル変数がコピーされないことを示しています—それらは参照によって保持されます。これは、外部関数が終了した後でも、スタックフレームがメモリ内で存続しているようです。
function say667() {
// Local variable that ends up within closure
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = say667();
sayNumber(); // logs 43
3つのグローバル関数はすべて、sameクロージャーへの共通参照を持っています。これらはすべて、setupSomeGlobals()
への単一の呼び出し内で宣言されているためです。
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 42;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobals();
gLogNumber(); // 42
oldLog() // 5
3つの関数は、同じクロージャー(3つの関数が定義されたときのsetupSomeGlobals()
のローカル変数)への共有アクセスを持っています。
上記の例では、setupSomeGlobals()
を再度呼び出すと、新しいクロージャ(スタックフレーム!)が作成されることに注意してください。古いgLogNumber
、gIncreaseNumber
、gSetNumber
変数は、新しいクロージャーを持つnew関数で上書きされます。 (JavaScriptでは、別の関数内で関数を宣言するたびに、外部関数が呼び出されるたびに内部関数が再作成されますeach。 )
この例は、クロージャに、終了する前に外部関数内で宣言されたローカル変数が含まれていることを示しています。変数alice
は、実際には匿名関数の後に宣言されることに注意してください。匿名関数が最初に宣言され、その関数が呼び出されると、alice
は同じスコープ内にあるため、alice
変数にアクセスできます(JavaScriptは variable hoisting )。また、sayAlice()()
は、sayAlice()
から返された関数参照を直接呼び出します。これは、以前に行われたものとまったく同じですが、一時変数はありません。
function sayAlice() {
var say = function() { console.log(alice); }
// Local variable that ends up within closure
var alice = 'Hello Alice';
return say;
}
sayAlice()();// logs "Hello Alice"
トリッキー:say
変数もクロージャー内にあり、sayAlice()
内で宣言される可能性のある他の関数からアクセスできることに注意してください。または、内部関数内で再帰的にアクセスできます。
これは多くの人々にとって本当に落とし穴ですので、あなたはそれを理解する必要があります。ループ内で関数を定義している場合は非常に注意してください。クロージャからのローカル変数は、最初に考えたように動作しない場合があります。
この例を理解するには、Javascriptの「可変巻き上げ」機能を理解する必要があります。
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.Push( function() {console.log(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() //logs "item2 undefined" 3 times
result.Push( function() {console.log(item + ' ' + list[i])}
行は、匿名関数への参照を結果配列に3回追加します。匿名関数にあまり慣れていない場合は、次のように考えてください。
pointer = function() {console.log(item + ' ' + list[i])};
result.Push(pointer);
この例を実行すると、"item2 undefined"
が3回記録されることに注意してください!これは、前の例と同様に、buildList
(result
、i
、list
、およびitem
)のローカル変数のクロージャーが1つしかないためです。 。行fnlist[j]()
で匿名関数が呼び出されたとき;それらはすべて同じ単一のクロージャーを使用し、その1つのクロージャー内でi
およびitem
の現在の値を使用します(ここで、i
の値は3
です。完了し、item
の値は'item2'
になります。 0からインデックスを作成しているため、item
の値はitem2
になります。そして、i ++はi
を値3
にインクリメントします。
item
キーワードによる関数スコープの変数宣言の代わりに、変数let
のブロックレベル宣言が(var
キーワードによる)使用された場合に何が起こるかを確認すると役立つ場合があります。 。その変更が行われた場合、配列result
内の各匿名関数には独自のクロージャーがあります。この例を実行すると、出力は次のようになります。
item0 undefined
item1 undefined
item2 undefined
変数i
もlet
の代わりにvar
を使用して定義されている場合、出力は次のようになります。
item0 1
item1 2
item2 3
この最後の例では、main関数の呼び出しごとに個別のクロージャーが作成されます。
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.Push(num);
console.log('num: ' + num +
'; anArray: ' + anArray.toString() +
'; ref.someVar: ' + ref.someVar + ';');
}
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
すべてが完全に不明瞭に思える場合は、サンプルを試してみることをお勧めします。説明を読むことは、例を理解するよりもはるかに困難です。クロージャーやスタックフレームなどの私の説明は技術的には正しくありません。これらは理解を助けることを目的とした単純な単純化です。基本的なアイデアを確認したら、後で詳細を確認できます。
function
を使用するときは常に、クロージャーが使用されます。eval()
を使用するときは常に、クロージャーが使用されます。 eval
は、関数のローカル変数を参照でき、eval
内では、eval('var foo = …')
を使用して新しいローカル変数を作成することもできます。new Function(…)
( Function constructor )を使用すると、クロージャーは作成されません。 (新しい関数は、外部関数のローカル変数を参照できません。)myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));
)、myFunction
がクロージャの場合は機能しません(もちろん、ソースコード文字列を実行することさえ考えないでしょう)実行時の置換、しかし...)。justクロージャーを学習している場合(ここまたは他の場所で!)、これを行う可能性のある変更についてのフィードバックをお待ちしています記事を明確にします。 morrisjohns.com(morris_closure @)にメールを送信します。私はJavaScriptの第一人者でも、クロージャーの第一人者でもないことに注意してください。
Morrisによる元の投稿は Internet Archive にあります。
他の関数の中にfunctionキーワードがある場合はいつでも、内側の関数は外側の関数内の変数にアクセスできます。
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
bar
はx
への引数として定義されたfoo
にアクセスでき、tmp
からfoo
にもアクセスできるため、これは常に16をログに記録します。
それ is クロージャ。関数はクロージャと呼ばれるためにreturnする必要はありません。 直接の字句スコープ外の変数にアクセスするだけでクロージャが作成されます 。
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2); // bar is now a closure.
bar(10);
bar
はスコープ内ではなくなっていますが、x
とtmp
を参照することができるため、上記の関数も16を記録します。
しかし、tmp
はまだbar
のクロージャーの内側でぶら下がっているので、それも増加しています。 bar
を呼び出すたびに増加します。
クロージャの最も簡単な例はこれです。
var a = 10;
function test() {
console.log(a); // will output 10
console.log(b); // will output 6
}
var b = 6;
test();
JavaScript関数が呼び出されると、新しい実行コンテキストが作成されます。関数の引数と親オブジェクトとともに、この実行コンテキストはその外側で宣言されたすべての変数も受け取ります(上記の例では、 'a'と 'b'の両方)。
それらのリストを返すか、それらをグローバル変数に設定することによって、複数のクロージャ関数を作成することが可能です。これらはすべて 同じx
と同じtmp
を参照します。それらは独自のコピーを作成しません。
ここで、番号x
はリテラル番号です。 JavaScriptの他のリテラルと同様に、foo
が呼び出されると、数値x
は引数(foo
)として コピーされた からx
になります。
一方、JavaScriptはオブジェクトを扱うときに常に参照を使用します。例えば、あなたがオブジェクトでfoo
を呼び出した場合、それが返すクロージャーは 参照 その元のオブジェクトです!
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + tmp);
x.memb = x.memb ? x.memb + 1 : 1;
console.log(x.memb);
}
}
var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);
予想通り、bar(10)
を呼び出すたびにx.memb
がインクリメントされます。予想されないかもしれませんが、x
は単にage
変数と同じオブジェクトを参照しているだけです。 bar
を数回呼び出した後、age.memb
は2になります。この参照は、HTMLオブジェクトでのメモリリークの基礎です。
はじめに:この答えは質問がされたときに書かれました:
アルバートが言ったように、「6歳に説明できないのであれば、自分では理解できません」27歳の友人にJSの閉鎖について説明しようとしましたが、失敗しました。
誰かが私が6歳で、その話題に不思議に興味を持っていると考えることはできますか?
私が最初の質問を文字通り受けとろうとした唯一の人々のうちの1人だったと確信しています。それ以来、質問は何度も変化したので、私の答えは信じられないほど愚かで場違いに見えるかもしれません。うまくいけば、物語の一般的なアイデアは一部の人にとっては楽しいままです。
難しい概念を説明するとき、私はアナロジーと比喩の大ファンです。そこで、ストーリーを手にしてみましょう。
昔々:
王女がいた...
function princess() {
彼女は冒険に満ちた素晴らしい世界に住んでいました。彼女は王子様の魅力に出会い、ユニコーンで世界を駆け巡り、ドラゴンとの戦い、動物たちとの会話、そしてその他多くの素晴らしいものに出会いました。
var adventures = [];
function princeCharming() { /* ... */ }
var Unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
/* ... */
しかし彼女はいつも退屈で大人っぽい彼女の退屈な世界に戻らなければならないでしょう。
return {
そして彼女は頻繁に彼女らに王女としての彼女の最新の驚くべき冒険について話すでしょう。
story: function() {
return adventures[adventures.length - 1];
}
};
}
しかし、彼らが見るのは小さな女の子だけです...
var littleGirl = princess();
...魔法と幻想についての物語を語る。
littleGirl.story();
そして、大人が本当の王女を知っていたとしても、彼らは決して彼らを見ることができなかったので、彼らはユニコーンやドラゴンを決して信じないでしょう。大人は彼らが小さな女の子の想像の中にのみ存在していたと述べました。
しかし、私たちは真実を知っています。その王女の中の小さな女の子...
...は、本当に小さな女の子がいるお姫様です。
真剣に質問をして、私たちは典型的な6歳が認知的に可能であるものを見つけるべきです、確かに、JavaScriptに興味がある人はそれほど典型的ではありません。
On 子供の頃の発達:5〜7年 /それは言う:
あなたの子供は二段階の指示に従うことができるでしょう。たとえば、子供に「キッチンに行ってゴミ袋を持っていって」と言うと、その方向を思い出すことができます。
次のように、この例を使用してクロージャを説明できます。
キッチンは
trashBags
と呼ばれるローカル変数を持つクロージャです。キッチン内にgetTrashBag
という名前の関数があり、これは1つのゴミ袋を取得してそれを返します。
これをJavaScriptで次のようにコーディングできます。
function makeKitchen() {
var trashBags = ['A', 'B', 'C']; // only 3 at first
return {
getTrashBag: function() {
return trashBags.pop();
}
};
}
var kitchen = makeKitchen();
console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A
クロージャがなぜ面白いのかを説明するさらなるポイント:
makeKitchen()
が呼び出されるたびに、新しいクロージャがそれ自身の個別のtrashBags
で作成されます。trashBags
変数は各キッチンの内側にローカルであり、外側からアクセスすることはできませんが、getTrashBag
プロパティの内側の関数はそれにアクセスできます。getTrashBag
関数でオブジェクトを返すことはここでそれをします。ボタンが何回クリックされたかを知り、3回クリックするごとに何かをする必要があります。
// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');
element.addEventListener("click", function() {
// Increment outside counter
counter++;
if (counter === 3) {
// Do something every third time
console.log("Third time's the charm!");
// Reset counter
counter = 0;
}
});
<button id="button">Click Me!</button>
これでうまくいくでしょうが、変数を追加することで外側の範囲に侵入します。その唯一の目的は、カウントを追跡することです。状況によっては、外部アプリケーションがこの情報にアクセスする必要がある場合があるので、これが望ましいでしょう。しかし、この場合は、3回クリックするたびに動作が変わるので、 イベントハンドラ内にこの機能を含めることをお勧めします 。
var element = document.getElementById('button');
element.addEventListener("click", (function() {
// init the count to 0
var count = 0;
return function(e) { // <- This function becomes the click handler
count++; // and will retain access to the above `count`
if (count === 3) {
// Do something every third time
console.log("Third time's the charm!");
//Reset counter
count = 0;
}
};
})());
<button id="button">Click Me!</button>
ここでいくつかのことに注意してください。
上記の例では、JavaScriptのクロージャー動作を使用しています。 この動作により、すべての関数は、それが作成されたスコープに無期限にアクセスできます。 これを実際に適用するには、すぐに別の関数を返す関数を呼び出します。そして、返す関数は内部のcount変数にアクセスできるため(上記のクロージャの動作のため)、これは使用のためのプライベートスコープになります。結果の関数によって...それほど単純ではないですか?希釈してみましょう...
単純な1行クロージャー
// _______________________Immediately invoked______________________
// | |
// | Scope retained for use ___Returned as the____ |
// | only by returned function | value of func | |
// | | | | | |
// v v v v v v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();
返された関数の外側のすべての変数は返された関数で利用できますが、返された関数オブジェクトでは直接利用できません...
func(); // Alerts "val"
func.a; // Undefined
それを得る?そのため、私たちの主な例では、count変数はクロージャ内に含まれており、イベントハンドラでは常に利用可能であるため、クリックごとに状態を保持します。
また、このプライベート変数の状態は、読み取りとそのプライベートスコープ変数への割り当ての両方に対して、 fully accessibleです。
そこに行きます。これで、この動作は完全にカプセル化されました。
ブログ記事全体(jQueryに関する考慮事項を含む)
クロージャは、誰もが直感的にとにかく動作することを期待している動作を動作させるために使用されるため、説明するのは困難です。私はそれらを説明する最良の方法を見つけます(そしてIが彼らが何をするかを学んだ方法)はそれらなしで状況を想像することです:
var bind = function(x) {
return function(y) { return x + y; };
}
var plus5 = bind(5);
console.log(plus5(3));
JavaScript did n'tクロージャを知っている場合、ここで何が起こるでしょうか?最後の行の呼び出しをメソッド本体で置き換えるだけで(これは基本的に関数呼び出しの機能です)、次のようになります:
console.log(x + 3);
さて、x
の定義はどこですか?現在のスコープでは定義していません。唯一の解決策は、plus5
carryのスコープ(または、その親のスコープ)を使用することです。このように、x
は明確に定義されており、値5にバインドされています。
これは他の答えのいくつかに現れるクロージャーに関するいくつかの(可能性のある)誤解を解決する試みです。
さて、6歳のクロージャーファン。最も簡単な閉鎖の例を聞きたいですか?
次の状況を想像してみましょう:運転手が車に座っています。あの車は飛行機の中です。飛行機は空港にあります。たとえその飛行機が空港を離れていても、運転手が自分の車の外ではあるが飛行機の中のものにアクセスする能力は閉鎖である。それでおしまい。あなたが27を回すとき、 より詳細な説明 または以下の例を見てください。
これが私の飛行機の話をコードに変換する方法です。
var plane = function(defaultAirport) {
var lastAirportLeft = defaultAirport;
var car = {
driver: {
startAccessPlaneInfo: function() {
setInterval(function() {
console.log("Last airport was " + lastAirportLeft);
}, 2000);
}
}
};
car.driver.startAccessPlaneInfo();
return {
leaveTheAirport: function(airPortName) {
lastAirportLeft = airPortName;
}
}
}("Boryspil International Airport");
plane.leaveTheAirport("John F. Kennedy");
クロージャ はオブジェクトのようなものです。関数を呼び出すたびにインスタンス化されます。
JavaScriptの closure の範囲は字句解析です。つまり、 closure が属する関数内に含まれるすべてのものが、その中にあるすべての変数にアクセスできます。
変数は クロージャ に含まれています
var foo=1;
で割り当てるvar foo;
を書くだけ内側の関数(他の関数の中に含まれている関数)がvarでそれ自身のスコープ内で定義せずにそのような変数にアクセスすると、外側の closure の変数の内容を変更します。
クロージャ それを生み出した関数の実行時間を超えています。他の関数がそれらが定義されている closure/scope から外れている場合(例えば戻り値として)、それらは引き続き closure を参照します。
function example(closure) {
// define somevariable to live in the closure of example
var somevariable = 'unchanged';
return {
change_to: function(value) {
somevariable = value;
},
log: function(value) {
console.log('somevariable of closure %s is: %s',
closure, somevariable);
}
}
}
closure_one = example('one');
closure_two = example('two');
closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
私はしばらく前にクロージャを説明するブログ記事を書きました。ここで私はwhy _の観点からクロージャについて述べたことがあります。
クロージャは、関数に永続的なプライベート変数を持たせる方法です。つまり、1つの関数だけが知っている変数で、実行された前回の情報を追跡できます。
その意味で、それらは関数がプライベートな属性を持つオブジェクトのように少し動作するようにします。
全文:
次の簡単な例は、JavaScriptクロージャの主なポイントをすべて網羅しています。*
これは足し算や掛け算ができる電卓を作るファクトリーです。
function make_calculator() {
var n = 0; // this calculator stores a single number n
return {
add: function(a) {
n += a;
return n;
},
multiply: function(a) {
n *= a;
return n;
}
};
}
first_calculator = make_calculator();
second_calculator = make_calculator();
first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000
キーポイント: make_calculator
を呼び出すたびに、新しいローカル変数n
が作成されます。これは、make_calculator
が返された後も、その電卓のadd
およびmultiply
関数で引き続き使用できます。
あなたがスタックフレームに慣れているならば、これらの計算機は奇妙に思えます:make_calculator
が戻った後にどうやってn
にアクセスし続けることができますか?それらを返した関数呼び出しの後も持続します。
外部関数で宣言された変数にアクセスするadd
やmultiply
のような内部関数**はクロージャと呼ばれます。
クロージャーに必要なことはほとんどすべてです。
* 例えば、それは 別の答え で与えられた "Closures for Dummies"の記事の中のすべての点をカバーしています。それは、宣言される前に変数を使うことができることを単に示しています。クロージャーに。また、(1)関数がその引数をローカル変数にコピーする(名前付き関数の引数)、(2)数値をコピーすると新しい数値が作成されるという点を除いて、 受け入れられた答え のすべての点もカバーします。オブジェクト参照をコピーすると、同じオブジェクトに対する別の参照が得られます。これらは知っておくのもいいですが、やはり完全にクロージャとは無関係です。これは この答え の例にも非常に似ていますが、少し短く抽象的ではありません。それは この答え または このコメント の点をカバーしていません。つまり、JavaScriptはループ変数のcurrent valueをあなたの内部関数にプラグインすることを難しくしています。 「step」プラグインは、内部関数を囲み、各ループ反復で呼び出されるヘルパー関数でのみ実行できます。厳密に言うと、内部関数は、何かをプラグインするのではなく、ヘルパー関数の変数のコピーにアクセスします。クロージャーを作成するときは非常に役立ちますが、クロージャーやその機能の一部ではありません。 MLのような関数型言語ではクロージャが異なる働きをするため、さらに混乱が生じます。変数はストレージ空間ではなく値に束縛され、クロージャを理解する方法(つまり「プラグイン」の方法)を常に理解する人々の流れを提供します。 JavaScriptでは単に正しくありません。変数は常に記憶領域にバインドされ、値にはバインドされません。
** この答え が明確に指摘しているように、いくつかが入れ子になっている場合、あるいはグローバルな文脈の中でさえ、任意の外部関数。
私はそれを6歳の子供に説明したいのですが。
あなたは大人がどのように家を所有できるか知っています、そして彼らはそれを家と呼ぶのですか?お母さんが子供をもうけるとき、子供は本当に何も所有していませんね。しかし、その両親は家を所有しているので、誰かが子供に「あなたの家はどこですか?」と尋ねるときはいつでも、彼/彼女は「その家!」と答え、その親の家を指すことができます。 「閉鎖」とは、たとえそれが家を所有しているのが本当に親であるとしても、子供が常に(海外にいても)家を持っていると言うことができる能力です。
Googleの説明 は非常にうまく機能し、簡潔です。
/*
* When a function is defined in another function and it
* has access to the outer function's context even after
* the outer function returns.
*
* An important concept to learn in JavaScript.
*/
function outerFunction(someNum) {
var someString = 'Hey!';
var content = document.getElementById('content');
function innerFunction() {
content.innerHTML = someNum + ': ' + someString;
content = null; // Internet Explorer memory leak for DOM reference
}
innerFunction();
}
outerFunction(1);
* C#の質問
私は良い/悪い比較によってよりよく学ぶ傾向があります。私は、誰かが遭遇する可能性がある作業コードに続いて非作業コードを見るのが好きです。 jsFiddle を比較して、私が思い付くことができる最も簡単な説明との違いを明らかにしようとします。
console.log('CLOSURES DONE RIGHT');
var arr = [];
function createClosure(n) {
return function () {
return 'n = ' + n;
}
}
for (var index = 0; index < 10; index++) {
arr[index] = createClosure(index);
}
for (var index in arr) {
console.log(arr[index]());
}
上記のコードでは、ループのすべての繰り返しでcreateClosure(n)
が呼び出されます。変数n
に名前を付けて、それが新しい関数スコープで作成された new 変数であり、外側のスコープにバインドされているindex
と同じ変数ではないことを強調しておきます。
これにより新しいスコープが作成され、n
はそのスコープにバインドされます。つまり、反復ごとに1つずつ、10個の別々のスコープがあります。
createClosure(n)
は、そのスコープ内のnを返す関数を返します。
各スコープ内でn
はcreateClosure(n)
が呼び出されたときに持っていた値に束縛されているので、返される入れ子関数は常にcreateClosure(n)
が呼び出されたときに持っていたn
の値を返します。
console.log('CLOSURES DONE WRONG');
function createClosureArray() {
var badArr = [];
for (var index = 0; index < 10; index++) {
badArr[index] = function () {
return 'n = ' + index;
};
}
return badArr;
}
var badArr = createClosureArray();
for (var index in badArr) {
console.log(badArr[index]());
}
上記のコードでは、ループはcreateClosureArray()
関数内に移動され、関数は完成した配列を返すようになりました。一見するとより直感的に思えます。
明らかではないかもしれませんが、createClosureArray()
はループの繰り返しごとにスコープを作成するのではなく、この関数に対してスコープが1つだけ作成されると呼び出されるためです。
この関数内ではindex
という名前の変数が定義されています。ループが実行され、index
を返す関数が配列に追加されます。 index
はcreateClosureArray
関数内で定義されていることに注意してください。
createClosureArray()
関数内にはスコープが1つしかないため、index
はそのスコープ内の値にのみバインドされます。言い換えれば、ループがindex
の値を変更するたびに、そのスコープ内でそれを参照するすべてのもののためにそれを変更します。
配列に追加されたすべての関数は、最初の例のように10個の異なるスコープから10個の異なるスコープの代わりに、それが定義された親スコープからSAME index
変数を返します。その結果、10個の関数すべてが同じスコープから同じ変数を返します。
ループが終了してindex
が変更された後の終了値は10でした。したがって、配列に追加されたすべての関数は単一のindex
変数の値を返します。これは現在10に設定されています。
締め切りは正しく完了
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9間違って閉じた
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
コンピューターサイエンスでは、クロージャーとは、その関数の非ローカル名(自由変数)の参照環境を伴う関数です。
技術的には、 JavaScript 、すべての関数はクロージャーです。常に周囲のスコープで定義された変数にアクセスできます。
JavaScriptのスコープ定義構造は関数です、他の多くの言語のようなコードブロックではないので、JavaScriptのクロージャで通常意味するものは_です既に実行されている周囲の関数で定義された非ローカル変数を扱う関数。
クロージャーは、いくつかの隠されたプライベートデータを使用して関数を作成するためによく使用されます(常にそうであるとは限りません)。
var db = (function() {
// Create a hidden object, which will hold the data
// it's inaccessible from the outside.
var data = {};
// Make a function, which will provide some access to the data.
return function(key, val) {
if (val === undefined) { return data[key] } // Get
else { return data[key] = val } // Set
}
// We are calling the anonymous surrounding function,
// returning the above inner function, which is a closure.
})();
db('x') // -> undefined
db('x', 1) // Set x to 1
db('x') // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.
ems
上記の例は、一度実行された匿名関数を使用しています。しかし、そうである必要はありません。名前を付け(例:mkdb
)、後で実行し、呼び出されるたびにデータベース関数を生成します。生成されたすべての関数には、独自の非表示データベースオブジェクトがあります。クロージャーの別の使用例は、関数を返さず、異なる目的のために複数の関数を含むオブジェクトを返し、それらの関数のそれぞれが同じデータにアクセスする場合です。
どのようにクロージャが機能するのかを説明するために、対話型のJavaScriptチュートリアルをまとめました。 クロージャとは何ですか?
例を挙げましょう。
var create = function (x) {
var f = function () {
return x; // We can refer to x here!
};
return f;
};
// 'create' takes one argument, creates a function
var g = create(42);
// g is a function that takes no arguments now
var y = g();
// y is 42 here
両親がいなくなった後も、子供たちは両親と共有した秘密を常に覚えています。これがクロージャーが機能のためのものです。
JavaScript関数の秘密はプライベート変数です
var parent = function() {
var name = "Mary"; // secret
}
これを呼び出すたびに、ローカル変数 "name"が作成され、 "Mary"という名前が付けられます。そして関数が終了するたびに変数は失われ、名前は忘れられます。
ご想像のとおり、変数は関数が呼び出されるたびに再作成され、他に誰もそれらを知らないので、それらが格納される秘密の場所がなければなりません。 秘密の部屋 または stack または ローカルスコープ と呼ばれることもありますが、実際には関係ありません。私たちは彼らが記憶の中に隠されていることを知っています。
しかし、JavaScriptでは、他の関数の内部で作成された関数も、その親のローカル変数を認識し、それらが生きている限りそれらを保持できるという非常に特別なことがあります。
var parent = function() {
var name = "Mary";
var child = function(childName) {
// I can also see that "name" is "Mary"
}
}
したがって、親関数内にいる限り、秘密の場所からの秘密変数を共有する1つ以上の子関数を作成できます。
しかし、悲しいことに、子がその親関数の私用変数でもある場合、親が終了したときにもそれは消滅し、秘密も一緒に消滅します。
だから生きるためには、手遅れになる前に子供は去らなければなりません
var parent = function() {
var name = "Mary";
var child = function(childName) {
return "My name is " + childName +", child of " + name;
}
return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside
そして今、メアリーは「もう走っていない」にもかかわらず、彼女の記憶は失われず、彼女の子供はいつも彼女の名前と彼らが一緒に過ごした時に共有した他の秘密を思い出すでしょう。
あなたが子供を「アリス」と呼ぶならば、それで、彼女は答えます
child("Alice") => "My name is Alice, child of Mary"
言うことがそれだけです。
ここで答えがそんなに複雑な理由はわかりません。
クロージャーは次のとおりです。
var a = 42;
function b() { return a; }
はい。おそらく1日に何度も使用するでしょう。
クロージャが特定の問題に対処するための複雑な設計ハックであると信じる理由はありません。いいえ、クロージャーは、より高いスコープから来る変数を使用するだけです関数が宣言された(実行されていない)位置の観点から。
これでallowsでできることはもっと壮観になります。他の答えを見てください。
Dlaliberteによる最初のポイントの例:
クロージャーは、内部関数を返すときに作成されるだけではありません。実際、囲んでいる関数はまったく戻る必要はありません。代わりに、内側の関数を外側のスコープの変数に代入するか、またはそれを別の関数の引数として渡してすぐに使用できるようにすることができます。したがって、内側の関数は呼び出されるとすぐにアクセスできるため、外側の関数のクローズは、内側の関数が呼び出された時点ですでに存在している可能性があります。
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);
クロージャは、内部関数がその外部関数内の変数にアクセスする場所です。これはおそらくクロージャのために得ることができる最も簡単な一行の説明です。
私はすでにたくさんの解決策があることを知っています、しかし私はこの小さくて単純なスクリプトが概念を説明するのに役立つことができると思います:
// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
var _count = 0; // not accessible outside this function
var sequencer = function () {
return _count++;
}
return sequencer;
}
var fnext = makeSequencer();
var v0 = fnext(); // v0 = 0;
var v1 = fnext(); // v1 = 1;
var vz = fnext._count // vz = undefined
あなたは寝過ごしています、そしてあなたはダンを招待します。あなたはダンに一つのXBoxコントローラを持ってくるように言います。
ダンはポールを誘う。ダンはポールに1人のコントローラーを持って来るように頼みます。パーティーに持ち込まれたコントローラーはいくつですか?
function sleepOver(howManyControllersToBring) {
var numberOfDansControllers = howManyControllersToBring;
return function danInvitedPaul(numberOfPaulsControllers) {
var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
return totalControllers;
}
}
var howManyControllersToBring = 1;
var inviteDan = sleepOver(howManyControllersToBring);
// The only reason Paul was invited is because Dan was invited.
// So we set Paul's invitation = Dan's invitation.
var danInvitedPaul = inviteDan(howManyControllersToBring);
alert("There were " + danInvitedPaul + " controllers brought to the party.");
JavaScript関数はそれらにアクセスすることができます。
関数がその環境にアクセスする場合、その関数はクロージャです。
ここでは説明しませんが、外部関数は必須ではありません。環境内のデータにアクセスすることで、クロージャはそのデータを存続させます。外部/内部関数のサブケースでは、外部関数はローカルデータを作成して最終的に終了することができます。ただし、外部関数が終了した後に内部関数が存続する場合、内部関数は外部関数のローカルデータを保持します。生きている。
グローバル環境を使用するクロージャの例:
スタックオーバーフローのVote-UpおよびVote-Downボタンイベントが、グローバルに定義されている外部変数isVotedUpおよびisVotedDownへのアクセス権を持つvoteUp_clickおよびvoteDown_clickのクロージャとして実装されているとします。 (わかりやすくするために、私はStackOverflowのQuestion Voteボタンを参照しています。Answer Voteボタンの配列ではありません。)
ユーザーがVoteUpボタンをクリックすると、voteUp_click関数はisVotedDown == trueかどうかをチェックして、投票するか、単に投票を取り消すかを決定します。関数voteUp_clickは環境にアクセスしているのでクロージャです。
var isVotedUp = false;
var isVotedDown = false;
function voteUp_click() {
if (isVotedUp)
return;
else if (isVotedDown)
SetDownVote(false);
else
SetUpVote(true);
}
function voteDown_click() {
if (isVotedDown)
return;
else if (isVotedUp)
SetUpVote(false);
else
SetDownVote(true);
}
function SetUpVote(status) {
isVotedUp = status;
// Do some CSS stuff to Vote-Up button
}
function SetDownVote(status) {
isVotedDown = status;
// Do some CSS stuff to Vote-Down button
}
これら4つの機能はすべて、それらがすべて自分の環境にアクセスするため、クロージャです。
Closuresの作者は、クロージャを非常によく説明し、それらを必要とする理由を説明し、クロージャを理解するのに必要なLexicalEnvironmentも説明しました。
これが要約です。
変数にアクセスしても、それがローカルではない場合はどうなりますか。ここのような:
この場合、インタプリタは外側の LexicalEnvironment
オブジェクトで変数を見つけます。
このプロセスは2つのステップで構成されています。
関数が作成されると、それは現在のLexicalEnvironmentを参照する[[Scope]]という名前の隠しプロパティを取得します。
変数が読み取られても、どこにも見つからない場合は、エラーが発生します。
入れ子関数
関数を互いに入れ子にして、スコープチェーンとも呼ばれるLexicalEnvironmentのチェーンを形成することができます。
そのため、関数gは、g、a、およびfにアクセスできます。
クロージャ
入れ子関数は、外側の関数が終了した後も存続します。
LexicalEnvironmentsをマークアップする:
ご覧のとおり、this.say
はユーザーオブジェクトのプロパティなので、Userが完了した後も存続します。
あなたが覚えているなら、this.say
が作成されるとき、それは(すべての関数として)現在のLexicalEnvironmentへの内部参照this.say.[[Scope]]
を取得します。そのため、現在のUser実行のLexicalEnvironmentはメモリ内に残ります。 Userのすべての変数もそのプロパティであるため、通常どおりジャンクされることなく、慎重に保持されます。
全体的な要点は、将来内部関数が外部変数にアクセスすることを望んでいる場合、確実にそれができるようにすることです。
要約する:
これはクロージャと呼ばれます。
6歳で、現在幼児を教えている(そして正式な教育を受けていないのでコーディングが比較的初心者なので修正が必要になるでしょう)父親として、このレッスンは実践的な演習を通してうまくいくと思います。 6歳の人が閉鎖が何であるかを理解する準備ができているならば、それから彼らは自分自身で行くことができるくらい十分に老いています。私は、コードをjsfiddle.netに貼り付けて、少し説明して、それらを一人にしてユニークな曲を作ることをお勧めします。以下の説明文は、おそらく10歳にふさわしいでしょう。
function sing(person) {
var firstPart = "There was " + person + " who swallowed ";
var fly = function() {
var creature = "a fly";
var result = "Perhaps she'll die";
alert(firstPart + creature + "\n" + result);
};
var spider = function() {
var creature = "a spider";
var result = "that wiggled and jiggled and tickled inside her";
alert(firstPart + creature + "\n" + result);
};
var bird = function() {
var creature = "a bird";
var result = "How absurd!";
alert(firstPart + creature + "\n" + result);
};
var cat = function() {
var creature = "a cat";
var result = "Imagine That!";
alert(firstPart + creature + "\n" + result);
};
fly();
spider();
bird();
cat();
}
var person="an old lady";
sing(person);
命令
データ:データは事実の集まりです。それは数、言葉、測定値、観察、あるいは物事の単なる説明でさえありえます。あなたはそれに触れることはできません。あなたはそれを書き留め、それを話し、そしてそれを聞くことができます。あなたはそれを使って触れる香りを作りそしてコンピュータを使って味わうことができた。それはコードを使っているコンピュータによって役に立つようにすることができます。
コード:上記のすべての記述はコードと呼ばれます。 JavaScriptで書かれています。
JAVASCRIPT:JavaScriptは言語です。英語、フランス語、中国語などが言語です。コンピュータや他の電子プロセッサによって理解される言語はたくさんあります。 JavaScriptがコンピュータによって理解されるためには、インタプリタが必要です。ロシア語しか話せない教師が学校であなたのクラスを教えるようになったと想像してみてください。先生が "всесадятся"と言っても、クラスは理解できません。しかし幸いにもあなたはあなたのクラスにロシア人の生徒がいると言っています。クラスはコンピュータのようなもので、ロシアの生徒は通訳です。 JavaScriptの場合、最も一般的なインタプリタはブラウザと呼ばれます。
ブラウザ:あなたがウェブサイトを訪問するためにあなたがコンピュータ、タブレットまたは電話でインターネットに接続するとき、あなたはブラウザを使います。あなたが知っているかもしれない例は、Internet Explorer、Chrome、FirefoxとSafariです。ブラウザはJavaScriptを理解して、必要なことをコンピュータに伝えることができます。 JavaScriptの命令は関数と呼ばれます。
機能:JavaScriptの機能はファクトリのようなものです。内部には1台の機械しかない小さな工場かもしれません。あるいは、それは多くの他の小さな工場を含み、それぞれが異なる仕事をしている多くの機械を持っているかもしれません。実生活の洋服工場では、大量の布と糸のボビンが入っていて、Tシャツとジーンズが出ていることがあります。私たちのJavaScriptファクトリーはデータを処理するだけです、それは縫うこと、穴を開けることまたは金属を溶かすことはできません。私たちのJavaScriptファクトリーでは、データが入ってきてデータが出てきます。
これらすべてのデータは少し退屈に聞こえますが、本当にとてもクールです。ロボットに夕食のために何を作るべきかを伝える機能があるかもしれません。私はあなたとあなたの友人を私の家に招待したとしましょう。あなたは鶏の脚が一番好きです、私はソーセージが好きです、あなたの友人はあなたが望むものを常に望みます、そして私の友人は肉を食べません。
買い物に行く時間がないので、機能を決定するには冷蔵庫に何があるかを知る必要があります。それぞれの材料は異なる調理時間を持ちます、そして我々はすべてが同時にロボットによって熱い提供されることを望みます。私たちは自分の好きなものについてのデータを関数に提供する必要があります。関数は冷蔵庫と「話し合い」、そしてロボットを制御することができます。
関数には通常、名前、括弧、中括弧があります。このような:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
/*...*/
と//
はブラウザによるコードの読み込みを停止することに注意してください。
名前:あなたが望むどんなWordについてでも関数を呼び出すことができます。例 "cookMeal"は、2つの単語をつなぎ合わせて2番目の単語の先頭に大文字を付けるのが一般的です - ただし、これは必須ではありません。それはそれの中にスペースを持つことはできません、そしてそれはそれ自身で数になることはできません。
親子:「括弧」または()
は、JavaScript関数のファクトリーのドアにあるレターボックス、またはファクトリーに情報のパケットを送信するための路上のポストボックスです。ポストボックスになどのマークが付いていることがあります(例:cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
)。
BRACES:この{}
のように見える "中括弧"は、私たちの工場の色付きの窓です。工場の内側からは見えますが、外側からは見えません。
上記の長いコード例
私たちのコードはWord関数で始まっているので、それが1つであることがわかります。それから関数の名前sing - それは私自身の関数の説明です。次に括弧()を付けます。括弧は関数のために常にあります。時々それらは空で、そして時々彼らは何かを持っています。これはWordを持っています:(person)
。この後、この{
のようなブレースがあります。これは、関数sing()の始まりを示します。これはsing()の終わりを示す}
のようなパートナーを持っています
function sing(person) { /* STUFF INSIDE THE FUNCTION */ }
そのため、この関数は歌と関係があり、人に関するデータが必要な場合があります。そのデータを使って何かをするための指示が内部にあります。
さて、関数sing()の後、コードの終わり近くに行があります。
var person="an old lady";
可変:文字varは「変数」を表します。変数は封筒のようなものです。外側には、この封筒は「人」と記されています。内側には、私たちの機能が必要とする情報が入った紙切れが入っています。いくつかの文字とスペースは、「老婦人」というフレーズを作る文字列(文字列と呼ばれます)のようにつながっています。私たちのエンベロープは、数(整数と呼ばれる)、命令(関数と呼ばれる)、リスト(配列と呼ばれる)のような他の種類のものを含むことができます。この変数はすべての括弧{}
の外側に書かれており、括弧の内側にいると色付きの窓を通して見ることができるので、この変数はコード内のどこからでも見ることができます。これを「グローバル変数」と呼びます。
GLOBAL VARIABLE:personはグローバル変数です。つまり、値を「老婦人」から「若い男」に変更すると、人になります。 あなたがそれを再び変更することを決心するまで、そしてコードの他のどの関数でもそれが若い男性であることがわかることができるまで、若い男性であり続けるでしょう。を押す F12 ボタンをクリックするか、ブラウザの開発者コンソールを開くには[オプション]設定を見て、「person」と入力してこの値を確認します。変更するにはperson="a young man"
を入力し、変更されたことを確認するにはもう一度「person」と入力します。
この後私達はラインを持っています
sing(person);
この行はあたかも犬を呼んでいるかのように関数を呼んでいます。
「さあ歌いなさい、さあ、そして人を得なさい!」
ブラウザがJavaScriptコードを読み込んでこの行に到達すると、ブラウザは機能を開始します。ブラウザに実行に必要なすべての情報が含まれていることを確認するために、最後に行を追加しました。
関数はアクションを定義します - 主な機能は歌についてです。それはfirstPartと呼ばれる変数を含みます。それは歌のそれぞれの節に当てはまる人についての歌唱に当てはまります: "ありました" +人+ "誰が飲み込んだ"。コンソールにfirstPartと入力すると、変数が関数内でロックされているため、答えが表示されません。ブラウザは、波かっこの色付きウィンドウの内側を見ることができません。 。
クロージャ:クロージャは、bigsing()関数の中にある小さい関数です。大きな工場内の小さな工場。それらはそれぞれ独自のブレースを持っています。つまり、それらの内側の変数は外側からは見えません。それが、変数の名前(クリーチャーと結果)がクロージャ内では異なる値で繰り返される理由です。これらの変数名をコンソールウィンドウに入力しても、2層の色付きウィンドウに隠れているため、その値は取得できません。
sing()関数の変数firstPartは、色の付いたウィンドウから確認できるので、すべてのクロージャーはそれを知っています。
クロージャーの後に行が来る
fly();
spider();
bird();
cat();
Sing()関数は、与えられた順序でこれらの各関数を呼び出します。その後、sing()関数の仕事が行われます。
さて、6歳の子供と話して、私はおそらく以下の協会を使用するでしょう。
想像してみてください - あなたは家の中であなたの弟や姉妹と遊んでいます、そしてあなたはあなたの玩具と一緒に動き回りそしてあなたの兄の部屋にそれらのいくつかを持ってきました。しばらくしてあなたの兄弟が学校から戻ってきて自分の部屋に行き、彼はその中に鍵をかけたので、今やあなたはもう直接そこに残っているおもちゃにアクセスすることができませんでした。しかし、あなたはドアをノックして、そのおもちゃをあなたの兄弟に頼むことができました。これは、おもちゃの クロージャ と呼ばれます。あなたの兄弟はあなたのためにそれを作り上げました、そして、彼は今外側 scope にいます。
ドアがドラフトや誰の内側にも閉じ込められておらず(一般的な機能の実行)、局所的な火災が発生して部屋を焼失し(ガベージコレクタ:D)、その後新しい部屋が建てられたときそこに別のおもちゃ(新しい関数インスタンス)がありますが、最初の部屋のインスタンスに残っていたのと同じおもちゃは絶対に手に入れないでください。
発達した子供のために私は次のようなものを置くでしょう。それは完璧ではありませんが、それはそれが何であるかについてあなたに感じさせます:
function playingInBrothersRoom (withToys) {
// We closure toys which we played in the brother's room. When he come back and lock the door
// your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
var closureToys = withToys || [],
returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.
var brotherGivesToyBack = function (toy) {
// New request. There is not yet closureToys on brother's hand yet. Give him a time.
returnToy = null;
if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.
for ( countIt = closureToys.length; countIt; countIt--) {
if (closureToys[countIt - 1] == toy) {
returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
break;
}
}
returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
}
else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
returnToy = 'Behold! ' + closureToys.join(', ') + '.';
closureToys = [];
}
else {
returnToy = 'Hey, lil shrimp, I gave you everything!';
}
console.log(returnToy);
}
return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
askBrotherForClosuredToy = playingInBrothersRoom(toys);
// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined
// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there
お分かりのように、部屋に残っているおもちゃは兄弟を通してまだアクセス可能で、部屋がロックされていても関係ありません。これは jsbin です。
6歳の子供のための答え(彼が関数とは何か、変数とは何か、そしてデータとは何かを知っていると仮定します):
関数はデータを返すことができます。関数から返すことができるデータの一種は他の関数です。その新しい関数が返されても、それを作成した関数で使用されていたすべての変数と引数は消えません。代わりに、その親関数は「閉じます」。言い換えれば、その中を見て、それが返した関数を除いてそれが使用した変数を見ることはできません。その新しい関数はそれを作成した関数の中を振り返ってその中のデータを見る特別な能力を持っています。
function the_closure() {
var x = 4;
return function () {
return x; // Here, we look back inside the_closure for the value of x
}
}
var myFn = the_closure();
myFn(); //=> 4
それを説明するためのもう1つの本当に簡単な方法は、範囲の観点からです。
大きなスコープの中に小さなスコープを作成するときはいつでも、小さなスコープは常に大きなスコープ内にあるものを見ることができます。
JavaScriptの関数は(C言語の場合のように)単なる命令セットへの参照ではありませんが、それが使用するすべての非局所変数(キャプチャー変数)への参照で構成される隠しデータ構造も含みます。このようなツーピースの機能はクロージャと呼ばれます。 JavaScriptのすべての関数はクロージャと見なすことができます。
クロージャは状態を持つ関数です。 "this"も関数の状態を提供するという意味で "this"と多少似ていますが、functionと "this"は別のオブジェクトです( "this"は単なる派手なパラメータであり、永続的にバインドする唯一の方法です)。機能はクロージャを作成することです)。 "this"と関数は常に別々に存在しますが、関数はそのクロージャから切り離すことはできず、言語はキャプチャされた変数にアクセスする手段を提供しません。
レキシカルにネストされた関数によって参照されるこれらすべての外部変数は実際にはそのレキシカルに囲まれた関数のチェーン内のローカル変数であり(グローバル変数はあるルート関数のローカル変数と見なすことができます)そのローカル変数は、ネストされた関数を返す(またはコールバックとして登録するなどの別の方法でそれを転送する)関数の実行ごとに(その実行を表す独自の潜在的に一意の参照される非局所変数のセットで)作成されるコンテキスト)。
また、JavaScriptのローカル変数はスタックフレーム上ではなくヒープ上に作成され、誰も参照していない場合にのみ破棄されることを理解する必要があります。関数が戻ると、そのローカル変数への参照は減分されますが、現在の実行中にそれらがクロージャの一部になり、その語彙的にネストされた関数によって参照されている場合これらのネストした関数は返されたか、そうでなければ何らかの外部コードに転送されました。
例:
function foo (initValue) {
//This variable is not destroyed when the foo function exits.
//It is 'captured' by the two nested functions returned below.
var value = initValue;
//Note that the two returned functions are created right now.
//If the foo function is called again, it will return
//new functions referencing a different 'value' variable.
return {
getValue: function () { return value; },
setValue: function (newValue) { value = newValue; }
}
}
function bar () {
//foo sets its local variable 'value' to 5 and returns an object with
//two functions still referencing that local variable
var obj = foo(5);
//Extracting functions just to show that no 'this' is involved here
var getValue = obj.getValue;
var setValue = obj.setValue;
alert(getValue()); //Displays 5
setValue(10);
alert(getValue()); //Displays 10
//At this point getValue and setValue functions are destroyed
//(in reality they are destroyed at the next iteration of the garbage collector).
//The local variable 'value' in the foo is no longer referenced by
//anything and is destroyed too.
}
bar();
おそらく、6歳児の最も早熟なことを除いて、多少の余裕はありますが、JavaScriptでクロージャの概念を作成するのに役立ったいくつかの例が私には役立ちます。
クロージャーは、他の関数のスコープ(その変数と関数)にアクセスできる関数です。クロージャを作成する最も簡単な方法は、関数内に関数を使用することです。その理由は、JavaScriptでは、関数は常にそれを含む関数のスコープにアクセスできるからです。
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
innerFunction();
}
outerFunction();
ALERT:サル
上記の例では、outerFunctionが呼び出され、これが次にinnerFunctionを呼び出します。 outerVarがinnerFunctionでどのように利用可能であるかに注意してください。これは、outerVarの値を正しくアラートしていることによって証明されます。
今度は次のことを検討してください。
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
ALERT:サル
referenceToInnerFunctionはouterFunction()に設定されています。これは単にinnerFunctionへの参照を返します。 referenceToInnerFunctionが呼び出されると、outerVarが返されます。また、上記のように、これは、innerFunctionが、outerFunctionの変数であるouterVarにアクセスできることを示しています。さらに、outerFunctionが実行を終了した後もこのアクセスを保持することに注意することは興味深いです。
そして、ここで物事が本当におもしろくなるところです。もし我々がouterFunctionを取り除くために、例えばそれをnullに設定すると、あなたはreferenceToInnerFunctionがouterVarの値へのアクセスを失うと考えるかもしれない。しかし、そうではありません。
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
outerFunction = null;
alert(referenceToInnerFunction());
アラート:猿アラート:猿
しかし、これはどうですか? outerFunctionがnullに設定されたので、referenceToInnerFunctionはまだouterVarの値をどのようにして知ることができますか?
それでもreferenceToInnerFunctionがouterVarの値にアクセスできるのは、innerFunctionをouterFunctionの内側に配置することによってクロージャが最初に作成されたときに、innerFunctionがそのスコープチェーンにそのouterFunctionのスコープ(その変数と関数)への参照を追加したためです。これが意味するのは、innerFunctionがouterVarを含むouterFunctionのすべての変数へのポインタまたは参照を持っているということです。したがって、outerFunctionの実行が終了したとき、または削除されたかnullに設定されたときでも、outerVarのようにスコープ内の変数は、innerFunctionに返された未解決の参照のため、メモリ内で固定されます。 referenceToInnerFunction。 outerVarとそれ以外のouterFunctionの変数をメモリから本当に解放するには、referenceToInnerFunctionをnullに設定するなどして、これらの未解決の参照を削除する必要があります。
////////// //////////
注意すべきクロージャーに関する他の2つのこと。まず、クロージャーは常にそれを含む関数の最後の値にアクセスできます。
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
outerVar = "gorilla";
innerFunction();
}
outerFunction();
ALERT:ゴリラ
第二に、クロージャが作成されるとき、それはそれを囲む関数のすべての変数と関数への参照を保持します。選択して選択することはできません。しかし、クロージャはメモリを大量に消費する可能性があるため、慎重に、または少なくとも慎重に使用する必要があります。包含関数の実行が終了した後も、多くの変数をメモリに保持できます。
Mozilla Closuresページ を指すだけです。これは、私が見つけたクロージャーの基本と実用的な使用法についての最良かつ最も簡潔かつ簡単な説明です。 JavaScriptを学習している人に強くお勧めします。
はい、6歳の方にもお勧めします-6歳の方が閉鎖について学んでいるなら、簡潔で簡単な説明を理解する準備ができているのは論理的です記事で提供されます。
私は短い説明を信じるので、下の画像を見てください。
function f1()
..>ライトレッドボックス
function f2()
..>赤い小箱
ここにはf1()
とf2()
の2つの関数があります。 f2()はf1()の内側にあります。 f1()には変数var x = 10
があります。
関数f1()
を呼び出すとき、f2()
はvar x = 10
の値にアクセスできます。
これがコードです:
function f1() {
var x=10;
function f2() {
console.log(x)
}
return f2
}
f1()
ここで呼び出すf1()
:
クロージャーは、親関数が閉じた後でも、親スコープにアクセスできる関数です。
だから基本的にクロージャは他の関数の関数です。子関数のように言うことができます。
クロージャーは、外側の(囲んでいる)関数の変数、つまりスコープチェーンにアクセスできる内側の関数です。クロージャには3つのスコープチェーンがあります。それは、それ自身のスコープ(中括弧内に定義された変数)へのアクセス、外部関数の変数へのアクセス、およびグローバル変数へのアクセスを持ちます。
内部関数は、外部関数の変数だけでなく、外部関数のパラメータにもアクセスできます。ただし、内側の関数は外側の関数の引数を直接呼び出すことはできませんが、外側の関数のargumentsオブジェクトを呼び出すことはできません。
関数を他の関数の中に追加することによってクロージャを作成します。
また、それはAngular
、Node.js
およびjQuery
を含む多くの有名なフレームワークで使われている非常に便利なメソッドです。
クロージャはNode.jsで広く使われています。それらはNode.jsの非同期のノンブロッキングアーキテクチャの主力です。クロージャはjQueryや、あなたが読んだほとんどすべてのJavaScriptコードでも頻繁に使われています。
しかし、実際のコーディングでクロージャはどのように見えるのでしょうか。この簡単なサンプルコードを見てください。
function showName(firstName, lastName) {
var nameIntro = "Your name is ";
// this inner function has access to the outer function's variables, including the parameter
function makeFullName() {
return nameIntro + firstName + " " + lastName;
}
return makeFullName();
}
console.log(showName("Michael", "Jackson")); // Your name is Michael Jackson
また、これはjQueryの古典的なクロージャー方法であり、すべてのJavaScriptおよびjQuery開発者がそれを多用していました。
$(function() {
var selections = [];
$(".niners").click(function() { // this closure has access to the selections variable
selections.Push(this.prop("name")); // update the selections variable in the outer function's scope
});
});
しかし、なぜクロージャを使うのですか?実際のプログラミングでそれを使うと?クロージャの実用的な使用は何ですか?下記はMDNによる良い説明と例です。
実用的な閉鎖
クロージャは、あるデータ(字句環境)をそのデータを操作する関数に関連付けることを可能にするので便利です。これは明らかにオブジェクト指向プログラミングに似ています。オブジェクト指向プログラミングでは、あるデータ(オブジェクトのプロパティ)を1つ以上のメソッドに関連付けることができます。
したがって、通常は単一のメソッドだけでオブジェクトを使用する可能性がある場所であればどこでもクロージャを使用できます。
あなたがこれをしたいかもしれない状況はウェブ上で特に一般的です。フロントエンドJavaScriptで記述するコードの大部分はイベントベースです - 何らかの動作を定義してから、それをユーザーがトリガーするイベント(クリックやキー入力など)にアタッチします。私たちのコードは一般的にコールバックとして添付されています:イベントに応答して実行される単一の関数です。
たとえば、テキストサイズを調整するボタンをページに追加したいとします。これを行う1つの方法は、body要素のfont-sizeをピクセル単位で指定してから、相対em単位を使用してページ上の他の要素(ヘッダーなど)のサイズを設定することです。
以下のコードを読み、コードを実行して、セクションごとに個別の機能を簡単に作成するためのクロージャーの効果を確認してください。
//javascript
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
/*css*/
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
<!--html><!-->
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
クロージャについてのさらなる研究のために、私はあなたがMDNによってこのページを訪問することを勧めます: https://developer.mozilla.org/en/docs/Web/JavaScript/Closures
6歳ですか?
あなたとあなたの家族はアンビルの神話の町に住んでいます。あなたは隣に住んでいる友達がいるので、あなたは彼らを呼んで出てきて遊ぶように頼みます。あなたがダイヤルする:
000001(ジャムハウス)
一ヵ月後、あなたとあなたの家族はアンビルから次の町に移動しますが、あなたとあなたの友人はまだ連絡を取り合っています。適切な数:
001 000001(annVille.jamiesHouse)
それから1年後、あなたの両親は全く新しい国に引っ越しますが、あなたとあなたの友人はまだ連絡を取り合っています。
01 001 000001(myOldCountry.annVille.jamiesHouse)
不思議なことに、あなたの新しい国に引っ越した後、あなたとあなたの家族はちょうどAnn Villeと呼ばれる新しい町に引っ越すように偶然にそうします...そしてあなたはJamieと呼ばれる新しい人と友達になるために偶然にそうです...コール...
000001(ジャムハウス)
不気味な...
実際に不気味です、あなたがそれについてあなたの古い国からのジェイミーに話すこと...あなたはそれについてよく笑います。だからある日、あなたとあなたの家族は古い国に戻って休暇を取ります。あなたはあなたの旧市街(Ann Ville)を訪問し、そしてJamieを訪問しに行きます...
02 001 000001(myNewCountry.annVille.jamiesHouse)
ご意見?
さらに、現代の6歳の忍耐力についての質問がたくさんあります...
JavaScriptでは、変数や引数が内部関数で利用可能であり、外部関数が返された後も有効であるというJavaScriptのクロージャは素晴らしいものです。
function getFullName(a, b) {
return a + b;
}
function makeFullName(fn) {
return function(firstName) {
return function(secondName) {
return fn(firstName, secondName);
}
}
}
makeFullName(getFullName)("stack")("overflow"); // Stackoverflow
これが簡単なリアルタイムのシナリオです。それを読み通すだけで、あなたは私たちがここでどのように閉鎖を使ったかを理解するでしょう(座席番号がどのように変化しているか見てください)。
前に説明した他のすべての例も、この概念を理解するのに非常に役立ちます。
function movieBooking(movieName) {
var bookedSeatCount = 0;
return function(name) {
++bookedSeatCount ;
alert( name + " - " + movieName + ", Seat - " + bookedSeatCount )
};
};
var MI1 = movieBooking("Mission Impossible 1 ");
var MI2 = movieBooking("Mission Impossible 2 ");
MI1("Mayur");
// alert
// Mayur - Mission Impossible 1, Seat - 1
MI1("Raju");
// alert
// Raju - Mission Impossible 1, Seat - 2
MI2("Priyanka");
// alert
// Raja - Mission Impossible 2, Seat - 1
クロージャはJavaScriptプログラマがより良いコードを書くことを可能にします。クリエイティブで表現力豊かで簡潔。私たちはJavaScriptでしばしばクロージャを使います、そして私たちのJavaScriptの経験に関係なく、私たちは間違いなく何度もそれらに遭遇します。クロージャは複雑に見えるかもしれませんが、これを読んだ後は、クロージャがはるかに理解しやすくなり、日々のJavaScriptプログラミングタスクにとってより魅力的になるでしょう。
クロージャを理解するにはJavaScriptの変数スコープを理解する必要があるため、先に進む前に JavaScript変数スコープ に慣れておく必要があります。
クロージャーは、外側の(囲んでいる)関数の変数、つまりスコープチェーンにアクセスできる内側の関数です。クロージャには3つのスコープチェーンがあります。それは、それ自身のスコープ(中括弧内に定義された変数)へのアクセス、外部関数の変数へのアクセス、およびグローバル変数へのアクセスを持ちます。
内部関数は、外部関数の変数だけでなく、外部関数のパラメータにもアクセスできます。ただし、内側の関数は外側の関数の引数を直接呼び出すことはできませんが、外側の関数のargumentsオブジェクトを呼び出すことはできません。
関数を他の関数の中に追加することによってクロージャを作成します。
JavaScriptでのクロージャの基本的な例:
function showName (firstName, lastName) {
var nameIntro = "Your name is ";
// this inner function has access to the outer function's variables, including the parameter
function makeFullName () {
return nameIntro + firstName + " " + lastName;
}
return makeFullName ();
}
showName ("Michael", "Jackson"); // Your name is Michael Jackson
クロージャはNode.jsで広く使われています。それらはNode.jsの非同期のノンブロッキングアーキテクチャの主力です。クロージャはjQueryや、あなたが読んだほとんどすべてのJavaScriptコードでも頻繁に使われています。
古典的なjQueryによるクロージャの例:
$(function() {
var selections = [];
$(".niners").click(function() { // this closure has access to the selections variable
selections.Push (this.prop("name")); // update the selections variable in the outer function's scope
});
});
1.クロージャーは、外部関数が戻った後でも、外部関数の変数にアクセスできます。
クロージャーで最も重要で目立った機能の1つは、外側の関数が戻った後も内側の関数が外側の関数の変数にアクセスできることです。うん、あなたはそれを正しく読んだ。 JavaScriptの関数が実行されると、それらが作成されたときに有効だったのと同じスコープチェーンを使用します。つまり、外部関数が戻った後でも、内部関数は外部関数の変数にアクセスできます。したがって、プログラムの後半で内部関数を呼び出すことができます。この例は次のことを示しています。
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
2.クロージャは外部関数の変数への参照を格納します:
実際の値は保存されません。クロージャは、クロージャが呼び出される前に外部関数の変数の値が変化するとさらに面白くなります。そしてこの強力な機能は、Douglas Crockfordによって最初に示されたプライベート変数の例のように、創造的な方法で利用することができます。
function celebrityID () {
var celebrityID = 999;
// We are returning an object with some inner functions
// All the inner functions have access to the outer function's variables
return {
getID: function () {
// This inner function will return the UPDATED celebrityID variable
// It will return the current value of celebrityID, even after the changeTheID function changes it
return celebrityID;
},
setID: function (theNewID) {
// This inner function will change the outer function's variable anytime
celebrityID = theNewID;
}
}
}
var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.
mjID.getID(); // 999
mjID.setID(567); // Changes the outer function's variable
mjID.getID(); // 567: It returns the updated celebrityId variable
3.クロージャが消えた
クロージャは外部関数の変数の更新された値にアクセスできるため、外部関数の変数がforループで変更されたときにもバグにつながる可能性があります。したがって:
// This example is explained in detail below (just after this code box).
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function () {
return uniqueID + i;
}
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id()); // 103
これが私が与えることができる最も禅の答えです:
このコードに何を期待しますか?実行する前にコメントで教えてください。私は興味がある!
function foo() {
var i = 1;
return function() {
console.log(i++);
}
}
var bar = foo();
bar();
bar();
bar();
var baz = foo();
baz();
baz();
baz();
ブラウザでコンソールを開きます(Ctrl + Shift + I または F12うまくいけば)コードを貼り付けてヒット Enter。
このコードがあなたが期待したもの(JavaScriptの初心者 - 最後の "undefined"を無視する)を印刷するなら、あなたはすでに無意味な理解を持っています。 言い換えれば、変数i
は内部関数の一部ですインスタンスのクロージャ。
このコードがfoo()
の内部関数のインスタンスをbar
とbaz
に入れ、それらの変数を介してそれらを呼び出すことを理解すれば、他に何も驚かなかったので、このようにしました。
しかし、私が間違っていてコンソールの出力があなたを驚かせたのなら、教えてください!
クロージャーについて考えるほど、2段階のプロセスとして見ることが多くなります。 init - action
init: pass first what's needed...
action: in order to achieve something for later execution.
6歳になるまで、私は実用的な側面の閉鎖を強調したいと思います。
Daddy: Listen. Could you bring mum some milk (2).
Tom: No problem.
Daddy: Take a look at the map that Daddy has just made: mum is there and daddy is here.
Daddy: But get ready first. And bring the map with you (1), it may come in handy
Daddy: Then off you go (3). Ok?
Tom: A piece of cake!
例 :母乳にミルクを持参(= action)。最初に準備をして地図を表示してください(= init)
function getReady(map) {
var cleverBoy = 'I examine the ' + map;
return function(what, who) {
return 'I bring ' + what + ' to ' + who + 'because + ' cleverBoy; //I can access the map
}
}
var offYouGo = getReady('daddy-map');
offYouGo('milk', 'mum');
非常に重要な情報(地図)を持ってくると、他の同様の操作を実行するのに十分な知識があるためです。
offYouGo('potatoes', 'great mum');
開発者にとって、クロージャーと _ oop _ の間で平行関係を作ることにします。 initフェーズ は、従来のOO言語でコンストラクタに引数を渡すのと似ています。 アクションフェーズ は、最終的にあなたが望むものを達成するために呼び出すメソッドです。そしてメソッドはクロージャと呼ばれるメカニズムを使ってこれらのinit引数にアクセスします。
OOとクロージャの並列性を説明する私の別の答えを見てください。
function person(name, age){
var name = name;
var age = age;
function introduce(){
alert("My name is "+name+", and I'm "+age);
}
return introduce;
}
var a = person("Jack",12);
var b = person("Matt",14);
関数person
が呼び出されるたびに、新しいクロージャが作成されます。変数a
とb
は同じintroduce
関数を持ちますが、異なるクロージャにリンクされています。そしてその閉鎖は関数person
が実行を終えた後でさえもまだ存在します。
a(); //My name is Jack, and I'm 12
b(); //My name is Matt, and I'm 14
抽象的なクロージャは、このようなものに表すことができます。
closure a = {
name: "Jack",
age: 12,
call: function introduce(){
alert("My name is "+name+", and I'm "+age);
}
}
closure b = {
name: "Matt",
age: 14,
call: function introduce(){
alert("My name is "+name+", and I'm "+age);
}
}
あなたが他の言語のclass
がどのように機能するかを知っていると仮定して、私は類推します。
のように考える
function
としてのJavaScript constructor
local variables
をinstance properties
としてproperties
は非公開ですinner functions
をinstance methods
としてfunction
が呼び出されるたびに
object
が作成されます。"properties"
にアクセスできます。JavaScriptクロージャの美しい定義がインターネット上に多数存在していますが、私は自分のお気に入りのクロージャの定義を使って私の6歳の友人に説明を始めようとしています。
クロージャとは
クロージャーは、外側の(囲んでいる)関数の変数、つまりスコープチェーンにアクセスできる内側の関数です。クロージャには3つのスコープチェーンがあります。それは、それ自身のスコープ(中括弧内に定義された変数)へのアクセス、外部関数の変数へのアクセス、およびグローバル変数へのアクセスを持ちます。
クロージャーは関数のローカル変数で、関数が戻った後も存続します。
クロージャは独立(自由)変数を参照する関数です。言い換えると、クロージャで定義された関数は、それが作成された環境を「記憶」します。
クロージャはスコープの概念を拡張したものです。クロージャでは、関数はその関数が作成されたスコープ内で利用可能だった変数にアクセスできます。
クロージャーは、関数が戻ったときに解放されないスタックフレームです。 (まるで 'スタックフレーム'がスタック上にあるのではなくmallocされているかのように!)
Javaなどの言語では、メソッドをprivateとして宣言する機能が提供されています。つまり、同じクラス内の他のメソッドからしか呼び出せません。 JavaScriptはこれを行うためのネイティブな方法を提供しませんが、クロージャを使用してプライベートメソッドをエミュレートすることは可能です。
「クロージャー」とは、自由変数をそれらの変数を束縛する(式を「閉じる」)環境と一緒に持つことができる式(通常は関数)です。
クロージャは、懸念を非常にきれいに分離することを可能にする抽象化メカニズムです。
クロージャの使用:
クロージャは、インタフェースを明らかにしながら機能の実装を隠すのに役立ちます。
クロージャを使用して、JavaScriptでカプセル化の概念をエミュレートできます。
クロージャは jQuery および Node.js で広く使用されています。
オブジェクトリテラルは確かに作成しやすく、データを格納するのに便利ですが、大規模なWebアプリケーションで静的なシングルトン名前空間を作成するには、クロージャを使用することをお勧めします。
クロージャの例:
私の6歳の友人が彼の小学校でごく最近になって足し算を知るようになったと仮定すると、私は2つの数字を加えるこの例がクロージャーを学ぶ6歳の子供にとって最も簡単で適切であると感じました。
例1:関数を返すことでここで閉じられます。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
例2:オブジェクトリテラルを返すことで、ここで閉じられます。
function makeAdder(x) {
return {
add: function(y){
return x + y;
}
}
}
var add5 = makeAdder(5);
console.log(add5.add(2));//7
var add10 = makeAdder(10);
console.log(add10.add(2));//12
例3:jQueryのクロージャー
$(function(){
var name="Closure is easy";
$('div').click(function(){
$('p').text(name);
});
});
便利なリンク:
上記のリンクのおかげで、私はクロージャをよりよく理解し説明することができます。
クロージャを理解するには、プログラムにアクセスして、実行時のように文字通り実行する必要があります。この単純なコードを見てみましょう。
JavaScriptは2段階でコードを実行します。
JavaScriptがコンパイル段階を通過するとき、それは変数と関数の宣言を抽出します。これは巻き上げと呼ばれます。この段階で検出された関数は、ラムダとも呼ばれるメモリ内のテキストBLOBとして保存されます。コンパイル後、JavaScriptはすべての値を割り当てて関数を実行する実行段階に入ります。関数を実行するには、ヒープからメモリを割り当て、関数のコンパイルと実行の段階を繰り返すことで実行コンテキストを準備します。このメモリ領域を関数のスコープと呼びます。実行開始時にはグローバルスコープがあります。スコープはクロージャを理解するための鍵です。
この例では、最初に変数a
が定義され、次にコンパイル段階でf
が定義されます。未宣言の変数はすべてグローバルスコープに保存されます。実行フェーズではf
が引数付きで呼び出されます。 f
のスコープが割り当てられ、それに対してコンパイルと実行のフェーズが繰り返されます。
f
の引数もこのローカルスコープに保存されます。ローカル実行コンテキストまたはスコープが作成されるときはいつでも、それはその親スコープへの参照ポインタを含みます。すべての変数アクセスはこの字句スコープチェーンに従ってその価値を見つけます。変数がローカルスコープ内に見つからない場合は、それがチェーンの後に続き、その親スコープ内にあります。これが、ローカル変数が親スコープ内の変数をオーバーライドする理由でもあります。親の範囲は、ローカルの範囲または機能のための「クロージャー」と呼ばれます。
ここでg
のスコープが設定されているとき、それはf
の親のスコープへの字句ポインタを得ました。 f
の範囲はg
のクロージャです。 JavaScriptでは、関数、オブジェクト、スコープになんらかの方法でアクセスできる場合、それらへの参照があれば、ガベージコレクトされません。そのため、myGが実行されているとき、それはクロージャであるf
のスコープへのポインタを持ちます。 f
が戻っても、このメモリ領域はガベージコレクションされません。ランタイムに関しては、これはクロージャです。
[[scope]]
参照。var data = "My Data!";
setTimeout(function() {
console.log(data); // Prints "My Data!"
}, 3000);
function makeAdder(n) {
var inc = n;
var sum = 0;
return function add() {
sum = sum + inc;
return sum;
};
}
var adder3 = makeAdder(3);
クロージャなどについての非常に興味深い話はArindam Paul - JavaScript VM内部、EventLoop、AsyncおよびScopeChainsです。
クロージャーは 関数内の関数 で、その "親"関数の変数とパラメータにアクセスできます。
例:
function showPostCard(Sender, Receiver) {
var PostCardMessage = " Happy Spring!!! Love, ";
function PreparePostCard() {
return "Dear " + Receiver + PostCardMessage + Sender;
}
return PreparePostCard();
}
showPostCard("Granny", "Olivia");
図解説明 :JavaScriptのクロージャは背後でどのように機能するのかを満たしてください。
この記事では、スコープオブジェクト(またはLexicalEnvironment
s)の割り当て方法と直感的な使用方法について説明します。この単純なスクリプトでは、
"use strict";
var foo = 1;
var bar = 2;
function myFunc() {
//-- Define local-to-function variables
var a = 1;
var b = 2;
var foo = 3;
}
//-- And then, call it:
myFunc();
トップレベルのコードを実行すると、スコープオブジェクトは次のように配置されます。
そしてmyFunc()
が呼び出されると、次のスコープチェーンができます。
スコープオブジェクトがどのように作成、使用、削除されるかを理解することは、全体像を把握し、クロージャが内部でどのように機能するのかを理解するための鍵です。
詳細については前述の記事を参照してください。
(私は6歳のことを考慮に入れていません。)
JavaScriptのように、他の関数にパラメータとして関数を渡すことができる言語(関数がファーストクラスの市民である言語)では、よく自分自身が次のようなことをしていることに気づくでしょう。
var name = 'Rafael';
var sayName = function() {
console.log(name);
};
sayName
はname
変数の定義を持っていませんが、name
の外側で定義されたsayName
の値を使用します(親スコープ内)。 ).
sayName
を別の関数のパラメータとして渡し、それがsayName
をコールバックとして呼び出すとしましょう。
functionThatTakesACallback(sayName);
ご了承ください:
sayName
はfunctionThatTakesACallback
の内側から呼び出されます(この例ではfunctionThatTakesACallback
を実装していないため、この例を想定してください)。sayName
が呼び出されると、name
変数の値を記録します。functionThatTakesACallback
はname
変数を定義していません(それは可能ですが、問題にならないので、定義しないと仮定します)。そのため、sayName
はfunctionThatTakesACallback
の内部で呼び出され、name
の内部で定義されていないfunctionThatTakesACallback
変数を参照しています。
その後どうなりますか? ReferenceError: name is not defined
?
いいえ! name
の値は クロージャ の中に取り込まれます。このクロージャは、関数に関連付けられた コンテキストと見なすことができます は、その関数が定義されている場所で利用可能だった値を保持します。
そのため、name
は、関数sayName
が呼び出される(functionThatTakesACallback
の内側の)範囲にはありませんが、sayName
はname
の値にアクセスできます。 sayName
に関連付けられているクロージャでキャプチャされました。
-
本からEloquent JavaScript:
良い精神的なモデルは、関数の値を、その本体にあるコードとそれらが作成される環境の両方に含まれていると考えることです。呼び出されると、関数本体は呼び出しが行われる環境ではなく、元の環境を見ます。
この回答は、このYouTubeビデオ Javascript Closures の概要です。そのビデオへの完全なクレジット。
クロージャは、プライベート変数の状態を維持するステートフル関数に他なりません。
通常、下の図に示すように関数を呼び出すとき。変数は使用されるスタック(実行中のRAM memory)に作成され、その後割り当て解除されます。
しかし今では、Javascriptクロージャが使用するようになる機能のこの状態を維持したいという状況があります。クロージャーは、以下のコードに示すように、戻り呼び出しを伴う関数内部の関数です。
そのため、上記のcounter関数のクロージャーコードは、次のようになります。returnステートメントを含むfunction内部の関数です。
function Counter() {
var counter = 0;
var Increment = function () {
counter++;
alert(counter);
}
return {
Increment
}
}
それで、あなたが電話をかけるならば、カウンターは増加するでしょう、言い換えれば関数呼び出しは状態を維持します。
var x = Counter(); // get the reference of the closure
x.Increment(); // Displays 1
x.Increment(); // Display 2 ( Maintains the private variables)
しかし今最大の問題は、そのようなステートフル関数の使用です。ステートフル関数は、抽象化、カプセル化、自己完結型モジュールの作成などのOOP概念を実装するための構成要素です。
そのため、カプセル化したいものは何でもプライベートとしてそれを置くことができ、パブリックに公開されるものはreturnステートメントに入れるべきです。また、これらのコンポーネントは自己完結型の独立したオブジェクトなので、グローバル変数を汚染することはありません。
OOPの原則に従うオブジェクトは、自己完結型で、抽象化に続き、カプセル化に続きます。 Javascriptが閉じていないため、これを実装するのは困難です。
次の例は、JavaScriptクロージャの簡単な例です。これはクロージャ関数で、ローカル変数xにアクセスして関数を返します。
function outer(x){
return function inner(y){
return x+y;
}
}
このような機能を呼び出します。
var add10 = outer(10);
add10(20); // The result will be 30
add10(40); // The result will be 50
var add20 = outer(20);
add20(20); // The result will be 40
add20(40); // The result will be 60
クロージャーは、多くのJavaScript開発者が常に使用しているものですが、当然のことと思います。その仕組みはそれほど複雑ではありません。それを意図的に使用する方法を理解することは は complexです。
最も単純な定義では(他の答えが指摘したように)、クロージャは基本的に他の関数の中で定義された関数です。そしてその内部関数は、外部関数の範囲内で定義された変数にアクセスできます。クロージャを使用する最も一般的な方法は、グローバルスコープで変数と関数を定義し、その関数の関数スコープでそれらの変数にアクセスすることです。
var x = 1;
function myFN() {
alert(x); //1, as opposed to undefined.
}
// Or
function a() {
var x = 1;
function b() {
alert(x); //1, as opposed to undefined.
}
b();
}
だから何?
あなたが彼らなしでどんな生活ができるかについて考えるまで、クロージャーはJavaScriptユーザーにとってそれほど特別ではありません。他の言語では、関数内で使用されている変数は、その関数が戻ったときにクリーンアップされます。上の例では、xは "nullポインタ"であり、ゲッターとセッターを設定して参照の受け渡しを開始する必要があります。 JavaScriptのようには思えませんか。強力な閉鎖をありがとう。
なぜ私は気にする必要がありますか?
あなたは本当にそれらを使用するためにクロージャを意識する必要はありません。しかし、他の人たちも指摘しているように、彼らは てこ入れされている 偽のプライベート変数を作成することができます。プライベート変数が必要になるまでは、いつものように使用してください。
個人的な ブログ投稿から :
デフォルトでは、JavaScriptはグローバルとローカルの2種類のスコープを認識します。
var a = 1;
function b(x) {
var c = 2;
return x * c;
}
上記のコードでは、変数aと関数bはコード内のどこからでも(つまりグローバルに)使用できます。変数c
は、b
関数の有効範囲内(つまりローカル)でのみ使用可能です。ほとんどのソフトウェア開発者は、特に大規模なプログラムでは、このような範囲の柔軟性の欠如に満足できません。
JavaScriptクロージャは、コンテキストと関数を結びつけることによってその問題を解決するのを助けます:
function a(x) {
return function b(y) {
return x + y;
}
}
ここで、関数a
はb
と呼ばれる関数を返します。 b
はa
内で定義されているので、a
に定義されているもの、つまりこの例ではx
に自動的にアクセスできます。 b
を宣言せずにx
がy
+ x
を返すことができるのはこのためです。
var c = a(3);
変数c
には、withパラメーター3の呼び出しの結果が代入されます。つまり、b
= 3の関数x
のインスタンスです。つまり、c
は、次の関数と等価になります。
var c = function b(y) {
return 3 + y;
}
関数b
は、そのコンテキスト内でx
= 3を記憶しています。したがって:
var d = c(4);
値3 + 4をd
、つまり7に代入します。
備考 :関数x
のインスタンスが作成された後に誰かがx
の値を変更すると(b
= 22と言う)、これもb
に反映されます。したがって、後でc
(4)を呼び出すと、22 + 4、つまり26が返されます。
クロージャは、グローバルに宣言された変数とメソッドの範囲を制限するためにも使用できます。
(function () {
var f = "Some message";
alert(f);
})();
上記は、関数に名前も引数もなく、ただちに呼び出されるクロージャです。ハイライトされたコードはグローバル変数f
を宣言し、f
の範囲をクロージャに限定します。
さて、クロージャが役に立つことができる一般的なJavaScriptの警告があります。
var a = new Array();
for (var i=0; i<2; i++) {
a[i]= function(x) { return x + i ; }
}
上記から、ほとんどの人は配列a
が次のように初期化されると仮定します。
a[0] = function (x) { return x + 0 ; }
a[1] = function (x) { return x + 1 ; }
a[2] = function (x) { return x + 2 ; }
実際には、コンテキスト内のi
の最後の値は2であるため、これはaの初期化方法です。
a[0] = function (x) { return x + 2 ; }
a[1] = function (x) { return x + 2 ; }
a[2] = function (x) { return x + 2 ; }
解決策は次のとおりです。
var a = new Array();
for (var i=0; i<2; i++) {
a[i]= function(tmp) {
return function (x) { return x + tmp ; }
} (i);
}
引数/変数tmp
は、関数インスタンスを作成するときのi
の変化する値のローカルコピーを保持します。
JavaScript:The Definitive Guide のDavid Flanagan著、第6版、2011年のO'Reillyによる8章6セクション6「Closures」。私は言い換えてみます。
関数が呼び出されると、その呼び出しのローカル変数を保持するための新しいオブジェクトが作成されます。
関数の有効範囲は、実行場所ではなく宣言場所に依存します。
ここで、内部関数が外部関数内で宣言され、その外部関数の変数を参照しているとします。さらに、外側の関数が内側の関数を関数として返すと仮定します。これで、内部関数のスコープ内にあった値に対する外部参照ができました(これには、外部関数からの値も含まれます)。
JavaScriptは、完成した外部関数から渡されることで現在の実行の範囲内にあるため、これらの値を保持します。すべての関数はクロージャですが、ここで取り上げるクロージャは、想定されているシナリオでは、返されたときに「Enclosure」(ここでは言語を正しく使用していると思います)外部関数から。私はこれが6歳の要件を満たしていないことを知っていますが、うまくいけばそれはまだ役に立ちます。
Einstein 私たちが難解なブレインストーマーのものを選び、それらを「クレイジー」にするための無駄な試みをして6歳以上になることを直接期待して言ったのではありません(さらに悪いことです)私の6歳のときは、そのような親を持ちたくないし、そのような退屈な慈善家との友好関係もないでしょう、ごめんなさい:)
とにかく、赤ん坊のために、 クロージャ は単に ハグ です、あなたが説明しようとしているどんな方法でもね:)そしてあなたがあなたの友人を抱擁するときみんなが現時点で持っています。誰かを抱きしめようとすると、自分の信頼と他人からは見えないような多くのことを彼女にやらせようとする気持ちを示してください。それは友情の行為です:)。
5〜6歳の赤ちゃんに説明する方法がよくわかりません。私はどちらも彼らが次のようなJavaScriptコードスニペットに感謝するとは思わない。
function Baby(){
this.iTrustYou = true;
}
Baby.prototype.hug = function (baby) {
var smiles = 0;
if (baby.iTrustYou) {
return function() {
smiles++;
alert(smiles);
};
}
};
var
arman = new Baby("Arman"),
morgan = new Baby("Morgana");
var hug = arman.hug(morgan);
hug();
hug();
子供だけのために:
クロージャー is hug
バグ is fly
_ kiss _ は です。 :)
あなたが6歳の子供にそれを説明したいならば、あなたは何か非常に簡単でNOコードを見つけなければなりません。
ただ彼が「オープン」であると子供に言いなさい、それは彼が何人かの他の人、彼の友人と関係を持つことができると言います。ある時点で、彼は友人(私たちは彼の友人の名前を知ることができます)を決定しました、それは閉鎖です。あなたが彼と彼の友人の写真を撮るならば、彼は彼の友情能力に対して相対的に「閉じられて」います。しかし、一般的に、彼は「オープン」です。彼の一生の間に彼は友達の多くの異なるセットを持つことになります。これらのセットの1つはクロージャです。
関数は、それが定義されているオブジェクト/関数の範囲内で実行されます。前記関数は、それが実行されている間にそれが定義されているオブジェクト/関数において定義された変数にアクセスすることができる。
そして、文字通りそれを取ります....コードが書かれるように.... P
あなたがそれをよく理解すれば、あなたはそれを簡単に説明することができます。そして最も簡単な方法は文脈からそれを抽象化することです。さておき、プログラミングもさておき。比喩的な例はそれをより良くするでしょう。
機能が壁がガラスでできている部屋であると想像しましょう、しかしそれらは尋問部屋のもののように特別なガラスです。外側からは不透明、内側からは透明です。それは他の部屋の中の部屋であることができます、そして唯一の連絡方法は電話です。
外部から電話をかけても、その中に何があるのかわかりませんが、特定の情報を提供すれば内部の人々が仕事をすることはわかっています。彼らは外側を見ることができるので、彼らはあなたに外側のものを求めてそのものに変更を加えることができますが、あなたは外側から内側のものを変えることはできません。あなたが呼んでいるその部屋の中の人々はそれが外のものであることを見るが、それがその部屋の部屋の中にあるものではないので、彼らはあなたが外からしている方法で彼らと対話する。最も内側の部屋の中の人々は多くのことを見ることができますが、最も外側の部屋の人々は最も内側の部屋の存在についてさえ知らない。
インナールームへのコールごとに、そのルームの人々はその特定のコールに関する情報を記録しています。
ルームは機能、可視性はスコープ、タスクを実行する人々はステートメント、スタッフはオブジェクトコール、電話コールはファンクションコール、コール情報は引数、コールレコードはスコープインスタンス、最も外側のルームはグローバルオブジェクトです。
クロージャーとは、親関数が既に終了した後で、内部関数がその外側の囲み関数に存在する変数を参照できるようにする手段です。
// A function that generates a new function for adding numbers.
function addGenerator( num ) {
// Return a simple function for adding two numbers
// with the first number borrowed from the generator
return function( toAdd ) {
return num + toAdd
};
}
// addFive now contains a function that takes one argument,
// adds five to it, and returns the resulting number.
var addFive = addGenerator( 5 );
// We can see here that the result of the addFive function is 9,
// when passed an argument of 4.
alert( addFive( 4 ) == 9 );
クロージャーは、親関数が閉じた後でも、親スコープにアクセスできる関数です。
var add = (function() {
var counter = 0;
return function() {
return counter += 1;
}
})();
add();
add();
add();
// The counter is now 3
説明した例:
add
には、自己呼び出し関数の戻り値が割り当てられています。あなたの町には、Mr. Coderという魔術師がJavaScriptと呼ばれる魔法の杖を使って野球の試合を始めているところがあると想像してください。
当然のことながら、各野球の試合にはまったく同じルールがあり、各試合には独自のスコアボードがあります。
当然のことながら、ある野球の試合の得点は他の試合とは完全に別のものです。
クロージャーはMr.Coderが彼のすべての魔法の野球の試合の得点を別々に保つ特別な方法です。
ピノキオ:1883年の閉鎖(JavaScriptの前の1世紀以上)
私はそれが最も良い冒険で6歳に説明されることができると思います...ピノキオが特大なドッグフィッシュによって飲み込まれている ピノキオの冒険 の一部...
var tellStoryOfPinocchio = function(original) {
// Prepare for exciting things to happen
var pinocchioFindsMisterGeppetto;
var happyEnding;
// The story starts where Pinocchio searches for his 'father'
var pinocchio = {
name: 'Pinocchio',
location: 'in the sea',
noseLength: 2
};
// Is it a dog... is it a fish...
// The dogfish appears, however there is no such concept as the belly
// of the monster, there is just a monster...
var terribleDogfish = {
swallowWhole: function(snack) {
// The swallowing of Pinocchio introduces a new environment (for the
// things happening inside it)...
// The BELLY closure... with all of its guts and attributes
var mysteriousLightLocation = 'at Gepetto\'s ship';
// Yes: in my version of the story the monsters mouth is directly
// connected to its belly... This might explain the low ratings
// I had for biology...
var mouthLocation = 'in the monsters mouth and then outside';
var puppet = snack;
puppet.location = 'inside the belly';
alert(snack.name + ' is swallowed by the terrible dogfish...');
// Being inside the belly, Pinocchio can now experience new adventures inside it
pinocchioFindsMisterGeppetto = function() {
// The event of Pinocchio finding Mister Geppetto happens inside the
// belly and so it makes sence that it refers to the things inside
// the belly (closure) like the mysterious light and of course the
// hero Pinocchio himself!
alert(puppet.name + ' sees a mysterious light (also in the belly of the dogfish) in the distance and swims to it to find Mister Geppetto! He survived on ship supplies for two years after being swallowed himself. ');
puppet.location = mysteriousLightLocation;
alert(puppet.name + ' tells Mister Geppetto he missed him every single day! ');
puppet.noseLength++;
}
happyEnding = function() {
// The escape of Pinocchio and Mister Geppetto happens inside the belly:
// it refers to Pinocchio and the mouth of the beast.
alert('After finding Mister Gepetto, ' + puppet.name + ' and Mister Gepetto travel to the mouth of the monster.');
alert('The monster sleeps with its mouth open above the surface of the water. They escape through its mouth. ');
puppet.location = mouthLocation;
if (original) {
alert(puppet.name + ' is eventually hanged for his innumerable faults. ');
} else {
alert(puppet.name + ' is eventually turned into a real boy and they all lived happily ever after...');
}
}
}
}
alert('Once upon a time...');
alert('Fast forward to the moment that Pinocchio is searching for his \'father\'...');
alert('Pinocchio is ' + pinocchio.location + '.');
terribleDogfish.swallowWhole(pinocchio);
alert('Pinocchio is ' + pinocchio.location + '.');
pinocchioFindsMisterGeppetto();
alert('Pinocchio is ' + pinocchio.location + '.');
happyEnding();
alert('Pinocchio is ' + pinocchio.location + '.');
if (pinocchio.noseLength > 2)
console.log('Hmmm... apparently a little white lie was told. ');
}
tellStoryOfPinocchio(false);
たぶんあなたは内部関数の代わりにオブジェクト指向の構造を考えるべきです。例えば:
var calculate = {
number: 0,
init: function (num) {
this.number = num;
},
add: function (val) {
this.number += val;
},
rem: function (val) {
this.number -= val;
}
};
とにかく "return"が必要なcalculate.number変数から結果を読み取ります。
クロージャーは3つの基準を満たすコードのブロックです。
値として渡すことができます
その時点でその価値を持っている人なら誰でも要求に応じて実行される
それは、それが作成された文脈からの変数を参照することができます(つまり、変数 "closed"の数学的意味では変数アクセスに関して閉じられています)。
(「クロージャー」という言葉は実際には不正確な意味を持っており、基準#1が定義の一部であるとは思わない人もいます。それはそうだと思います)
クロージャは関数型言語の主力ですが、他の多くの言語にも存在します(たとえば、Javaの匿名内部クラス)。あなたはそれらでクールなことをすることができます:それらは延期された実行とスタイルのいくつかの優雅なトリックを可能にします。
投稿者:Paul Cantrell、@ http://innig.net/software/Ruby/closures-in-Ruby
関数が呼び出されると、それは範囲外になります。その関数にコールバック関数のようなものが含まれている場合、そのコールバック関数はまだ有効範囲内です。コールバック関数が親関数の直接の環境でローカル変数を参照している場合、当然その変数はコールバック関数にアクセスできず、undefinedを返すことになります。
クロージャは、その親関数が範囲外になった場合でも、コールバック関数によって参照されるすべてのプロパティがその関数によって使用可能であることを保証します。
クロージャーは、内部関数が何らかの形で外部関数の外側の任意のスコープで使用可能になったときに作成されます。
例:
var outer = function(params){ //Outer function defines a variable called params
var inner = function(){ // Inner function has access to the params variable of the outer function
return params;
}
return inner; //Return inner function exposing it to outer scope
},
myFunc = outer("myParams");
myFunc(); //Returns "myParams"
おそらく6歳の子供にクロージャについて話すべきではありませんが、クロージャは他の関数スコープで宣言された変数にアクセスする能力を与えると言うかもしれません。
function getA() {
var a = [];
// this action happens later,
// after the function returned
// the `a` value
setTimeout(function() {
a.splice(0, 0, 1, 2, 3, 4, 5);
});
return a;
}
var a = getA();
out('What is `a` length?');
out('`a` length is ' + a.length);
setTimeout(function() {
out('No wait...');
out('`a` length is ' + a.length);
out('OK :|')
});
<pre id="output"></pre>
<script>
function out(k) {
document.getElementById('output').innerHTML += '> ' + k + '\n';
}
</script>
質問を考えることはそれを 6才 のように単純に説明することである、私の答えはそうだろう:
"JavaScriptで関数を宣言すると、その関数宣言の前の行で利用可能だったすべての変数と関数に永遠にアクセスできるようになります。クロージャー」
クロージャ はやや高度で、誤解されがちなJavaScript言語の機能です。簡単に言うと、クロージャは、関数とその関数が作成された環境への参照を含むオブジェクトです。しかし、クロージャを完全に理解するためには、JavaScript言語には、最初に理解しなければならない2つの他の機能(ファーストクラス関数と内部関数)があります。
ファーストクラス関数
プログラミング言語では、関数が他のデータ型のように操作できる場合、その関数は第一級の市民であると見なされます。たとえば、第一級関数は実行時に構築して変数に割り当てることができます。他の関数に渡したり、他の関数から返すこともできます。前述の基準を満たすことに加えて、JavaScript関数には独自のプロパティとメソッドもあります。次の例は、一級関数の機能の一部を示しています。この例では、2つの関数が作成され、変数「foo」と「bar」に割り当てられています。 “ foo”に格納された関数はダイアログボックスを表示しますが、“ bar”は単に渡された引数を返します。例の最後の行はいくつかのことを行います。まず、 "bar"に格納されている関数が、 "foo"を引数として呼び出されます。その後、「bar」は「foo」関数参照を返します。最後に、返された“ foo”参照が呼び出され、“ Hello World!”が表示されます。
var foo = function() {
alert("Hello World!");
};
var bar = function(arg) {
return arg;
};
bar(foo)();
内部関数
内部関数は、入れ子関数とも呼ばれ、別の関数(外部関数と呼ばれる)の内部で定義されている関数です。外部関数が呼び出されるたびに、内部関数のインスタンスが作成されます。次の例は、内部関数の使用方法を示しています。この場合、add()が外部関数です。 add()の内部で、doAdd()内部関数が定義されて呼び出されます。
function add(value1, value2) {
function doAdd(operand1, operand2) {
return operand1 + operand2;
}
return doAdd(value1, value2);
}
var foo = add(1, 2);
// foo equals 3
内部関数の1つの重要な特徴は、それらが外部関数のスコープに暗黙的にアクセスできることです。これは、内部関数が外部関数の変数、引数などを使用できることを意味します。前の例では、add()の“ value1”と“ value2”引数は、“ operand1”と“ operand2”引数としてdoAdd()に渡されました。 。ただし、doAdd()は「value1」および「value2」に直接アクセスするため、これは不要です。上記の例は、doAdd()で“ value1”と“ value2”を使用する方法を示すために、以下のように書き直されました。
function add(value1, value2) {
function doAdd() {
return value1 + value2;
}
return doAdd();
}
var foo = add(1, 2);
// foo equals 3
クロージャの作成
クロージャは、それを作成した関数の外側から内側の関数にアクセスできるようになったときに作成されます。これは通常、外部関数が内部関数を返すときに発生します。この場合、内部関数はそれが作成された環境への参照を維持します。これは、その時点で有効範囲内にあったすべての変数(およびその値)を記憶していることを意味します。次の例は、クロージャの作成方法と使用方法を示しています。
function add(value1) {
return function doAdd(value2) {
return value1 + value2;
};
}
var increment = add(1);
var foo = increment(2);
// foo equals 3
この例に関して注意すべきことがいくつかあります。
Add()関数はその内部関数doAdd()を返します。内部関数への参照を返すことによって、クロージャが作成されます。 「value1」はadd()のローカル変数、およびdoAdd()の非ローカル変数です。非ローカル変数は、ローカルスコープにもグローバルスコープにもない変数を指します。 “ value2”はdoAdd()のローカル変数です。 add(1)が呼び出されると、クロージャが作成され、 "increment"に格納されます。クロージャの参照環境では、「value1」はvalue 1にバインドされています。束縛された変数もまた閉じられると言われます。これが名前のクロージャの由来です。 increment(2)が呼び出されると、クロージャに入ります。これは、doAdd()が呼び出され、「value1」変数に値1が保持されていることを意味します。クロージャは基本的に以下の関数を作成すると考えることができます。
function increment(value2) {
return 1 + value2;
}
クロージャを使用する場合
クロージャは多くのことを達成するために使用することができます。それらはパラメータでコールバック関数を設定することのようなもののために非常に役に立ちます。この節では、クロージャによって開発者としての生活がはるかに簡単になる2つのシナリオについて説明します。
タイマーの操作
クロージャは、setTimeout()およびsetInterval()関数と組み合わせて使用すると便利です。より具体的に言うと、クロージャはsetTimeout()とsetInterval()のコールバック関数に引数を渡すことを可能にします。たとえば、次のコードはshowMessage()を呼び出して、文字列「some message」を1秒に1回出力します。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Closures</title>
<meta charset="UTF-8" />
<script>
window.addEventListener("load", function() {
window.setInterval(showMessage, 1000, "some message<br />");
});
function showMessage(message) {
document.getElementById("message").innerHTML += message;
}
</script>
</head>
<body>
<span id="message"></span>
</body>
</html>
残念ながら、Internet ExplorerはsetInterval()によるコールバック引数の受け渡しをサポートしていません。 「何らかのメッセージ」を表示する代わりに、Internet Explorerは「未定義」を表示します(実際にはshowMessage()に値が渡されないため)。この問題を回避するには、「message」引数を目的の値にバインドするクロージャを作成します。クロージャはsetInterval()のコールバック関数として使用できます。この概念を説明するために、前の例のJavaScriptコードをクロージャを使用するように以下に書き換えました。
window.addEventListener("load", function() {
var showMessage = getClosure("some message<br />");
window.setInterval(showMessage, 1000);
});
function getClosure(message) {
function showMessage() {
document.getElementById("message").innerHTML += message;
}
return showMessage;
}
個人データのエミュレート
多くのオブジェクト指向言語は、プライベートメンバーデータの概念をサポートしています。ただし、JavaScriptは純粋なオブジェクト指向言語ではなく、プライベートデータをサポートしません。しかし、クロージャを使ってプライベートデータをエミュレートすることは可能です。クロージャには、それが最初に作成された環境への参照が含まれていることを思い出してください。これは現在範囲外です。参照環境の変数はクロージャ関数からしかアクセスできないので、本質的にプライベートデータです。
次の例は、単純なPersonクラスのコンストラクタを示しています。各Personが作成されると、“ name”引数を介して名前が付けられます。内部的には、Personはその名前を“ _name”変数に格納します。良いオブジェクト指向プログラミングの慣習に従って、名前を取得するためのメソッドgetName()も提供されます。
function Person(name) {
this._name = name;
this.getName = function() {
return this._name;
};
}
Personクラスにはまだ1つ大きな問題があります。 JavaScriptは非公開データをサポートしていないので、他の誰かが一緒になって名前を変更するのを妨げるものは何もありません。たとえば、次のコードはColinという名前のPersonを作成し、その名前をTomに変更します。
var person = new Person("Colin");
person._name = "Tom";
// person.getName() now returns "Tom"
個人的には、だれかが一緒に来て合法的に私の名前を変更できるのであれば、それは望ましくありません。これを防ぐために、クロージャーを使用して“ _name”変数を非公開にすることができます。 Personコンストラクタは、クロージャを使用して以下のように書き直されました。 「_name」は、オブジェクトプロパティではなく、Personコンストラクタのローカル変数になりました。外側の関数Person()がpublic getName()メソッドを作成することによって内側の関数を公開するため、クロージャが形成されます。
function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
}
現在、getName()が呼び出されると、元々コンストラクタに渡された値が返されることが保証されています。だれかがオブジェクトに新しい“ _name”プロパティを追加することはまだ可能ですが、それらがクロージャによって束縛された変数を参照する限り、オブジェクトの内部の働きは影響を受けません。次のコードは、“ _ name”変数が実際にプライベートであることを示しています。
var person = new Person("Colin");
person._name = "Tom";
// person._name is "Tom" but person.getName() returns "Colin"
クロージャを使用しない場合
クロージャがどのように機能するのか、またいつ使用するのかを理解することが重要です。それらが当面の仕事に適したツールではない場合を理解することも同様に重要です。クロージャを使いすぎると、スクリプトの実行速度が遅くなり、不要なメモリを消費する可能性があります。そしてクロージャは作成がとても簡単なので、それを知らなくてもそれらを誤用することは可能です。このセクションでは、クロージャを慎重に使用する必要があるいくつかのシナリオについて説明します。
ループ内
ループ内にクロージャを作成すると、誤解を招く可能性があります。その一例を以下に示します。この例では、3つのボタンが作成されています。 「button1」をクリックすると、「Clicked button 1」という警告が表示されます。 「button2」と「button3」についても同様のメッセージが表示されます。ただし、このコードを実行すると、すべてのボタンに「Clicked button 4」と表示されます。これは、ボタンの1つがクリックされるまでにループが実行を終了し、ループ変数が最終値の4に達したためです。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Closures</title>
<meta charset="UTF-8" />
<script>
window.addEventListener("load", function() {
for (var i = 1; i < 4; i++) {
var button = document.getElementById("button" + i);
button.addEventListener("click", function() {
alert("Clicked button " + i);
});
}
});
</script>
</head>
<body>
<input type="button" id="button1" value="One" />
<input type="button" id="button2" value="Two" />
<input type="button" id="button3" value="Three" />
</body>
</html>
この問題を解決するには、クロージャを実際のループ変数から切り離す必要があります。これは新しい関数を呼び出すことで実行でき、それによって新しい参照環境が作成されます。次の例は、これがどのように行われるかを示しています。ループ変数はgetHandler()関数に渡されます。その後、getHandler()は、元の「for」ループから独立したクロージャを返します。
function getHandler(i) {
return function handler() {
alert("Clicked button " + i);
};
}
window.addEventListener("load", function() {
for (var i = 1; i < 4; i++) {
var button = document.getElementById("button" + i);
button.addEventListener("click", getHandler(i));
}
});
コンストラクタでの不要な使用
コンストラクタ関数もクロージャの悪用の一般的な原因です。クロージャを使用して個人データをエミュレートする方法を説明しました。ただし、実際にプライベートデータにアクセスしない場合は、クロージャとしてメソッドを実装するのはやり過ぎです。次の例ではPersonクラスを再検討していますが、今回はプライベートデータを使用しないsayHello()メソッドを追加します。
function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
this.sayHello = function() {
alert("Hello!");
};
}
Personがインスタンス化されるたびに、sayHello()メソッドの作成に時間が費やされます。多数のPersonオブジェクトが作成されると、これは時間の無駄になります。もっと良い方法は、PersonプロトタイプにsayHello()を追加することです。プロトタイプに追加することで、すべてのPersonオブジェクトが同じメソッドを共有できます。これにより、インスタンスごとにクロージャを作成する必要がなくなるため、コンストラクタの時間を節約できます。前の例は、無関係なクロージャをプロトタイプに移動して以下のように書き換えられます。
function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
}
Person.prototype.sayHello = function() {
alert("Hello!");
};
覚えておくべきこと
var pure = function pure(x){
return x
// only own environment is used
}
var foo = "bar"
var closure = function closure(){
return foo
// foo is free variable from the outer environment
}
MDNがそれを最もよく説明していると思う:
クロージャは独立(自由)変数を参照する関数です。言い換えれば、クロージャで定義された関数は、それが作成された環境を「記憶」します。
クロージャは常に外側の関数と内側の関数を持ちます。内部関数はすべての作業が行われる場所であり、外部関数は内部関数が作成されたスコープを保持する環境にすぎません。このように、クロージャの内部関数はそれが作成された環境/スコープを「記憶」します。最も古典的な例はカウンタ関数です。
var closure = function() {
var count = 0;
return function() {
count++;
console.log(count);
};
};
var counter = closure();
counter() // returns 1
counter() // returns 2
counter() // returns 3
上記のコードでは、count
は外部関数(environment関数)によって保持されているため、counter()
を呼び出すたびに、内部関数(work関数)で増分することができます。
Kyle Simpsonによるクロージャの定義が好きです。
関数がその語彙の範囲外で実行されていても、その関数がその語彙の範囲を記憶しアクセスすることができるとき、クロージャはあります。
字句スコープは、内部スコープがその外部スコープにアクセスできる場合です。
これは彼が彼の著書シリーズ「あなたはわからないJS:Scopes&Closures」で提供する修正例です。
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
function test() {
var bz = foo();
bz();
}
// prints 2. Here function bar referred by var bz is outside
// its lexical scope but it can still access it
test();
これは、初心者が関数のようにClosuresに頭を包んだ方法で、 Closures とも呼ばれる関数本体の中に包まれる方法です。
本からの定義JavaScriptを話す「クロージャとは、関数と、その関数が作成されたスコープへの接続」 - - Dr.Axel Rauschmayer
それで、それはどのように見えるでしょうか?これが例です
function newCounter() {
var counter = 0;
return function increment() {
counter += 1;
}
}
var counter1 = newCounter();
var counter2 = newCounter();
counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3
newCounter を閉じる increment 、 counter は、 increment を参照してアクセスできます。
counter1 および counter2 は、それぞれの値を追跡します。
単純ではあるがうまくいけば、クロージャがこれらすべての偉大で先進的な答えのまわりにあるものの明確な展望.
関数が呼ばれる時までに不変である名前空間でそれが定義されたという方法で、関数は closed であるときにクロージャです。
JavaScriptでは、次の場合に起こります。
// 'name' is resolved in the namespace created for one invocation of bindMessage
// the processor cannot enter this namespace by the time displayMessage is called
function bindMessage(name, div) {
function displayMessage() {
alert('This is ' + name);
}
$(div).click(displayMessage);
}
6歳のために...
あなたはどんな物が知っていますか?
オブジェクトはプロパティを持ち、何かをするものです。
クロージャーに関する最も重要なことの1つは、それらを使用してJavaScriptでオブジェクトを作成できることです。 JavaScriptのオブジェクトは、作成されたオブジェクトのプロパティの値をJavaScriptに格納させるための単なる関数およびクロージャです。
オブジェクトは非常に便利で、すべてを素晴らしく整理されたものにします。異なるオブジェクトは異なる仕事をすることができ、オブジェクトを一緒に扱うことは複雑なことをすることができます。
JavaScriptがオブジェクトを作成するためのクロージャを持っているのはラッキーです。さもなければ、すべてが厄介な悪夢になるでしょう。
最善の方法は、これらの概念を少しずつ説明することです。
変数
console.log(x);
// undefined
ここで、undefined
はJavaScriptの「私はx
が何を意味するのかわかりません」と言います。
変数はタグのようなものです。
タグx
は値42
を指していると言えます。
var x = 42;
console.log(x);
// 42
これでJavaScriptはx
の意味を知っています。
変数を再割り当てすることもできます。
タグx
が異なる値を指すようにします。
x = 43;
console.log(x);
// 43
今x
は他の何かを意味します。
スコープ
あなたが関数を作るとき、その関数は変数のためのそれ自身の "ボックス"を持ちます。
function A() {
var x = 42;
}
console.log(x);
// undefined
箱の外側から、箱の内側にあるものを見ることはできません。
しかし、箱の内側から、その箱の外側にあるものを見ることができます。
var x = 42;
function A() {
console.log(x);
}
// 42
関数
A
の中には、x
への "スコープアクセス"があります。
2つの箱が並んでいるとします。
function A() {
var x = 42;
}
function B() {
console.log(x);
}
// undefined
関数
B
内では、関数A
内の変数にアクセスすることはできません。
しかし、関数B
の中にdefine function A
を入れると、
function A() {
var x = 42;
function B() {
console.log(x);
}
}
// 42
これで「スコープアクセス」ができました。
関数
JavaScriptでは、関数を呼び出して実行します。
function A() {
console.log(42);
}
このような:
A();
// 42
値としての機能
JavaScriptでは、数字を指すのと同じように、タグを関数に向けることができます。
var a = function() {
console.log(42);
};
変数
a
は関数を意味するので、実行することができます。
a();
// 42
この変数を渡すこともできます:
setTimeout(a, 1000);
1秒(1000ミリ秒)の間に、a
が指す関数が呼び出されます。
// 42
閉鎖範囲
関数を定義すると、それらの関数は外側のスコープにアクセスできるようになります。
値として関数を渡した場合、そのアクセスが失われると面倒になります。
JavaScriptでは、関数は外部スコープ変数へのアクセスを維持します。たとえそれらが他のどこかに走らせるために回されても。
var a = function() {
var text = 'Hello!'
var b = function() {
console.log(text);
// inside function `b`, you have access to `text`
};
// but you want to run `b` later, rather than right away
setTimeout(b, 1000);
}
今、何が起きた?
// 'Hello!'
またはこれを考慮してください。
var c;
var a = function() {
var text = 'Hello!'
var b = function() {
console.log(text);
// inside function `b`, you have access to `text`
};
c = b;
}
// now we are out side of function `a`
// call `a` so the code inside `a` runs
a();
// now `c` has a value that is a function
// because what happened when `a` ran
// when you run `c`
c();
// 'Hello!'
クロージャスコープ内の変数にアクセスすることはできます。
a
は実行を終了していますが、現在はc
の外側でa
を実行しています。
ここで起こったことは、JavaScriptでは ' closing 'と呼ばれています。
閉鎖は理解するのが難しくありません。それは観点からだけに依存します。
私は個人的に日常生活の場合にそれらを使用するのが好きです。
function createCar()
{
var rawMaterial = [/* lots of object */];
function transformation(rawMaterials)
{
/* lots of changement here */
return transformedMaterial;
}
var transformedMaterial = transformation(rawMaterial);
function assemblage(transformedMaterial)
{
/*Assemblage of parts*/
return car;
}
return assemblage(transformedMaterial);
}
特定の場合には、特定の手順を踏むだけで済みます。材料の変換に関してはあなたが部品を持っているときだけ役に立ちます。
かつて穴居人がいました
function caveman {
非常に特別な岩を持っていた人、
var rock = "diamond";
それは穴居人の私的な洞窟の中にあったので、あなたは自分で岩を手に入れることができませんでした。穴居人だけが岩を見つけて手に入れる方法を知っていました。
return {
getRock: function() {
return rock;
}
};
}
幸いなことに、彼は友好的な穴居人だった、そしてあなたが彼の帰りを待っても構わないと思っていたら、彼は喜んであなたのためにそれを得るでしょう。
var friend = caveman();
var rock = friend.getRock();
かなり賢い穴居人。
ここから始めましょう、MDNで定義されているように、 Closures は、独立した(自由な)変数(ローカルで使用されているが包含スコープで定義された変数)を参照する関数です。言い換えれば、これらの関数はそれらが作成された環境を「記憶」します。
字句スコープ
次の点を考慮してください。
function init() {
var name = 'Mozilla'; // name is a local variable created by init
function displayName() { // displayName() is the inner function, a closure
alert(name); // use variable declared in the parent function
}
displayName();
}
init();
init()はnameというローカル変数とdisplayName()という関数を作成します。 displayName()関数はinit()内で定義されている内部関数であり、init()関数の本体内でのみ使用可能です。 displayName()関数には、それ自体のローカル変数はありません。ただし、内部関数は外部関数の変数にアクセスできるため、displayName()は親関数init()で宣言された変数名にアクセスできます。
function init() {
var name = "Mozilla"; // name is a local variable created by init
function displayName() { // displayName() is the inner function, a closure
alert (name); // displayName() uses variable declared in the parent function
}
displayName();
}
init();
コードを実行して、displayName()関数内のalert()ステートメントが、その親関数で宣言されているname変数の値を正常に表示することを確認します。これは、関数がネストされているときにパーサーが変数名を解決する方法を説明している字句スコープの例です。 「字句」という語は、字句スコープがソースコード内で変数が宣言されている場所を使用して、その変数が使用可能な場所を判断するという事実を指します。入れ子になった関数は、その外側の有効範囲で宣言された変数にアクセスできます。
閉鎖
では、次の例を見てください。
function makeFunc() {
var name = 'Mozilla';
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
このコードを実行すると、上記のinit()関数の例とまったく同じ効果が得られます。今回は、文字列 "Mozilla"がJavaScriptの警告ボックスに表示されます。何が違うのか、そして面白いのは、displayName()内部関数が実行前に外部関数から返されることです。
一見すると、このコードがまだ機能することは直感に反するように思われるかもしれません。プログラミング言語によっては、関数内のローカル変数はその関数の実行中だけ存在します。 makeFunc()の実行が終了すると、名前変数にアクセスできなくなることが予想されます。ただし、コードはまだ期待どおりに機能するため、JavaScriptでは明らかにそうではありません。
その理由は、JavaScriptの関数がクロージャを形成するからです。クロージャーは、関数とその関数が宣言されている字句環境の組み合わせです。この環境は、クロージャが作成された時点でスコープ内にあったローカル変数から構成されています。この場合、myFuncは、makeFuncの実行時に作成された関数displayNameのインスタンスへの参照です。 displayNameのインスタンスは、変数名が存在するそのレキシカル環境への参照を維持します。このため、myFuncが呼び出されても、変数名は引き続き使用可能であり、「Mozilla」がアラートに渡されます。
これはもう少し興味深い例です - makeAdder関数:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
この例では、関数makeAdder(x)を定義しました。これは単一の引数xを取り、新しい関数を返します。これが返す関数は単一の引数yを取り、xとyの合計を返します。
本質的に、makeAdderは関数ファクトリです - 引数に特定の値を追加できる関数を作成します。上記の例では、関数ファクトリを使用して2つの新しい関数を作成します。1つは引数に5を追加し、もう1つは10を追加します。
add5とadd10はどちらもクロージャです。それらは同じ関数本体定義を共有しますが、異なる字句環境を格納します。 add5の字句環境ではxは5ですが、add10の字句環境ではxは10です。
実用的な閉鎖
クロージャは、あるデータ(字句環境)をそのデータを操作する関数に関連付けることを可能にするので便利です。これは明らかにオブジェクト指向プログラミングに似ています。オブジェクト指向プログラミングでは、あるデータ(オブジェクトのプロパティ)を1つ以上のメソッドに関連付けることができます。
したがって、通常は単一のメソッドだけでオブジェクトを使用する可能性がある場所であればどこでもクロージャを使用できます。
あなたがこれをしたいかもしれない状況はウェブ上で特に一般的です。フロントエンドJavaScriptで記述するコードの大部分はイベントベースです - 何らかの動作を定義してから、それをユーザーがトリガーするイベント(クリックやキー入力など)にアタッチします。私たちのコードは一般的にコールバックとして添付されています:イベントに応答して実行される単一の関数です。
たとえば、テキストサイズを調整するボタンをページに追加したいとします。これを行う1つの方法は、body要素のfont-sizeをピクセル単位で指定してから、相対em単位を使用してページ上の他の要素(ヘッダーなど)のサイズを設定することです。
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
インタラクティブテキストサイズボタンはbody要素のfont-sizeプロパティを変更することができ、調整は相対的な単位のおかげでページ上の他の要素によって拾われます。これがJavaScriptです。
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
size12、size14、およびsize16は、本文をそれぞれ12、14、および16ピクセルにサイズ変更する関数になりました。次のようにそれらをボタン(この場合はリンク)に添付することができます。
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
クロージャの詳細については、MDNの リンクをご覧ください
私は過去にこれらのすべてを以前に読んだことがあり、それらはすべて非常に有益です。単純な説明を得ることに非常に近づき、それから複雑になるか抽象的なままになり、目的を破り、非常に単純な現実世界の使用法を示すことに失敗する人もいます。
すべての例と説明をくまなく参照することで、コメントやコードを介してクロージャが何であるのか、またそうでないのかがよくわかりますが、非常に複雑なことなくクロージャの有用性を得るのに役立ちました。私の妻はコーディングを学びたいのですが、私はここで何を、なぜ、そしてどうやってそしてどのように見せることができる必要があると思いました。
6歳の子供がこれを実現するかどうかは定かではありませんが、実際には簡単でわかりやすい簡単なケースを実際の方法で示すのには少し近いかもしれません。
最善の(または最も単純な)最も近い方法の1つは、MorrisのClosures for Dummiesの例の改訂です。
「SayHi2Bob」という概念を一歩だけ踏み出すと、すべての答えを読むことから集めることができる2つの基本的なことがさらに実証されます。
私自身にこれを証明し実証するために、私はちょっとした手間をかけました:
http://jsfiddle.net/9ZMyr/2/ /
function sayHello(name) {
var text = 'Hello ' + name; // Local variable
console.log(text);
var sayAlert = function () {
alert(text);
}
return sayAlert;
}
sayHello();
/* This will write 'Hello undefined' to the console (in Chrome anyway),
but will not alert though since it returns a function handle to nothing).
Since no handle or reference is created, I imagine a good js engine would
destroy/dispose of the internal sayAlert function once it completes. */
// Create a handle/reference/instance of sayHello() using the name 'Bob'
sayHelloBob = sayHello('Bob');
sayHelloBob();
// Create another handle or reference to sayHello with a different name
sayHelloGerry = sayHello('Gerry');
sayHelloGerry();
/* Now calling them again demonstrates that each handle or reference contains its own
unique local variable memory space. They remain in memory 'forever'
(or until your computer/browser explode) */
sayHelloBob();
sayHelloGerry();
これはクロージャーについてあなたが得るべきである基本的な概念の両方を示しています。
これがなぜ有用であるかを説明する簡単な言葉で、私はそのメモリ参照内に存続するユニークなデータを含む参照またはハンドルを作ることができる基本関数を持っています。誰かの名前を言うたびに関数を書き直す必要はありません。そのルーチンをカプセル化して再利用可能にしました。
私には、これは少なくともコンストラクタの基本概念、OOPプラクティス、シングルトンとインスタンス化されたインスタンスとそれら自身のデータなどとをもたらします。
これで初心者を始めたら、より複雑なオブジェクトプロパティ/メンバーベースの呼び出しに進むことができ、うまくいけば概念は継承します。
私は一歩後退して、「クロージャー」、いわゆる「結合演算子」のより一般的な概念を検討することが価値があると思います。
数学では、「結合」演算子は、その引数以上の最小のオブジェクトを返す半順序集合の関数です。シンボルでは、d> = aおよびd> = bとなるように[a、b] = dを結合します。ただし、d> e> = aまたはd> e> = bのようなeは存在しません。
そのため、結合は部品よりも「大きい」最小のものを提供します。
さて、JavaScriptのスコープは半順序構造です。参加という賢明な概念があるように。特に、スコープの結合は、元のスコープよりも大きい最小のスコープです。その範囲は クロージャ と呼ばれます。
したがって、変数a、b、cのクロージャーは、a、b、およびcをスコープに入れる最小のスコープ(プログラムのスコープの範囲内)です。
JavaScriptのクロージャ を説明するために私が考えることができる最も簡単なユースケースはモジュールパターンです。モジュールパターンでは、関数を定義し、その直後にこれを即時呼び出し関数式(IIFE)と呼びます。 クロージャ の中で定義されているので、その関数の中に書くものはすべてプライベートスコープを持ちます。したがって、JavaScriptでプライバシーを「シミュレート」することができます。そのようです:
var Closure = (function () {
// This is a closure
// Any methods, variables and properties you define here are "private"
// and can't be accessed from outside the function.
//This is a private variable
var foo = "";
//This is a private method
var method = function(){
}
})();
一方、1つまたは複数の変数またはメソッドをクロージャの外側で見えるようにしたい場合は、それらをオブジェクトリテラルの中で返すことができます。そのようです:
var Closure = (function () {
// This is a closure
// Any methods, variables and properties you define here are "private"
// and can't be accessed from outside the function.
//This is a private variable
var foo = "";
//This is a private method
var method = function(){
}
//The method will be accessible from outside the closure
return {
method: method
}
})();
Closure.method();
それが役に立てば幸い。よろしく、
クロージャ は単に objects (6歳:モノ)にアクセスできることを意味します closed (6歳:プライベート) 関数内 (6歳:ボックス)。 関数 (6歳:ボックス)が 範囲 (6歳:遠くに送られた)の範囲外であっても.
クロージャーは基本的に2つのことを作り出しています。 - 関数 - その関数だけがアクセスできるプライベートスコープ
関数の周りにコーティングを施すのと同じです。
6歳の子供にとって、それは例えをすることによって説明することができます。ロボットを作るとしましょう。そのロボットはいろいろなことができます。それらの中で、私は彼が空に見る鳥の数を数えるようにそれをプログラムしました。彼は25羽の鳥を見たことがあるたびに、彼は初めから彼が何羽の鳥を見たかを私に言うべきです。
彼が私に言っていない限り彼が見た鳥の数はわかりません。彼だけが知っています。それが私的な範囲です。それは基本的にロボットの記憶です。私が彼に4 GBを渡したとしましょう。
彼が見た鳥の数を教えてくれるのが返された関数です。私もそれを作りました。
その類推はちょっと意地悪ですが、誰かがそれを改善することができると思います。
私のクロージャーの見方:
本棚のクロージャは、ブックマーク付きの本と比較できます。
あなたが本を読んだことがあり、その本の中のあるページが好きだとします。あなたはそれを追跡するためにそのページにブックマークを入れました。
あなたがその本を読み終えたら、あなたはもうその本を必要としません。あなたはちょうどページを切り取ったかもしれませんが、それから物語の文脈を失うでしょう。それであなたはその本をしおりと共にあなたの本棚に戻す。
これはクロージャに似ています。本は外側の関数で、ページは内側の関数で、外側の関数から返されます。ブックマークはあなたのページへの参照であり、ストーリーのコンテキストは字句の範囲です。それを保持する必要があります。本棚は関数スタックであり、あなたがページを掴むまで、古い本を片付けることはできません。
コード例:
function book() {
var pages = [....]; //array of pages in your book
var bookMarkedPage = 20; //bookmarked page number
function getPage(){
return pages[bookMarkedPage];
}
return getPage;
}
var myBook = book(),
myPage = myBook.getPage();
book()
関数を実行すると、関数が実行されるようにスタック内のメモリが割り当てられます。ただし、内部関数は外部のコンテキストから変数にアクセスするため、メモリを解放することはできません。この場合は 'pages'と 'bookMarkedPage'です。
そのため、book()
を効果的に呼び出すと、クロージャへの参照、つまり関数だけでなく、本とそのコンテキストへの参照、つまり getPage 、 pages への参照が返されますおよび bookMarkedPage 変数。
考慮すべき点がいくつかあります。
Point 1: /本棚は、関数スタックのスペースが限られているのと同じように、慎重に使用してください。
Point 2: 単一のページを追跡したいだけのときに、本全体を掴む必要があるかどうかという事実について考えてみてください。クロージャが返されたときにブックのすべてのページを保存しないことで、メモリの一部を解放できます。
これが私のクロージャーの見方です。それが助けになることを願っています、そして、誰かがこれが正しくないと思うならば、私が範囲と閉鎖についてさらに理解することに非常に興味があるので、私に知らせてください!
Also ...おそらく、27歳の友人を少しslack、カットする必要があります。 "本当にis(!) ...voodoo!
ということは:(a)直感的には期待しない...そして...(b)誰かがあなたにそれを説明するのに時間をかけるとき、あなたは確かにそれが働くことを期待しない!
直観は、「これはナンセンスでなければならない...確かに何らかの構文エラーか何かにならなければならない!」 どのように地球上で(!)、実際には「実際に」できるように、「どこでも」の「中央」から関数を引き出すことができますか? 「wherever-it-was-at ?!」のコンテキストへの読み取り/書き込みアクセス権があります。
そのようなことが可能、であることを最終的に認識すると、...確かに...誰かの事後反応「whoa-aaa(!)... kew-el-lll ...(!!!)」
しかし、最初に克服する「大きな直観に反するハードル」があります。直観は、そのようなことが「コース、絶対に無意味であり、したがって非常に不可能である」という完全に妥当な期待をたくさん与えます。
私が言ったように:「それはブードゥー教です。」
クロージャーとは、各行が同じ変数名を持つ同じ変数セットを参照できるコードブロックです。
「これ」が他の場所とは異なる意味を持つ場合は、2つの異なるクロージャであることがわかります。
クロージャは、プライベートおよびパブリックの変数または関数です。
var ClusureDemo = function() {
//privare variables
var localVa1, localVa2;
//private functions
var setVaOne = function(newVa) {
localVa1 = newVa;
},
setVaTwo = function(newVa) {
localVa2 = newVa;
},
getVaOne = function() {
return localVa1;
},
getVaTwo = function() {
return localVa2;
};
return {
//public variables and functions
outVaOne : localVa1,
outVaTwo : localVa2,
setVaOne : setVaOne,
setVaTwo : setVaTwo,
getVaOne : getVaOne,
getVaTwo : getVaTwo
};
};
//Test Demo
var app = new ClusureDemo();
app.outVaOne = 'Hello Variable One';
app.outVaTwo = 'Hello Variable Two';
app.setVaOne(app.outVaOne);
app.setVaTwo(app.outVaTwo);
alert(app.getVaOne());
alert(app.getVaTwo());
クロージャーは、それが定義された環境からの情報にアクセスできる機能です。
一部の人にとっては、情報は作成時の環境内の 値 です。他の人にとっては、この情報は作成時の環境内の変数です。
クロージャが参照する字句環境が終了した関数に属している場合、(環境内の変数を参照するクロージャの場合)それらの字句変数はクロージャによる参照用に存在し続けます。
クロージャーは、グローバル変数の特別な場合 - 関数専用に作成されたプライベートコピー - と考えることができます。
あるいは、環境が、プロパティが環境内の変数であるオブジェクトの特定のインスタンスである方法と考えることもできます。
前者(環境としてのクロージャ)は後者と似ていますが、環境コピーは前者の各関数に渡されるコンテキスト変数であり、インスタンス変数は後者のコンテキスト変数を形成します。
したがって、クロージャは、コンテキストをパラメータとして、またはメソッド呼び出しのオブジェクトとして明示的に指定しなくても関数を呼び出す方法です。
var closure = createclosure(varForClosure);
closure(param1); // closure has access to whatever createclosure gave it access to,
// including the parameter storing varForClosure.
vs
var contextvar = varForClosure; // use a struct for storing more than one..
contextclosure(contextvar, param1);
vs
var contextobj = new contextclass(varForClosure);
contextobj->objclosure(param1);
保守可能なコードには、オブジェクト指向の方法をお勧めします。ただし、すばやく簡単な一連のタスク(コールバックの作成など)では、特にラムダ関数や無名関数の場合には、クロージャが自然で明確になります。
クロージャとは、スコープの関数が実行を終了した後でも、その関数がその外側のスコープにアクセスできる場合のことです。例:
function multiplier(n) {
function multiply(x) {
return n*x;
}
return mutliply;
}
var 10xmultiplier = multiplier(10);
var x = 10xmultiplier(5); // x= 50
乗算器の実行が終了した後でも、内部関数multiplyはxの値(この例では10)にまだアクセスできることがわかります。
クロージャの非常に一般的な使い方はカリー化(上の例と同じ)です。ここでは、すべての引数を一度に渡すのではなく、関数を徐々にパラメータでスパイスします。
これは、Javascript(プロトタイプのOOPに加えて)が高次関数が他の関数を引数として取ることができるような関数形式でプログラミングすることを可能にする(fisrtクラス関数)ために実現できます。 ウィキペディアの関数型プログラミング
この本をKyle Simpsonが読むことを強くお勧めします: 2 本のシリーズの一部はクロージャ専用であり、スコープとクロージャと呼ばれます。 jsがわからない:githubで自由に読んでください