JavaScriptクロージャの構成概念(関数、変数など)を知っている人に、JavaScriptクロージャをどのように説明しますか?しかし、クロージャ自体は理解していませんか?
WikipediaにあるSchemeの例を見たことがありますが、残念ながら役に立ちませんでした。
closure
コードの利点Object Literal
を知りました。これは、コード自体を再利用してオーバーヘッドを減らすのとは対照的ですが、必要なラッピングコードは100%少ないのです。
JavaScriptクロージャの構成概念(関数、変数など)を知っている人に、JavaScriptクロージャをどのように説明しますか?しかし、クロージャ自体は理解していませんか?
WikipediaにあるSchemeの例を見たことがありますが、残念ながら役に立ちませんでした。
closure
コードの利点Object Literal
を知りました。これは、コード自体を再利用してオーバーヘッドを減らすのとは対照的ですが、必要なラッピングコードは100%少ないのです。
回答:
クロージャーは次の組み合わせです。
字句環境は、すべての実行コンテキスト(スタックフレーム)の一部であり、識別子(つまり、ローカル変数名)と値の間のマップです。
JavaScriptのすべての関数は、外部の字句環境への参照を維持します。この参照は、関数が呼び出されたときに作成される実行コンテキストを構成するために使用されます。この参照により、関数がいつ、どこで呼び出されたかに関係なく、関数内のコードが関数外で宣言された変数を「参照」できるようになります。
関数が関数によって呼び出され、その関数が別の関数によって呼び出された場合、外部の字句環境への参照のチェーンが作成されます。このチェーンは、スコープチェーンと呼ばれます。
次のコードでinner
は、foo
が呼び出されたときに作成された実行コンテキストの字句環境でクロージャーを形成し、variableをクローズします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
次のコードでは、関数onClick
は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 = function() { console.log(x) }
x = x+1
return inner
}
var f = foo()
f() // logs 43
次のコードでは、3つのメソッドlog
、increment
および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
場合は、閉じる変数を理解するように注意してください。を使用し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(…)
(関数コンストラクター)を使用する場合、そのレキシカル環境ではなく、代わりにグローバルコンテキストで閉じます。新しい関数は、外部関数のローカル変数を参照できません。Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.
から
let i = 0
代わりにを使用するとvar i = 0
、testList()
最初に必要なものが印刷されます。
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
。
Functionはbar
、関数の字句環境とのリンクとともに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
。
すべての関数は外部の字句環境へのリンクを持っているため、すべての関数はクロージャーを作成します。
変数自体はコピーではなく、クロージャー内から見えることに注意してください。
delete
が失敗したため、実際には何も証明されないことがわかりました。それにもかかわらず、関数が[[Scope]]として持ち運ぶ(そして最終的には、呼び出されたときに独自の字句環境のベースとして使用される)字句環境は、関数を定義するステートメントが実行されるときに決定されます。これは、関数が実際に参照する値やスコープをエスケープするかどうかに関係なく、関数が実行スコープの全内容をクローズしていることを意味します。仕様の
序文:この回答は、質問が次のようなときに書かれたものです。
古いアルバートが言ったように:「あなたがそれを6歳に説明できない場合、あなたは本当にそれを自分で理解できません。」さて、私はJSの閉鎖を27歳の友人に説明しようとしましたが、完全に失敗しました。
誰かが私が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();
そして、大人は本当の王女を知っていたとしても、ユニコーンやドラゴンを見ることができなかったので、彼らは決して信じませんでした。大人たちは少女たちの想像の中にしか存在しなかったと言いました。
しかし、私たちは真実を知っています。王女が中にいる少女...
...本当に小さな女の子がいるプリンセスです。
story()
、littleGirl
インスタンスが魔法の世界に公開する唯一のインターフェースである関数のクロージャーに取り込まれます。
story
閉鎖ですが、コードがあったvar story = function() {}; return story;
後、littleGirl
閉鎖となります。少なくともそれは、MDN がクロージャを
story
スコープ内で提供される環境を参照するクロージャーですprincess
。princess
また別です暗黙のクロージャでます。つまり、princess
およびは、存在およびが定義されている環境/スコープ内に存在littleGirl
するparents
配列への参照を共有します。littleGirl
princess
princess
記述されたものよりも本文内に多くの操作があることを示す/暗示する明示的なコードコメントを追加しました。残念ながら、この話は今やこのスレッドでは少し場違いです。元々の質問は、「5歳までのJavaScriptクロージャを説明する」ことでした。私の返答はそれを試みた唯一のものでした。それが無残に失敗したことは間違いありませんが、少なくともこの応答には5歳の興味を抱く機会があったかもしれません。
質問を真剣に受け止めて、JavaScriptに興味がある人はそれほど典型的ではありませんが、典型的な6歳の人が認知的に何ができるかを見つける必要があります。
上の 子供の頃の開発: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のクロージャー動作を使用しています。この動作により、どの関数も、それが作成されたスコープに無期限にアクセスできます。これを実際に適用するには、別の関数を返す関数をすぐに呼び出します。返される関数は内部の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変数はクロージャー内に含まれ、常にイベントハンドラーで使用できるため、クリックからクリックまでその状態が保持されます。
また、このプライベート変数の状態は、読み取りとプライベートスコープ変数への割り当ての両方で、完全にアクセス可能です。
はい、どうぞ。これで、この動作が完全にカプセル化されました。
完全なブログ投稿(jQueryの考慮事項を含む)
クロージャは、誰もがとにかく直感的に機能することを期待する動作を機能させるために使用されるため、説明が困難です。それらを説明する最良の方法(および彼らが何をするかを私が学んだ方法)は、それらのない状況を想像することです。
var bind = function(x) {
return function(y) { return x + y; };
}
var plus5 = bind(5);
console.log(plus5(3));
JavaScript がクロージャーを知らなかった場合、ここで何が起こるでしょうか?最後の行の呼び出しをメソッド本体(基本的には関数呼び出しが行うことです)に置き換えるだけで、次のようになります。
console.log(x + 3);
さて、の定義はx
どこですか?現在のスコープでは定義していません。唯一の解決策は、そのスコープ(または、その親のスコープ)をplus5
持ち歩くことです。この方法x
は明確に定義されており、値5にバインドされています。
TLDR
クロージャーは、その環境内で定義された識別子(変数、パラメーター、関数宣言など)がいつ、どこからでも関数内から見えるようにする、関数とその外側の字句(つまり、作成時)環境の間のリンクです。関数が呼び出される場所。
詳細
ECMAScript仕様の用語では、クロージャーは、関数が定義され[[Environment]]
ている字句環境を指すすべての関数オブジェクトの参照によって実装されると言えます。
内部[[Call]]
メソッドを介して関数が呼び出される[[Environment]]
と、関数オブジェクトの参照が、新しく作成された実行コンテキスト(スタックフレーム)の環境レコードの外部環境参照にコピーされます。
次の例では、関数f
はグローバル実行コンテキストの字句環境を閉じます。
function f() {}
次の例では、function h
がfunction のレキシカル環境をg
閉じ、次にそれがグローバル実行コンテキストのレキシカル環境を閉じます。
function g() {
function h() {}
}
内部関数が外部関数によって返された場合、外部関数が返された後、外部レキシカル環境が存続します。これは、内部関数が最終的に呼び出される場合、外部レキシカル環境が利用可能である必要があるためです。
次の例では、functionは、function j
の字句環境を閉じますi
。つまり、functionの実行が完了した後も、function 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
外部環境参照を介して実行コンテキスト間でリンクされたレキシカル環境のチェーンは、スコープチェーンを形成し、特定の関数から可視の識別子を定義します。
わかりやすさと正確さを向上させるために、この回答は元の回答から大幅に変更されていることに注意してください。
console.log
そのような場合に文字列置換を使用できることを知りませんでした。誰にも興味を持っている場合は、よりありますdeveloper.mozilla.org/en-US/docs/DOM/...
var
)。
はい、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つの関数のみが認識している変数)を持たせる方法であり、実行された以前の時間からの情報を追跡できます。
その意味で、関数はプライベート属性を持つオブジェクトのように機能します。
完全な投稿:
devError = emailError("devinrhode2@googmail.com", errorString)
関数があるとしたら、共有されたemailError関数の独自のカスタムバージョンを持つことができますか?
次の簡単な例は、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
。このローカル変数は、返された後もずっと、その計算機add
とmultiply
関数で引き続き使用できmake_calculator
ます。
スタックフレームに精通している場合、これらの計算機は奇妙に見えます。どのようにしてn
、make_calculator
戻ってきた後もアクセスし続けることができますか?答えは、JavaScriptが「スタックフレーム」を使用せず、代わりに「ヒープフレーム」を使用することを想像することです。これは、それらを作成した関数呼び出しが戻った後も存続する可能性があります。
add
andなどの内部関数multiply
は、外部関数**で宣言された変数にアクセスし、クロージャーと呼ばれます。
クロージャについては、これでほぼすべてです。
*たとえば、例6を除いて、別の回答で示されている「Closures for Dummies」記事のすべてのポイントをカバーしています。例6は、変数が宣言される前に使用できることを示しています。知っておくと便利ですが、クロージャーとはまったく無関係です。また、(1)関数が引数をローカル変数(名前付き関数の引数)にコピーする点と、(2)数値をコピーすると新しい数値が作成されるが、オブジェクト参照をコピーする点を除いて、受け入れられた回答のすべての点をカバーします同じオブジェクトへの別の参照を提供します。これらも知っておくと良いですが、クロージャーとはまったく無関係です。この回答、が、少し短く、抽象的なものではありません。それはのポイントをカバーしていませんこの回答、またはこのコメントの例にも非常に似ています、つまり、JavaScriptを使用すると、現在の内部関数へのループ変数の値:「プラグイン」ステップは、内部関数を囲み、各ループ反復で呼び出されるヘルパー関数でのみ実行できます。(厳密に言えば、内部関数はプラグインされているものではなく、ヘルパー関数の変数のコピーにアクセスします。)繰り返しになりますが、クロージャーを作成するときに非常に役立ちますが、クロージャーの機能や機能の一部ではありません。MLのような関数型言語ではクロージャーが異なる動作をするため、混乱がさらにあります。変数はストレージスペースではなく値にバインドされ、クロージャーを(つまり、「プラグイン」の方法で)理解している人々の一定の流れを提供します。変数は常にストレージスペースにバインドされ、値には決してバインドされないJavaScriptの場合は単に正しくありません。
** この回答が明確に指摘しているように、いくつかのネストされている場合、またはグローバルコンテキストでさえ、任意の外部関数。
first_calculator
はオブジェクト(関数ではない)second_calculator = first_calculator;
であるため、関数呼び出しではなく代入であるため、では括弧を使用しないでください。あなたの質問に答えるには、make_calculatorへの呼び出しは1つだけなので、1つの計算機だけが作成され、変数first_calculatorとsecond_calculatorは両方とも同じ計算機を参照するため、答えは3、403、4433、44330になります。
私は今でも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
let
しvar
て違いを修正します。
n
は、新しいクロージャで作成された参照のみを返します。関数を返すだけなので、それを配列に格納して後で呼び出すことができます。
arr[index] = (function (n) { return 'n = ' + n; })(index);
。しかし、結果の文字列を、関数の代わりに配列に格納して、私の例のポイントを無効にします。
コンピュータサイエンスでは、クロージャは、その関数の非ローカル名(自由変数)の参照環境を備えた関数です。
技術的には、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
)、後で実行して、呼び出されるたびにデータベース関数を生成できます。生成されたすべての関数には、独自の非表示のデータベースオブジェクトがあります。クロージャーのもう1つの使用例は、関数を返さない場合ですが、オブジェクトは異なる目的で複数の関数を含み、各関数は同じデータにアクセスできます。
インタラクティブな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」という名前が付けられます。そして、関数が終了するたびに変数は失われ、名前は忘れられます。
ご想像のとおり、関数が呼び出されるたびに変数が再作成され、他の誰もそれらを知らないため、変数が格納される秘密の場所が必要です。それは秘密の部屋またはスタックまたはローカルスコープと呼ばれることができますませんが、それは本当に重要ではありません。私たちは、彼らが記憶のどこかに隠れていることを知っています。
しかし、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日に何回も使用します。
クロージャが特定の問題に対処するための複雑な設計ハックであると信じる理由はありません。いいえ、クロージャーは、より高いスコープからの変数を使用するだけです、関数が宣言された(実行されていない)視点から見からの。
今それがあなたにできることはもっと壮観になることができます、他の答えを見てください。
a
です。驚いたことに、JSスコープオブジェクトa
は定数ではなくプロパティとして効果的に提供されます。あなたにして、それを変更した場合、あなたはそれだけで、重要な行動に気付くでしょうreturn a++;
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.");
著者クロージャは、我々はそれらを必要とする理由を説明しても理解閉鎖することが必要であるLexicalEnvironmentを説明する、かなりよくクロージャを説明しています。
ここに要約があります:
変数がアクセスされたがローカルではない場合はどうなりますか?ここのように:
この場合、インタプリタは外側の変数を見つけます LexicalEnvironment
オブジェクトでます。
プロセスは2つのステップで構成されています。
関数が作成されると、現在のLexicalEnvironmentを参照する[[Scope]]という名前の非表示プロパティを取得します。
変数が読み取られても、どこにも見つからない場合は、エラーが生成されます。
入れ子関数
関数は互いに入れ子にして、スコープチェーンとも呼ばれるLexicalEnvironmentsのチェーンを形成できます。
したがって、関数gはg、a、fにアクセスできます。
閉鎖
ネストされた関数は、外側の関数が終了した後も存続する場合があります。
LexicalEnvironmentsのマークアップ:
見てのとおり、 this.say
はユーザーオブジェクトのプロパティなので、ユーザーが完了した後も存続します。
覚えておくと、this.say
が作成されると、(すべての関数として)内部参照が取得されますthis.say.[[Scope]]
現在のLexicalEnvironmentへの。したがって、現在のユーザー実行のLexicalEnvironmentはメモリに残ります。Userのすべての変数もそのプロパティであるため、通常のようにジャンクされることなく、慎重に保持されます。
重要なのは、内部関数が将来外部変数にアクセスしたい場合に確実にアクセスできるようにすることです。
要約する:
これはクロージャーと呼ばれます。
JavaScript関数は次のものにアクセスできます。
関数がその環境にアクセスする場合、関数はクロージャです。
外部関数は必須ではないことに注意してください。ただし、ここでは説明しませんが、利点はあります。環境内のデータにアクセスすることにより、クロージャーはそのデータを存続させます。外部/内部関数のサブケースでは、外部関数はローカルデータを作成し、最終的に終了できますが、外部関数が終了した後も内部関数が残っている場合、内部関数は外部関数のローカルデータを保持します。生きています。
グローバル環境を使用するクロージャーの例:
Stack Overflowの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);
インストラクション
データ:データはファクトのコレクションです。数字、単語、測定値、観察結果、または単なる説明です。触ったり、匂ったり、味わったりすることはできません。あなたはそれを書き留め、それを話し、それを聞くことができます。あなたはそれを使ってコンピューターを使用して、タッチの香りと味を。コードを使用してコンピュータで便利にできます。
コード:上記のすべての記述はコードと呼ばれます。JavaScriptで書かれています。
JAVASCRIPT:JavaScriptは言語です。英語、フランス語、中国語などが言語です。コンピュータや他の電子プロセッサが理解できる言語はたくさんあります。JavaScriptをコンピューターで理解するには、インタープリターが必要です。ロシア語しか話せない教師が学校でクラスを教えに来ると想像してみてください。先生が「всесадятся」と言うと、クラスは理解しませんでした。しかし、幸運なことに、あなたのクラスにはロシアの生徒がいて、全員に「誰もが座っている」という意味だと教えています。クラスはコンピューターのようなもので、ロシアの生徒は通訳です。JavaScriptの場合、最も一般的なインタープリターはブラウザーと呼ばれます。
ブラウザー:コンピューター、タブレット、または電話でインターネットに接続してWebサイトにアクセスする場合、ブラウザーを使用します。あなたが知っているかもしれない例は、Internet Explorer、Chrome、Firefox、Safariです。ブラウザはJavaScriptを理解し、必要な処理をコンピュータに伝えることができます。JavaScriptの指示は関数と呼ばれます。
関数:JavaScriptの関数はファクトリーのようなものです。機械が1台だけの小さな工場かもしれません。または、他の多くの小さな工場があり、それぞれに多くのマシンが異なる仕事をしているかもしれません。実際の衣料品工場では、一連の布と糸のボビンが入っており、Tシャツとジーンズが出てくることがあります。JavaScriptファクトリーはデータのみを処理し、縫製、穴あけ、金属の溶解はできません。JavaScriptファクトリーでは、データが入り、データが出てきます。
このすべてのデータは少し退屈に聞こえますが、それは本当にとてもクールです。夕食に何を作るかをロボットに伝える機能があるかもしれません。あなたとあなたの友人を私の家に招待したとしましょう。あなたは鶏の足が一番好きです、私はソーセージが好きです、あなたの友達はいつもあなたが欲しいものを望んでいて、私の友達は肉を食べません。
買い物に行く時間がないので、関数は決定を下すために冷蔵庫に何があるかを知る必要があります。食材ごとに調理時間は異なりますが、ロボットがすべてを同時に熱いうちに提供したいと考えています。私たちは好きなものに関するデータを関数に提供する必要があります。関数は冷蔵庫と「話す」ことができ、関数はロボットを制御することができます。
関数には通常、名前、括弧、中括弧があります。このような:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
ブラウザーによって読み取られているコード/*...*/
を//
停止することに注意してください。
名前:好きな言葉を使って関数を呼び出すことができます。「cookMeal」の例は、2つの単語を結合し、2番目の単語に最初に大文字を付けるのが一般的ですが、これは必須ではありません。スペースを入れることはできません。また、それ自体で数字にすることもできません。
かっこ:「かっこ」または()
JavaScript関数ファクトリのドアのレターボックス、または工場に情報のパケットを送信するための道路のポストボックスです。たとえば cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
、ポストボックスがマークされている場合があります。その場合、どのデータを指定する必要があるかがわかります。
ブレース:このように見える「ブレース」{}
は、工場の色付きの窓です。工場の中からは見えますが、外からは見えません。
上記の長いコード例
コードはfunctionという単語で始まっているので、それが1つであることがわかります。次に、関数の名前が歌います -それは、関数についての私自身の説明です。次に括弧()。括弧は関数の場合は常にそこにあります。空の場合もあれば、中に何かがある場合もあります。これには次の言葉があります(person)
。この後、このようなブレースがあります{
。これは、関数sing()の開始を示します。このようにsing()の終わりを示すパートナーがあります}
function sing(person) { /* STUFF INSIDE THE FUNCTION */ }
したがって、この関数は歌唱と関係がある場合があり、人物に関するデータが必要になる場合があります。そのデータを使って何かをするための命令が内部にあります。
ここで、関数sing()の後に、コードの終わり近くに次の行があります
var person="an old lady";
変数:文字varは「変数」を表します。変数はエンベロープのようなものです。この封筒の外側には「人」のマークが付いています。内部には、私たちの機能に必要な情報が記載された紙片が含まれ、文字やスペースが文字列(文字列と呼ばれます)のように結合されて、「老婦人」というフレーズを読みます。エンベロープには、数値(整数と呼ばれる)、命令(関数と呼ばれる)、リスト(配列と呼ばれる)などの他の種類のものを含めることができます。この変数はすべてのブレースの外側に書き込まれ、ブレースの{}
内側にいると色付きのウィンドウを通して見ることができるため、この変数はコードのどこからでも見ることができます。これを「グローバル変数」と呼びます。
グローバル変数:人はグローバル変数です。つまり、その値を「老婦人」から「若者」に変更すると、その人は、再び変更することを決定し、その他の機能がコードは、それが若い男であることがわかります。押してF12オプション設定で、ボタンや外観は、ブラウザのデベロッパーコンソールを開き、この値が何であるかを確認するために、「人」と入力します。入力person="a young man"
して変更し、もう一度「人」と入力して、変更されたことを確認します。
この後、次の行があります
sing(person);
この行は、まるで犬を呼んでいるかのように関数を呼び出しています
「歌いに来て、是非、人を手に入れよう!」
ブラウザがJavaScriptコードをロードすると、この行に到達すると、関数が開始されます。この行を最後に置いて、ブラウザで実行するために必要なすべての情報がブラウザにあることを確認します。
関数はアクションを定義します-主な関数は歌うことです。これには、歌の各詩に当てはまる人についての歌唱に当てはまるfirstPartと呼ばれる変数が含まれています。コンソールにfirstPartと入力すると、変数が関数内に閉じ込められているため、応答が得られません。ブラウザは、中括弧の色付きのウィンドウ内を見ることができません。
クロージャ:クロージャは、大きなsing()関数内にある小さな関数です。大きな工場内の小さな工場。それらにはそれぞれ独自のブレースがあり、それらの内部の変数は外部から見ることができません。変数の名前(creatureと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です。
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
それを説明する別の本当に簡単な方法は、スコープの観点からです:
大きいスコープ内に小さいスコープを作成すると、小さいスコープは常に大きいスコープの内容を確認できます。
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();
おそらく、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());
ALERT:monkey ALERT:monkey
しかし、これはどうですか?outerFunctionがnullに設定された後、referenceToInnerFunctionはどのようにしてouterVarの値を知ることができますか?
referenceToInnerFunctionが依然としてouterVarの値にアクセスできる理由は、innerFunctionをouterFunctionの内部に配置することによってクロージャが最初に作成されたときに、innerFunctionがそのスコープチェーンにouterFunctionのスコープ(その変数と関数)への参照を追加したためです。つまり、innerFunctionには、outerVarを含むすべてのouterFunctionの変数へのポインタまたは参照があります。そのため、outerFunctionの実行が終了した場合や、削除されたりnullに設定された場合でも、outerVarなどのスコープ内の変数は、innerFunctionに返された部分で未処理の参照があるため、メモリ内に留まります。 referenceToInnerFunction。outerVarとその他のouterFunctionの変数をメモリから完全に解放するには、これらの未処理の参照を取り除く必要があります。
///////////
注意すべきクロージャについて他に2つあります。まず、クロージャは常に、それを含む関数の最後の値にアクセスできます。
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
outerVar = "gorilla";
innerFunction();
}
outerFunction();
ALERT:ゴリラ
第2に、クロージャが作成されると、クロージャは、それを囲んでいるすべての関数の変数と関数への参照を保持します。選択する必要はありません。ただし、クロージャーはメモリを集中的に使用する可能性があるため、控えめに、または少なくとも慎重に使用する必要があります。含まれている関数の実行が終了した後も、多くの変数をメモリに保持できます。
私は、単にMozillaのクロージャーのページを示します。これは、私が見つけたクロージャーの基本と実際の使用法の最も簡潔で簡単な説明です。JavaScriptを学んでいる人には強くお勧めします。
はい、6歳の方にもお勧めします。6歳の方が閉鎖について学んでいるのであれば、記事で提供されている簡潔で簡単な説明を理解する準備ができているのは当然です。