JavaScript クロージャーを構成する概念 (関数、変数など) についての知識はあるが、クロージャー自体を理解していない人に、JavaScript クロージャーをどのように説明しますか?
ウィキペディアにあるスキームの例を見たことがありますが、残念ながら役に立ちませんでした。
JavaScript クロージャーを構成する概念 (関数、変数など) についての知識はあるが、クロージャー自体を理解していない人に、JavaScript クロージャーをどのように説明しますか?
ウィキペディアにあるスキームの例を見たことがありますが、残念ながら役に立ちませんでした。
クロージャーは、次の組み合わせです。
レキシカル環境は、すべての実行コンテキスト (スタック フレーム) の一部であり、識別子 (つまり、ローカル変数名) と値の間のマップです。
JavaScript のすべての関数は、外部のレキシカル環境への参照を維持します。この参照は、関数が呼び出されたときに作成される実行コンテキストを構成するために使用されます。この参照により、関数がいつどこで呼び出されるかに関係なく、関数内のコードが関数外で宣言された変数を「参照」できるようになります。
関数が関数によって呼び出され、その関数が別の関数によって呼び出された場合、外部レキシカル環境への参照のチェーンが作成されます。このチェーンは、スコープ チェーンと呼ばれます。
次のコードでは、が呼び出さinner
れたときに作成された実行コンテキストのレキシカル環境でクロージャーを形成し、variableを閉じます。foo
secret
function foo() {
const secret = Math.trunc(Math.random() * 100)
return function inner() {
console.log(`The secret number is ${secret}.`)
}
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`
言い換えれば、JavaScript では、関数はプライベートな「状態のボックス」への参照を持ち、関数 (および同じレキシカル環境内で宣言された他の関数) だけがアクセスできます。この状態のボックスは、関数の呼び出し元には見えず、データの隠蔽とカプセル化のための優れたメカニズムを提供します。
覚えておいてください: JavaScript の関数は、変数 (ファーストクラス関数) のように渡すことができます。つまり、これらの機能と状態のペアをプログラムで渡すことができます: C++ でクラスのインスタンスを渡す方法と同様です。
JavaScript にクロージャがなければ、関数間でより多くの状態を明示的に渡す必要があり、パラメータ リストが長くなり、コードが煩雑になります。
したがって、関数が常にプライベートな状態にアクセスできるようにする場合は、クロージャーを使用できます。
...そして、状態を関数に関連付けたいことがよくあります。たとえば、Java または C++ では、プライベート インスタンス変数とメソッドをクラスに追加すると、状態が機能に関連付けられます。
C および他のほとんどの一般的な言語では、関数が戻った後、スタック フレームが破棄されるため、すべてのローカル変数にアクセスできなくなります。JavaScript では、別の関数内で関数を宣言すると、外側の関数のローカル変数は、関数から戻った後も引き続きアクセスできます。このように、上記のコードでは、関数オブジェクトが から返された後も、関数オブジェクトでsecret
引き続き使用できます。inner
foo
クロージャーは、関数に関連付けられたプライベート ステートが必要な場合に役立ちます。これは非常に一般的なシナリオです。覚えておいてください: JavaScript には 2015 年までクラス構文がなく、プライベート フィールド構文もまだありません。クロージャーはこのニーズを満たします。
次のコードでは、関数toString
は車の詳細を閉じます。
function Car(manufacturer, model, year, color) {
return {
toString() {
return `${manufacturer} ${model} (${year}, ${color})`
}
}
}
const car = new Car('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver')
console.log(car.toString())
次のコードでは、関数は と の両方をinner
閉じます。fn
args
function curry(fn) {
const args = []
return function inner(arg) {
if(args.length === fn.length) return fn(...args)
args.push(arg)
return inner
}
}
function add(a, b) {
return a + b
}
const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5
次のコードでは、 functiononClick
は variable を閉じますBACKGROUND_COLOR
。
const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200, 200, 242, 1)'
function onClick() {
$('body').style.background = BACKGROUND_COLOR
}
$('button').addEventListener('click', onClick)
<button>Set background color</button>
次の例では、すべての実装の詳細が、すぐに実行される関数式内に隠されています。関数は、作業を完了するために必要なプライベート ステートと関数tick
を閉じます。toString
クロージャにより、コードをモジュール化してカプセル化することが可能になりました。
let namespace = {};
(function foo(n) {
let numbers = []
function format(n) {
return Math.trunc(n)
}
function tick() {
numbers.push(Math.random() * 100)
}
function toString() {
return numbers.map(format)
}
n.counter = {
tick,
toString
}
}(namespace))
const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())
この例は、ローカル変数がクロージャにコピーされていないことを示しています。クロージャは、元の変数自体への参照を保持しています。外側の関数が終了した後でも、スタック フレームがメモリ内で生き続けているかのようです。
function foo() {
let x = 42
let inner = () => console.log(x)
x = x + 1
return inner
}
foo()() // logs 43
次のコードではlog
、 、increment
、およびの 3 つのメソッドがupdate
すべて同じ字句環境を閉じています。
が呼び出されるたびcreateObject
に、新しい実行コンテキスト (スタック フレーム) が作成され、まったく新しい変数x
と、この新しい変数を閉じる新しい関数セット (log
など) が作成されます。
function createObject() {
let x = 42;
return {
log() { console.log(x) },
increment() { x++ },
update(value) { x = value }
}
}
const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42
を使用して宣言された変数を使用している場合はvar
、どの変数を閉じているかを理解するように注意してください。using で宣言された変数var
は巻き上げられます。let
これは、との導入により、最新の JavaScript ではそれほど問題になりませんconst
。
次のコードでは、ループのたびに新しい関数inner
が作成され、i
. しかし、var i
はループの外側に引き上げられるため、これらの内部関数はすべて同じ変数を閉じます。つまり、i
(3) の最終値が 3 回出力されます。
function foo() {
var result = []
for (var i = 0; i < 3; i++) {
result.push(function inner() { console.log(i) } )
}
return result
}
const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
result[i]()
}
function
は、クロージャーの典型的な例です。これは、外側の関数の実行が完了した後でも、返された内側の関数が外側の関数内の状態を暗黙的に利用できるためです。eval()
関数内で使用するときはいつでも、クロージャーが使用されます。関数のローカル変数を参照できるテキスト。eval
非厳密モードでは、 を使用して新しいローカル変数を作成することもできますeval('var foo = …')
。new Function(…)
関数内で( Function コンストラクター)を使用すると、そのレキシカル環境を閉じません。代わりに、グローバル コンテキストを閉じます。新しい関数は、外部関数のローカル変数を参照できません。JavaScript のすべての関数は、外部の字句環境へのリンクを維持します。レキシカル環境は、スコープ内のすべての名前 (変数、パラメーターなど) とその値のマップです。
そのため、キーワードが表示されるたびに、function
その関数内のコードは、関数の外部で宣言された変数にアクセスできます。
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
16
これは、関数がパラメータと変数をbar
閉じるためログに記録されます。これらは両方とも外側の関数のレキシカル環境に存在します。x
tmp
foo
Functionbar
は、 function のレキシカル環境とのリンクとともにfoo
クロージャーです。
クロージャを作成するために関数が戻る必要はありません。単にその宣言のおかげで、すべての関数はそれを囲んでいるレキシカル環境を閉じ、クロージャーを形成します。
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2);
bar(10); // 16
bar(10); // 17
上記の関数も 16 をログに記録します。これは、内部のコードbar
が引数x
と変数を参照できるためtmp
です。これらはスコープ内に直接含まれていません。
ただし、はまだのクロージャ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 関数が呼び出されると、新しい実行コンテキストec
が作成されます。この実行コンテキストは、関数の引数とターゲット オブジェクトとともに、呼び出し元の実行コンテキストのレキシカル環境へのリンクも受け取ります。つまり、外側のレキシカル環境で宣言された変数 (上記の例では、a
との両方b
) が から利用可能になりますec
。
すべての関数はその外側のレキシカル環境へのリンクを持っているため、すべての関数はクロージャを作成します。
変数自体は、コピーではなく、クロージャー内から見えることに注意してください。
序文: この回答は、質問が次の場合に書かれました。
昔のアルバートが言ったように:「6 歳の子供に説明できないのなら、自分でそれを理解していないということです。」まあ、私は 27 歳の友人に JS クロージャを説明しようとしましたが、完全に失敗しました。
私が 6 歳で、その主題に妙に興味を持っていると考える人はいますか?
最初の質問を文字通りに受け取ろうとしたのは私だけだったと確信しています。それ以来、質問は何度か変更されたため、私の答えは信じられないほどばかげて場違いに見えるかもしれません. うまくいけば、ストーリーの一般的なアイデアが、一部の人にとって楽しいままであることを願っています.
私は難しい概念を説明するときの類推と比喩の大ファンなので、ストーリーを試してみましょう。
昔々:
お姫様がいた…
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 に興味がある人はそれほど典型的ではありません。
小児期の発達: 5 歳から 7 歳について は、次のように書かれています。
お子様は 2 段階の指示に従うことができます。例えば、子供に「キッチンに行ってゴミ袋を持ってきて」と言うと、その方向を覚えることができます。
この例を使用して、次のようにクロージャーを説明できます。
キッチンは、 というローカル変数を持つクロージャです
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 のクロージャー動作を使用しています。この動作により、任意の関数が作成されたスコープに無期限にアクセスできます。これを実際に適用するために、別の関数を返す関数をすぐに呼び出します。返される関数は内部カウント変数にアクセスできるため (上記で説明したクロージャの動作のため)、結果として、結果として使用されるプライベート スコープが生成されます。関数... それほど単純ではありませんか?薄めてみましょう…
シンプルな 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 変数はクロージャー内に含まれており、イベント ハンドラーで常に使用できるため、クリックからクリックまで状態が保持されます。
また、このプライベート変数の状態は、読み取りとそのプライベート スコープ変数への割り当ての両方で、完全にアクセス可能です。
ほら、どうぞ。これで、この動作が完全にカプセル化されます。
完全なブログ投稿(jQuery に関する考慮事項を含む)
クロージャは説明が難しいです。なぜなら、クロージャは、誰もが直観的に動作すると期待する動作を動作させるために使用されるからです。それらを説明する最良の方法 (およびそれらが何をするかを学んだ方法) は、それらのない状況を想像することです。
const makePlus = function(x) {
return function(y) { return x + y; };
}
const plus5 = makePlus(5);
console.log(plus5(3));
JavaScriptがクロージャーを知らなかったら、ここで何が起こるでしょうか? 最後の行の呼び出しをそのメソッド本体 (基本的には関数呼び出しが行うこと) に置き換えるだけで、次のようになります。
console.log(x + 3);
では、 の定義はどこにあるのx
でしょうか? 現在のスコープでは定義していません。唯一の解決策は、そのスコープ (またはその親のスコープ) をplus5
持ち歩くことです。このように、x
明確に定義されており、値 5 にバインドされています。
TLDR
クロージャは、関数とその外部レキシカル (つまり、記述されたままの) 環境との間のリンクであり、その環境内で定義された識別子 (変数、パラメーター、関数宣言など) が、いつまたはどこからでも、関数内から見えるようになります。関数が呼び出される場所。
詳細
ECMAScript 仕様の用語では、クロージャーは、関数が定義されているレキシカル環境[[Environment]]
を指すすべての関数オブジェクトの参照によって実装されると言えます。
[[Call]]
内部メソッドを介して関数が呼び出されると、関数オブジェクトの参照が、新しく作成された実行コンテキスト(スタック フレーム)の環境レコードの外部環境参照[[Environment]]
にコピーされます。
次の例では、関数f
はグローバル実行コンテキストの字句環境を閉じます。
function f() {}
次の例では、 functionh
は function のレキシカル環境をg
閉じ、次にグローバル実行コンテキストのレキシカル環境を閉じます。
function g() {
function h() {}
}
内部関数が外部関数によって返された場合、外部関数が返された後も外部レキシカル環境が持続します。これは、最終的に内部関数が呼び出された場合に、外部レキシカル環境が利用可能である必要があるためです。
次の例では、 functionj
は function の字句環境を閉じます。これは、 functionが実行を完了した後も、 variableが function 内から見えるi
ことを意味します。x
j
i
function i() {
var x = 'mochacchino'
return function j() {
console.log('Printing the value of x, from within function j: ', x)
}
}
const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms
クロージャでは、コピーではなく、外側のレキシカル環境自体の変数が利用可能です。
function l() {
var y = 'vanilla';
return {
setY: function(value) {
y = value;
},
logY: function(value) {
console.log('The value of y is: ', y);
}
}
}
const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate
外部環境参照を介して実行コンテキスト間でリンクされたレキシカル環境のチェーンは、スコープチェーンを形成し、任意の関数から見える識別子を定義します。
明確さと正確さを向上させるために、この回答は元の回答から大幅に変更されていることに注意してください。
OK、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");
これは、他のいくつかの回答に見られる閉鎖に関するいくつかの(可能性のある)誤解を解消する試みです。
閉鎖について説明するブログ投稿をしばらく前に書きました。クロージャーが必要な理由に関して、クロージャーについて私が言ったことは次のとおりです。
クロージャーは、関数に永続的なプライベート変数を持たせる方法です。つまり、1 つの関数だけが知っている変数であり、以前に実行されたときからの情報を追跡できます。
その意味で、関数はプライベート属性を持つオブジェクトのように機能します。
完全な投稿:
私はまだ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);
* AC#の質問
良い点と悪い点を比較することで、よりよく学ぶ傾向があります。動作するコードの後に、誰かが遭遇する可能性のある動作しないコードが続くのを見るのが好きです。私は 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
ことを強調するために、変数に名前を付けたことに注意してください。index
これにより、新しいスコープが作成n
され、そのスコープにバインドされます。これは、反復ごとに 1 つずつ、合計 10 の個別のスコープがあることを意味します。
createClosure(n)
そのスコープ内で n を返す関数を返します。
各スコープ内でn
は、呼び出されたときに持っていた値にバインドされるため、返されるネストされた関数は、呼び出されたときに持っていたcreateClosure(n)
値を常に返します。n
createClosure(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 つではなく、この関数に対して 1 つのスコープのみが作成されることです。
この関数内で、という名前の変数index
が定義されています。ループが実行され、 を返す配列に関数が追加されますindex
。一度だけ呼び出される関数index
内で定義されていることに注意してください。createClosureArray
createClosureArray()
関数内にスコープが 1 つしかないため、index
はそのスコープ内の値にのみバインドされます。言い換えると、ループが の値を変更するたびに、index
そのスコープ内でそれを参照するすべての値を変更します。
index
配列に追加されたすべての関数は、最初の例のように 10 の異なるスコープから 10 の異なる変数を返すのではなく、定義された親スコープから SAME変数を返します。最終結果は、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
上記の例は、1回実行された無名関数を使用しています。しかし、そうである必要はありません。名前を付けて(たとえばmkdb
)後で実行して、呼び出されるたびにデータベース関数を生成できます。生成されたすべての関数には、独自の非表示のデータベースオブジェクトがあります。クロージャの別の使用例は、関数を返さないが、異なる目的のために複数の関数を含むオブジェクトであり、それらの関数のそれぞれが同じデータにアクセスできる場合です。
クロージャがどのように機能するかを説明するインタラクティブな JavaScript チュートリアルをまとめました。 閉鎖とは何ですか?
例の 1 つを次に示します。
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
dlaliberteによる最初のポイントの例:
クロージャは、内部関数を返すときに作成されるだけではありません。実際、囲んでいる関数はまったく戻る必要はありません。代わりに、内部関数を外部スコープの変数に割り当てるか、すぐに使用できる別の関数に引数として渡すことができます。したがって、包含関数のクロージャは、包含関数が呼び出された時点ですでに存在している可能性があります。これは、内部関数が呼び出されるとすぐにアクセスできるためです。
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);
クロージャーは、内部関数が外部関数の変数にアクセスできる場所です。これはおそらく、クロージャーについて得られる最も単純な 1 行の説明です。
すでに多くの解決策があることは知っていますが、この小さくて単純なスクリプトは、概念を示すのに役立つと思います。
// 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 つ持ってくるように言いました。
ダンはポールを招待します。ダンはポールにコントローラーを 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.");
Closuresの著者は、クロージャーについてかなりよく説明しており、クロージャーが必要な理由を説明し、クロージャーを理解するために必要な LexicalEnvironment についても説明しています。
要約は次のとおりです。
変数がアクセスされたが、それがローカルではない場合はどうなるでしょうか? ここみたいに:
この場合、インタプリタは外部LexicalEnvironment
オブジェクトで変数を見つけます。
このプロセスは、次の 2 つのステップで構成されます。
関数が作成されると、現在の LexicalEnvironment を参照する [[Scope]] という名前の隠しプロパティを取得します。
変数が読み取られてもどこにも見つからない場合、エラーが生成されます。
ネストされた関数
関数は互いに入れ子にすることができ、スコープチェーンとも呼ばれる LexicalEnvironments のチェーンを形成します。
したがって、関数 g は g、a、および f にアクセスできます。
閉鎖
ネストされた関数は、外側の関数が終了した後も存続する場合があります。
LexicalEnvironments のマークアップ:
ご覧のとおり、this.say
はユーザー オブジェクトのプロパティであるため、User が完了した後も存続します。
覚えていると思いますが、が作成されると、(すべての関数と同様に)現在の LexicalEnvironment へのthis.say
内部参照が取得されます。this.say.[[Scope]]
そのため、現在の User 実行の LexicalEnvironment はメモリに残ります。User のすべての変数もそのプロパティであるため、通常のように破棄されるのではなく、慎重に保持されます。
全体のポイントは、内部関数が将来外部変数にアクセスしたい場合に、アクセスできるようにすることです。
要約する:
これを閉鎖と呼びます。
JavaScript 関数は以下にアクセスできます。
関数がその環境にアクセスする場合、その関数はクロージャーです。
外部関数は必須ではありませんが、ここでは説明しませんが、利点があります。環境内のデータにアクセスすることにより、クロージャーはそのデータを存続させます。外部/内部関数のサブケースでは、外部関数はローカル データを作成し、最終的に終了できますが、外部関数が終了した後も内部関数が残っている場合、内部関数は外部関数のローカル データを保持します。生きている。
グローバル環境を使用するクロージャの例:
スタック オーバーフローの Vote-Up および Vote-Down ボタン イベントが、グローバルに定義された外部変数 isVotedUp および isVotedDown にアクセスできるクロージャー、voteUp_click および voteDown_click として実装されていると想像してください。(わかりやすくするために、回答投票ボタンの配列ではなく、StackOverflow の質問投票ボタンを参照しています。)
ユーザーが [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 つの関数はすべてクロージャーであり、すべて環境にアクセスします。
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);
手順
DATA: データは事実の集まりです。数字、単語、測定値、観察、または単に物事の説明でさえあります。触ったり、嗅いだり、味わったりすることはできません。書き留めたり、話したり、聞いたりできます。コンピューターを使用して、タッチの匂いと味を作成するために使用できます。コードを使用してコンピューターで使用できるようにすることができます。
CODE: 上記のすべての記述をcodeと呼びます。JavaScriptで書かれています。
JAVASCRIPT: JavaScript は言語です。英語やフランス語、中国語などは言語です。コンピュータやその他の電子プロセッサが理解できる言語はたくさんあります。JavaScript がコンピューターに理解されるためには、インタープリターが必要です。ロシア語しか話せない先生が学校であなたのクラスを教えに来たら想像してみてください。先生が「все садятся」と言うと、クラスは理解できません。しかし、幸運なことに、あなたのクラスにロシア人の生徒がいて、これは「みんな座って」という意味だと言いました。クラスはコンピューターのようなもので、ロシアの生徒は通訳者です。JavaScript の場合、最も一般的なインタープリターはブラウザーと呼ばれます。
ブラウザー: コンピューター、タブレット、または電話でインターネットに接続して Web サイトにアクセスするときは、ブラウザーを使用します。ご存知かもしれませんが、Internet Explorer、Chrome、Firefox、および Safari がその例です。ブラウザーは JavaScript を理解して、何をする必要があるかをコンピューターに伝えることができます。JavaScript 命令は関数と呼ばれます。
FUNCTION: JavaScript の関数は、工場のようなものです。中に機械が一台しかない小さな工場かもしれません。あるいは、それぞれが異なる仕事をしている多くの機械を備えた、他の多くの小さな工場を含んでいるかもしれません。実際の衣料品工場では、大量の布と糸のボビンが入ってきて、T シャツとジーンズが出てくるかもしれません。私たちの JavaScript ファクトリはデータを処理するだけで、縫ったり、穴を開けたり、金属を溶かしたりすることはできません。私たちの JavaScript ファクトリでは、データが入り、データが出てきます。
このすべてのデータは少し退屈に聞こえますが、実際には非常にクールです。夕食に何を作るかをロボットに伝える関数があるかもしれません。あなたとあなたの友達を私の家に招待するとしましょう。あなたは鶏の足が一番好き、私はソーセージが好き、あなたの友達はいつもあなたが欲しいものを欲しがる、そして私の友達は肉を食べない
買い物に行く時間がないので、決定を下すために関数は冷蔵庫に何があるかを知る必要があります。食材ごとに調理時間が異なるため、すべてをロボットが同時に熱々で提供したいと考えています。関数に好きなものに関するデータを提供する必要があります。関数は冷蔵庫と「会話」でき、関数はロボットを制御できます。
関数には通常、名前、括弧、および中括弧があります。このような:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
ブラウザによるコードの読み取り/*...*/
を停止することに注意してください。//
名前: 関数は、好きな言葉で呼び出すことができます。「cookMeal」の例では、2 つの単語を結合し、2 番目の単語の先頭を大文字にするのが一般的ですが、これは必須ではありません。スペースを入れることはできず、それ自体で数字にすることはできません。
PARENTHESES: 「括弧」()
は、JavaScript 関数ファクトリのドアにあるレター ボックス、または情報のパケットをファクトリに送信するための道路の郵便ポストです。場合によっては、郵便受けにたとえば cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
のマークが付けられることがあります。この場合、どのデータを指定する必要があるかがわかります。
BRACES: このように見える「ブレース」{}
は、当社の工場の着色された窓です。工場の中からは外が見えますが、外からは中が見えません。
上記の長いコード例
私たちのコードはfunctionという単語で始まるので、それが 1 であることがわかります! それから関数の名前が歌います- それは関数が何であるかについての私自身の説明です. 次に括弧()。関数には必ず括弧が付きます。空の場合もあれば、何かが入っている場合もあります。これには次の単語があります(person)
。この後、このようなブレースがあります{
。これは関数sing()の開始を示します。このようにsing()の終わりを示すパートナーがあります}
function sing(person) { /* STUFF INSIDE THE FUNCTION */ }
したがって、この関数は歌に関係している可能性があり、人物に関するデータが必要になる場合があります。そのデータで何かをするための指示が内部にあります。
さて、関数sing()の後、コードの終わり近くに次の行があります
var person="an old lady";
VARIABLE: 文字varは「変数」を表します。変数は封筒のようなものです。この封筒の外側には「人」と記されています。内側には、機能に必要な情報が書かれた紙片が含まれており、いくつかの文字とスペースが紐のように結合されており (これは紐と呼ばれます)、「おばあさん」というフレーズを構成しています。エンベロープには、数値 (整数と呼ばれる)、命令 (関数と呼ばれる)、リスト (配列と呼ばれる) など、他の種類のものを含めることができます。この変数はすべての中かっこの外側に記述されており{}
、中かっこの内側にいると色付きの窓から外が見えるため、この変数はコードのどこからでも見ることができます。これを「グローバル変数」と呼びます。
GLOBAL VARIABLE: personはグローバル変数です。つまり、その値を「an old lady」から「a young man」に変更すると、その値を再度変更するまでその人は若い男性であり続け、その他の関数はコードはそれが若い男であることを確認できます。ボタンを押すか、F12[オプション] 設定を見て、ブラウザーの開発者コンソールを開き、「person」と入力してこの値を確認します。入力person="a young man"
して変更し、もう一度「person」と入力して、変更されたことを確認します。
この後、次の行があります
sing(person);
この行は、まるで犬を呼んでいるかのように関数を呼び出しています
「さあ、歌って、来て、人をつかまえて!」
ブラウザが JavaScript コードをロードしてこの行に到達すると、関数が開始されます。実行に必要なすべての情報がブラウザにあることを確認するために、行を最後に追加しました。
関数はアクションを定義します - 主な関数は歌うことです。これには、歌の各節に適用される人物についての歌に適用されるfirstPartという変数が含まれています。コンソールに「 firstPart」と入力しても、変数が関数内でロックされているため、応答が得られません。ブラウザーは、ブレースの着色されたウィンドウ内を見ることができません。
クロージャー: クロージャーは、大きなsing()関数内にある小さな関数です。大きな工場の中の小さな工場。それらにはそれぞれ独自のブレースがあり、その中の変数は外側からは見えません。そのため、変数の名前 ( creativeとresult ) をクロージャーで繰り返すことができますが、値は異なります。これらの変数名をコンソール ウィンドウに入力しても、2 層の色付きウィンドウによって隠されているため、その値を取得できません。
クロージャーはすべて、sing()関数の変数firstPartが何であるかを知っています。
クロージャーの後にラインが来る
fly();
spider();
bird();
cat();
sing() 関数は、これらの各関数を指定された順序で呼び出します。次に、sing() 関数の作業が行われます。
さて、6歳の子供と話すと、私はおそらく次の協会を利用するでしょう。
想像してみてください。あなたは家全体で弟や妹と遊んでいて、おもちゃを持って動き回って、その一部を兄の部屋に持ってきました。しばらくして、あなたの兄は学校から戻って彼の部屋に行きました、そして彼はその中に閉じ込められました、それであなたはもうそこに残っているおもちゃに直接の方法でアクセスすることができませんでした。しかし、あなたはドアをノックして、そのおもちゃをあなたの兄弟に頼むことができました。これはおもちゃのクロージャーと呼ばれます; あなたの兄弟はあなたのためにそれを作りました、そして彼は今外側の範囲に入っています。
ドラフトでドアがロックされ、中に誰もいない状態(一般的な機能の実行)と比較すると、局所的な火災が発生して部屋が焼失し(ガベージコレクター: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です。
JavaScript の関数は、(C 言語のように) 一連の命令への単なる参照ではなく、使用するすべての非ローカル変数 (キャプチャされた変数) への参照で構成される隠しデータ構造も含みます。このような 2 つの部分からなる関数は、クロージャと呼ばれます。JavaScript のすべての関数は、クロージャーと見なすことができます。
クロージャーは、状態を持つ関数です。これは、「this」も関数の状態を提供するという意味で「this」に似ていますが、関数と「this」は別のオブジェクトです (「this」は単なる派手なパラメーターであり、それを永続的にバインドする唯一の方法です)。関数はクロージャを作成することです)。「this」と関数は常に別々に存在しますが、関数をそのクロージャから分離することはできず、言語はキャプチャされた変数にアクセスする手段を提供しません。
字句的にネストされた関数によって参照されるこれらすべての外部変数は、実際にはその字句的に囲んでいる関数のチェーン内のローカル変数であり (グローバル変数は、いくつかのルート関数のローカル変数であると見なすことができます)、関数の実行ごとに新しいインスタンスが作成されるためです。そのローカル変数の場合、関数を実行するたびに、関数を返す (または、コールバックとして登録するなどして転送する) と、ネストされた関数が新しいクロージャーを作成することになります (その実行を表す、参照される非ローカル変数の潜在的に一意のセットを持つ)環境)。
また、JavaScript のローカル変数はスタック フレームではなくヒープ上に作成され、誰も参照していない場合にのみ破棄されることを理解する必要があります。関数が戻ると、そのローカル変数への参照はデクリメントされますが、現在の実行中にそれらがクロージャーの一部になり、レキシカルにネストされた関数によって参照されている場合は、まだ null ではない可能性があります (これは、への参照がこれらのネストされた関数は、返されるか、外部コードに転送されます)。
例:
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();
私は単に彼らにMozillaClosuresページを指さします。これは、私が見つけたクロージャーの基本と実際の使用法についての最も簡潔で簡単な説明です。JavaScriptを学ぶ人には強くお勧めします。
そして、はい、私はそれを6歳にもお勧めします-6歳が閉鎖について学んでいるなら、彼らが記事で提供される簡潔で簡単な説明を理解する準備ができているのは論理的です。
JavaScript では、変数や引数を内部関数で使用できるクロージャーは素晴らしくユニークで、外部関数が戻った後でも有効です。クロージャーは、JS のほとんどのデザイン パターンで使用されます。
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
6歳用?
あなたとあなたの家族は、神話の町アン ヴィルに住んでいます。隣に友達が住んでいるので、電話して遊びに来てもらいます。ダイヤルします:
000001 (ジェイミーハウス)
1 か月後、あなたとあなたの家族はアン ヴィルから次の町に引っ越しますが、あなたとあなたの友人はまだ連絡を取り合っています。適切な番号:
001 000001 (annVille.jamiesHouse)
それから 1 年後、あなたの両親はまったく新しい国に引っ越しましたが、あなたとあなたの友人はまだ連絡を取り合っています。そのため、国際料金の通話を許可するように両親を悩ませた後、あなたは今、次のようにダイヤルします。
01 001 000001 (myOldCountry.annVille.jamiesHouse)
不思議なことに、新しい国に引っ越した後、あなたとあなたの家族はたまたまアン・ヴィルという新しい町に引っ越しました...そしてあなたはたまたまジェイミーという新しい人と友達になりました...あなたは彼らに電話...
000001 (ジェイミーハウス)
不気味な...
実際、とても不気味なので、あなたは故郷のジェイミーにそのことを話します.あなたはそれについて大笑いしています. ある日、あなたとあなたの家族は休暇を取り、古い国に戻ります。あなたは自分の旧市街(アン・ヴィル)を訪れ、ジェイミーに会いに行きます...
02 001 000001 (myNewCountry.annVille.jamiesHouse)
意見?
その上、現代の 6 歳児の忍耐力について、私はたくさんの疑問を持っています...
これは、単純なリアルタイム シナリオです。これを読んでいただければ、ここでどのように閉鎖が使用されているかを理解していただけます (座席番号がどのように変化しているかをご覧ください)。
前に説明した他のすべての例も、概念を理解するのに非常に役立ちます。
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
これが私が与えることができる最も禅の答えです:
このコードは何をすると思いますか? 実行する前にコメントで教えてください。私は興味がある!
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 の初心者 - 末尾の「未定義」は無視してください)、言葉のない理解がすでにできています。 つまり、変数i
は内部関数インスタンスのクロージャーの一部です。
foo()
このように表現したのは、このコードがの内部関数のインスタンスを配置し、それらの変数を介してそれらを呼び出していることを理解したらbar
、baz
他に何も驚かなかったからです。
しかし、私が間違っていて、コンソールの出力に驚かされた場合は、お知らせください。
(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という本から:
優れたメンタル モデルは、関数の値を、その本体内のコードとそれらが作成された環境の両方を含むものと考えることです。呼び出されると、関数本体は、呼び出しが行われた環境ではなく、元の環境を認識します。
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);
}
}
別の言語でa がどのように機能するかを知っていると仮定してclass
、類推を行います。
のように考える
function
としてconstructor
local variables
なのでinstance properties
properties
はプライベートですinner functions
なのでinstance methods
afunction
が呼び出されるたびに
object
すべてのローカル変数を含む新しいが作成されます。"properties"
このオブジェクトのメソッドは、そのインスタンス オブジェクトにアクセスできます。イラスト付きの説明をご覧ください: JavaScript クロージャーは舞台裏でどのように機能しますか。
この記事では、スコープ オブジェクト (または s) がどのようにLexicalEnvironment
割り当てられ、直感的な方法で使用されるかについて説明します。同様に、この単純なスクリプトの場合:
"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()
呼び出されると、次のスコープ チェーンが作成されます。
スコープ オブジェクトがどのように作成、使用、削除されるかを理解することは、全体像を把握し、内部でクロージャがどのように機能するかを理解するための鍵です。
詳細については、前述の記事を参照してください。
クロージャーは、 「親」関数の変数とパラメーターにアクセスできる関数内の関数です。
例:
function showPostCard(Sender, Receiver) {
var PostCardMessage = " Happy Spring!!! Love, ";
function PreparePostCard() {
return "Dear " + Receiver + PostCardMessage + Sender;
}
return PreparePostCard();
}
showPostCard("Granny", "Olivia");
関数は、それが定義されているオブジェクト/関数のスコープで実行されます。上記の関数は、実行中に定義されたオブジェクト/関数で定義された変数にアクセスできます。
そして、文字通りそれを受け取ってください....コードが書かれているので:P
個人のブログ投稿から:
デフォルトでは、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;
}
}
ここで、 functiona
は という関数を返しますb
。b
は 内で定義されているため、 で定義されているもの、つまりこの例a
のすべてに自動的にアクセスできます。これが、宣言せずに+を返すことができる理由です。a
x
b
x
y
x
var c = a(3);
変数c
には、パラメーター 3 を使用した への呼び出しの結果が割り当てられます。つまり、= 3 である関数のインスタンスです。つまり、b
は次と同等の関数になります。x
c
var c = function b(y) {
return 3 + y;
}
関数は、そのコンテキストで = 3 をb
覚えています。x
したがって:
var d = c(4);
値 3 + 4 を に割り当てますd
。つまり、7 です。
注意: 関数のインスタンスが作成された後に誰かがx
(たとえばx
= 22)の値を変更するb
と、これも反映され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 開発者が常に使用しているものですが、私たちはそれを当然のことと考えています。仕組みはそれほど複雑ではありません。意図的に使用する方法を理解することは複雑です。
最も単純な定義では(他の回答が指摘しているように)、クロージャーは基本的に別の関数内で定義された関数です。そして、その内部関数は、外部関数のスコープで定義された変数にアクセスできます。クロージャーを使用する最も一般的な方法は、グローバル スコープで変数と関数を定義し、その関数の関数スコープでそれらの変数にアクセスすることです。
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 ポインター」であり、getter と setter を確立して参照の受け渡しを開始する必要があります。JavaScript のように聞こえませんか? 強力な閉鎖に感謝します。
なぜ気にする必要があるのですか?
それらを使用するためにクロージャを意識する必要はありません。しかし、他の人も指摘しているように、それらを利用して偽のプライベート変数を作成できます。プライベート変数が必要になるまでは、いつものように使用してください。
内部関数ではなく、オブジェクト指向の構造を検討する必要があるかもしれません。例えば:
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 変数から結果を読み取ります。
//Addition
First think about scope which defines what variable you have to access to (In Javascript);
//there are two kinds of scope
Global Scope which include variable declared outside function or curly brace
let globalVariable = "foo";
覚えておくべきことの 1 つは、グローバル変数を宣言すると、関数内であってもコード内のどこでも使用できるということです。
コードの特定の部分でのみ使用できる変数を含むローカル スコープ:
関数スコープは、関数内で変数を宣言すると、関数内でのみ変数にアクセスできます
function User(){
let name = "foo";
alert(name);
}
alert(name);//error
//Block scope is when you declare a variable within a block then you can access that variable only within a block
{
let user = "foo";
alert(user);
}
alert(user);
//Uncaught ReferenceError: user is not defined at.....
//A Closure
function User(fname){
return function(lname){
return fname + " " lname;
}
}
let names = User("foo");
alert(names("bar"));
//When you create a function within a function you've created a closure, in our example above since the outer function is returned the inner function got access to outer function's scope
6歳の子供に説明したい場合は、非常に単純でコードのないものを見つける必要があります。
子供に「オープン」であることを伝えてください。これは、彼が他の人や友人と関係を持つことができるということです。ある時点で、彼は友人を決定しました(私たちは彼の友人の名前を知ることができます)、それは閉鎖です。あなたが彼と彼の友人の写真を撮ると、彼は彼の友情能力に比べて「閉じている」。しかし、一般的に、彼は「オープン」です。彼の生涯を通して、彼には多くの異なる友達がいます。これらのセットの1つはクロージャーです。
クロージャーは、親関数が既に終了した後、内側の関数が外側の囲み関数に存在する変数を参照できるようにする手段です。
// 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 );
関数が呼び出された後、スコープ外になります。その関数にコールバック関数のようなものが含まれている場合、そのコールバック関数はまだスコープ内にあります。コールバック関数が親関数の直近の環境でローカル変数を参照する場合、当然、その変数はコールバック関数にアクセスできず、未定義を返すと予想されます。
クロージャーは、親関数がスコープ外に出た場合でも、コールバック関数によって参照されるすべてのプロパティがその関数で使用できるようにします。
あなたの町に非常に大きな公園があり、Mr. Coder というマジシャンが JavaScript と呼ばれる魔法の杖を使って公園のさまざまなコーナーで野球の試合を始めているところを想像してみてください。
当然のことながら、野球の各試合にはまったく同じルールがあり、各試合には独自のスコアボードがあります。
当然のことながら、1 つの野球の試合のスコアは、他の試合とはまったく異なります。
クロージャーは、Mr.Coder がすべての魔法の野球ゲームの得点を分けておく特別な方法です。
よく理解できれば簡単に説明できます。最も簡単な方法は、コンテキストからそれを抽象化することです。コードはさておき、プログラミングもさておき。比喩的な例はそれをより良くします。
機能が、壁がガラスの部屋であると想像してみましょう。ただし、取り調べ室のような特別なガラスです。外側からは不透明で、内側からは透明です。他の部屋の中の部屋である場合もあり、連絡手段は電話のみです。
外部から電話をかければ、何が入っているかはわかりませんが、特定の情報を伝えれば、内部の人々がタスクを実行することはわかっています。彼らは外を見ることができるので、彼らはあなたに外にあるものを求め、そのものに変更を加えることができますが、外から内にあるものを変えることはできません。あなたが電話をかけているその部屋の中にいる人々は、それが外にあるものを見るが、その部屋の部屋の中にあるものは見えないので、彼らはあなたが外からしているように対話する. 最奥の部屋にいる人はいろいろなものを見ることができますが、最外の部屋にいる人は最奥の部屋の存在すら知りません。
内側の部屋への呼び出しごとに、その部屋にいる人々はその特定の呼び出しに関する情報の記録を保持します。彼らは非常に優れているため、ある呼び出しを他の呼び出しと間違えることはありません。
ルームは関数、可視性はスコープ、タスクを実行する人はステートメント、スタッフはオブジェクト、電話は関数呼び出し、電話の情報は引数、通話記録はスコープ インスタンス、最も外側の部屋はグローバル オブジェクトです。
クロージャーは、親関数が閉じた後でも、親スコープにアクセスできる関数です。
var add = (function() {
var counter = 0;
return function() {
return counter += 1;
}
})();
add();
add();
add();
// The counter is now 3
例の説明:
add
には、自己呼び出し関数の戻り値が割り当てられます。クロージャーはやや高度な機能ですが、よく誤解されている 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() の「<em>value1」および「<em>value2」引数が「<em>operand1」および「operand2」引数としてdoAdd()に渡されました。ただし、 doAdd()は「<em>value1」と「<em>value2」に直接アクセスできるため、これは不要です。doAdd()で「<em>value1」と「<em>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」は値 1 にバインドされます。バインドされた変数は、クローズド オーバーとも呼ばれます。これが名前の閉鎖の由来です。increment(2) が呼び出されると、クロージャーに入ります。これは、値 1 を保持する「value1」変数を使用して doAdd() が呼び出されることを意味します。クロージャーは基本的に、次の関数を作成するものと考えることができます。
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() に渡されないため)。この問題を回避するには、「メッセージ」引数を目的の値にバインドするクロージャを作成できます。クロージャーは、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 クラスのコンストラクターを示しています。各人物が作成されると、「<em>name」引数を介して名前が付けられます。内部的には、Person はその名前を「<em>_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 コンストラクターのローカル変数になっていることに注意してください。public getName()メソッドを作成することによって外部関数Person()が内部関数を公開するため、クロージャが形成されます。
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」と表示されます。これは、いずれかのボタンがクリックされるまでにループの実行が終了し、ループ変数が最終値の 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));
}
});
コンストラクターでの不必要な使用
コンストラクター関数は、クロージャーの誤用のもう 1 つの一般的な原因です。クロージャを使用してプライベート データをエミュレートする方法を見てきました。ただし、メソッドが実際にプライベート データにアクセスしない場合、メソッドをクロージャーとして実装するのはやり過ぎです。次の例では Person クラスを再度取り上げますが、今回はプライベート データを使用しない sayHello() メソッドを追加します。
function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
this.sayHello = function() {
alert("Hello!");
};
}
Person がインスタンス化されるたびに、sayHello() メソッドの作成に時間がかかります。多くの Person オブジェクトを作成すると、時間の無駄になります。より良いアプローチは、sayHello() を Person プロトタイプに追加することです。プロトタイプに追加することで、すべての Person オブジェクトが同じメソッドを共有できます。これにより、インスタンスごとにクロージャーを作成する必要がないため、コンストラクターでの時間を節約できます。前の例は、余分なクロージャをプロトタイプに移動して以下に書き直されています。
function Person(name) {
var _name = name;
this.getName = function() {
return _name;
};
}
Person.prototype.sayHello = function() {
alert("Hello!");
};
覚えておくべきこと
内部関数が何らかの方法で外部関数の外側のスコープで使用できるようになると、クロージャーが作成されます。
例:
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歳児に簡単に説明することだと考えると、私の答えは次のようになります。
「JavaScript で関数を宣言すると、その関数宣言の前の行で使用可能だったすべての変数と関数に永遠にアクセスできます。関数と、関数がアクセスできるすべての外部変数と関数は、クロージャーと呼ばれるものです。 "
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
}
Kyle Simpson のクロージャーの定義が好きです。
クロージャーとは、関数がレキシカル スコープ外で実行されている場合でも、関数がそのレキシカル スコープを記憶してアクセスできる場合です。
レキシカルスコープは、内側のスコープが外側のスコープにアクセスできるときです。
以下は、彼の書籍シリーズ「You Don't Know 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();
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
は、 は外側の関数 (環境関数) によって保持されるため、 を呼び出すたびcounter()
に、内側の関数 (作業関数) がそれをインクリメントできます。
クロージャーとは、関数が呼び出されるまでに不変である名前空間で定義された方法で関数が閉じられることです。
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 にオブジェクトを作成するためのクロージャーがあるのは幸運です。
かつて穴居人がいた
function caveman {
とても特別な岩を持っていた
var rock = "diamond";
穴居人の私有の洞窟にあったため、自分で岩を手に入れることはできませんでした。岩を見つけて手に入れる方法を知っていたのは穴居人だけでした。
return {
getRock: function() {
return rock;
}
};
}
幸いなことに、彼はフレンドリーな穴居人でした。もしあなたが彼の帰りを待っていれば、彼は喜んであなたのためにそれを手に入れるでしょう。
var friend = caveman();
var rock = friend.getRock();
かなり賢い穴居人。
クロージャは基本的に次の 2 つのものを作成します。 - 関数 - その関数だけがアクセスできるプライベート スコープ
これは、機能をコーティングするようなものです。
したがって、6 歳の子供には、類推を与えることで説明できます。私がロボットを作ったとしましょう。そのロボットは多くのことができます。その中で、彼が空で見た鳥の数を数えるようにプログラムしました。彼は 25 羽の鳥を見るたびに、最初から何羽の鳥を見たかを教えてください。
彼が私に言わない限り、彼が何羽の鳥を見たのかわかりません。彼だけが知っています。それがプライベートスコープです。それは基本的にロボットの記憶です。私が彼に 4 GB を与えたとしましょう。
彼が見た鳥の数を教えてくれる関数が返されます。私もそれを作成しました。
その例えは少し悪いですが、誰かがそれを改善できると思います.
過去にこれらすべてを読んだことがありますが、どれも非常に有益です。簡単な説明を得るのに非常に近づいた後、複雑になったり、抽象的なままになったりして、目的を破り、非常に単純な現実世界での使用を示すことができないものもあります.
すべての例と説明をくまなく調べて、コメントとコードを介してクロージャーとは何か、クロージャーではないことをよく理解しますが、それほど複雑になることなくクロージャーの有用性を得るのに役立つ非常に単純な図にはまだ満足していませんでした. 私の妻はコーディングを学びたがっています。私はここで、何を、なぜ、どのように、どのようにコーディングするかを示す必要があると考えました。
6 歳の子供がこれを理解できるかどうかはわかりませんが、実際に役に立ち、簡単に理解できる現実世界の方法での単純なケースのデモンストレーションに少し近いかもしれないと思います.
最良の (または最も単純な) 例の 1 つは、Morris の Closures for Dummies の例の再話です。
「SayHi2Bob」の概念をさらに一歩進めると、すべての回答から収集できる 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 プラクティス、シングルトンと独自のデータを持つインスタンス化されたインスタンスなどの基本的な概念につながります。
これで新人を開始する場合は、より複雑なオブジェクト プロパティ/メンバー ベースの呼び出しに進むことができ、できれば概念が引き継がれます。
JavaScript のクロージャーは、スコープの概念に関連付けられています。
es6 より前は、ブロック レベルのスコープはなく、JS には関数レベルのスコープしかありません。
つまり、ブロック レベルのスコープが必要な場合は常に、それを関数内にラップする必要があります。
このシンプルで興味深い例を確認してください。クロージャーがES5でこの問題をどのように解決するか
// let say we can only use a traditional for loop, not the forEach
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log('without closure the visited index - '+ i)
})
}
// this will print 10 times 'visited index - 10', which is not correct
/**
Expected output is
visited index - 0
visited index - 1
.
.
.
visited index - 9
**/
// we can solve it by using closure concept
//by using an IIFE (Immediately Invoked Function Expression)
// --- updated code ---
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(function() {
console.log('with closure the visited index - '+ i)
})
})(i);
}
注意: let はレキシカルスコープを作成するため、let
の代わりにes6 を使用することで簡単に解決できます。var
JavaScript クロージャーを説明するために考えられる最も簡単な使用例は、モジュール パターンです。Module パターンでは、関数を定義し、すぐに呼び出される関数式 (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();
それが役に立てば幸い。よろしく、
また... おそらく、「閉鎖」の全体的な概念は実際には(!) ...ブードゥーです!
つまり、(a)あなたは直感的にそれを期待していません...そして... (b)誰かがあなたに説明するのに時間がかかるとき、あなたは確かにそれがうまくいくとは思っていません!
直観は、「これはナンセンスに違いない...確かに何らかの構文エラーか何かが発生するに違いない!」 と教えてくれます。いったいどうやって (!)実際には、「どこでも」のコンテキストへの読み取り/書き込みアクセス権を実際に持つことができるように、「「どこでも」の「真ん中」から関数を引き出すことができますか?だった?!」
そのようなことが可能であることに最終的に気付いたとき、...確かに...誰もが事後の反応であるでしょう: !)」
しかし、最初に克服しなければならない「直感に反する大きなハードル」があります。直感は、そのようなことは「もちろんまったく無意味であり、したがってまったく不可能」であるという、まったくもっともらしい期待をたくさん与えてくれます。
私が言ったように:「それはブードゥーです」。
クロージャーは、各行が同じ変数名を持つ同じ変数セットを参照できるコードのブロックです。
「これ」が他の場所とは異なる意味を持っている場合、それは 2 つの異なるクロージャであることがわかります。
クロージャーは、それが定義された環境からの情報にアクセスできる関数です。
一部の情報は、作成時の環境での値です。その他の場合、情報は作成時の環境の変数です。
クロージャが参照するレキシカル環境が、終了した関数に属している場合、(環境内の変数を参照するクロージャの場合) それらのレキシカル変数は、クロージャによる参照のために存在し続けます。
クロージャーは、グローバル変数の特殊なケースと考えることができます-関数のためだけに作成されたプライベートコピーを使用します。
または、環境がオブジェクトの特定のインスタンスであり、そのプロパティが環境内の変数である方法と考えることができます。
前者 (環境としてのクロージャ) は後者と同様で、前者では環境コピーが各関数に渡されるコンテキスト変数であり、後者ではインスタンス変数がコンテキスト変数を形成します。
したがって、クロージャーは、メソッド呼び出しでコンテキストをパラメーターまたはオブジェクトとして明示的に指定する必要なく、関数を呼び出す方法です。
var closure = createclosure(varForClosure);
closure(param1); // closure has access to whatever createclosure gave it access to,
// including the parameter storing varForClosure.
対
var contextvar = varForClosure; // use a struct for storing more than one..
contextclosure(contextvar, param1);
対
var contextobj = new contextclass(varForClosure);
contextobj->objclosure(param1);
保守可能なコードについては、オブジェクト指向の方法をお勧めします。ただし、迅速かつ簡単な一連のタスク (コールバックの作成など) の場合、特にラムダ関数または無名関数のコンテキストでは、クロージャが自然でより明確になる可能性があります。