JavaScriptクロージャーはどのように機能しますか?


7636

JavaScriptクロージャの構成概念(関数、変数など)を知っている人に、JavaScriptクロージャをどのように説明しますか?しかし、クロージャ自体は理解していませんか?

WikipediaにあるSchemeの例を見ことがありますが、残念ながら役に立ちませんでした。


391
これらと多くの答えの私の問題は、Javascriptでクロージャーが必要な理由とそれらを使用する実際の状況を簡単に説明することから始めるのではなく、抽象的な理論的な視点からアプローチすることです。最終的には、ログインしなければならないtl; dr記事になってしまい、いつも「しかし、なぜなのか」と考えます。まず、クロージャーは、JavaScriptの次の2つの現実に対処するための優れた方法です。スコープがブロックレベルではなく関数レベルにあり、b。JavaScriptで実際に行うことの多くは、非同期/イベント駆動型です。
ジェレミーバートン

53
@Redsandro 1つは、イベント駆動型コードを非常に簡単に記述できるようにすることです。ページが読み込まれたときに関数を呼び出して、HTMLまたは使用可能な機能の詳細を決定する場合があります。その関数でハンドラーを定義して設定し、ハンドラーが呼び出されるたびに、そのコンテキスト情報をすべて再クエリすることなく利用できます。一度問題を解決し、ハンドラーが必要なすべてのページで再利用して、ハンドラーの再呼び出しのオーバーヘッドを減らします。同じデータが、それらを持たない言語で2回再マッピングされるのを見たことがありますか?クロージャを使用すると、そのようなことを回避するのがはるかに簡単になります。
Erik Reppen 2013年

1
@Erik Reppen回答ありがとうございます。実際、私は、このように読みにくいclosureコードの利点Object Literalを知りました。これは、コード自体を再利用してオーバーヘッドを減らすのとは対照的ですが、必要なラッピングコードは100%少ないのです。
Redsandro 2013年

6
Javaプログラマーにとって、短い答えは、それは内部クラスと同等の機能であるということです。内部クラスは、外部クラスのインスタンスへの暗黙のポインターも保持し、ほとんど同じ目的(つまり、イベントハンドラーの作成)で使用されます。
Boris van Schooten 2014年

8
この実用的な例が非常に役立つことがわかりました:youtube.com/watch
v

回答:


7360

クロージャーは次の組み合わせです。

  1. 関数、および
  2. その関数の外部スコープへの参照(字句環境)

字句環境は、すべての実行コンテキスト(スタックフレーム)の一部であり、識別子(つまり、ローカル変数名)と値の間のマップです。

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())

関数型プログラミング

次のコードでは、関数innerfnとの両方を閉じます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>

モジュール化

次の例では、すべての実装の詳細が、すぐに実行される関数式の中に隠されています。関数は、プライベート状態ticktoString閉じ、作業を完了するために必要な関数を閉じます。クロージャーにより、コードをモジュール化してカプセル化することができました。

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())

例1

この例は、ローカル変数がクロージャーにコピーされないことを示しています。クロージャーは元の変数自体への参照を維持します。これは、外部関数が終了した後でも、スタックフレームがメモリに残っているかのようです。

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

例2

次のコードでは、3つのメソッドlogincrementおよび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

例3

を使用して宣言された変数を使用している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]() 
}

最終ポイント:

  • JavaScriptで関数が宣言されるたびに、クロージャーが作成されます。
  • を返す function外部関数の実行が完了した後でも、外部関数の内部の状態は、返された内部関数で暗黙的に使用できるため、別の関数の内部からをは、クロージャの典型的な例です。
  • eval()関数内で使用する場合は常にクロージャーが使用されます。eval関数のローカル変数を参照できるテキスト。非厳密モードでは、次のコマンドを使用して新しいローカル変数を作成することもできます。eval('var foo = …')
  • 関数内でnew Function(…)関数コンストラクター)を使用する場合、そのレキシカル環境ではなく、代わりにグローバルコンテキストで閉じます。新しい関数は、外部関数のローカル変数を参照できません。
  • JavaScriptでのクロージャは、基準(保つようなものであるNOTの上部にグローバルオブジェクトに、順番にようにその外側のスコープへの参照を保持し、関数宣言の時点ですべての方法をスコープにコピー)スコープチェーン。
  • 関数が宣言されると、クロージャーが作成されます。このクロージャは、関数が呼び出されたときに実行コンテキストを構成するために使用されます。
  • 関数が呼び出されるたびに、ローカル変数の新しいセットが作成されます。

リンク集


74
「JavaScriptのクロージャーは、関数が終了したときのように、すべてのローカル変数のコピーを保持するようなものです。」しかし、いくつかの理由で誤解を招く可能性があります。(1)クロージャーを作成するために関数呼び出しを終了する必要はありません。(2)ローカル変数ののコピーではなく、変数自体です。(3)これらの変数に誰がアクセスできるかは明記していません。
dlaliberte 2013

27
例5は、コードが意図したとおりに機能しない「問題」を示しています。しかし、それはそれを修正する方法を示していません。 この別の答えは、それを行う方法を示しています。
Matt

190
この投稿が「Closures Are Magicではない」という大きな太字で始まり、最初の例が「JavaScriptでは関数参照にも、それが作成されたクロージャへの秘密の参照があるというのが魔法だ」と終わるのが好きです。
Andrew Macheret 2014

6
例3は、クロージャとJavaScriptの巻き上げを組み合わせたものです。今では、巻き上げ動作を導入せずに、閉鎖のみを説明するのは十分難しいと思います。これは私に最も役立ちました:developer.mozilla.org/en-US/docs/Web/JavaScript/ClosuresClosures 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.から
caramba

3
ECMAScript 6は、クロージャーに関するこの素晴らしい記事で何かを変えるかもしれません。たとえば、例5のlet i = 0代わりにを使用するとvar i = 0testList()最初に必要なものが印刷されます。
Nier

3989

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が作成されます。関数の引数とターゲットオブジェクトとともに、この実行コンテキストは呼び出し元の実行コンテキストの字句環境へのリンクも受け取ります。つまり、外側の字句環境で宣言された変数(上記の例ではab)はから利用できますec

すべての関数は外部の字句環境へのリンクを持っているため、すべての関数はクロージャーを作成します。

変数自体はコピーではなく、クロージャー内から見えることに注意してください。


24
@feeela:はい、すべてのJS関数がクロージャーを作成します。参照されていない変数は、最新のJSエンジンでガベージコレクションの対象になる可能性がありますが、実行コンテキストを作成するときに、そのコンテキストが囲んでいる実行コンテキストとその変数への参照を持っているという事実は変わりません。その関数は、元の参照を保持しながら、別の変数スコープに再配置される可能性があるオブジェクトです。それが閉鎖です。

@Ali私が提供したjsFiddle deleteが失敗したため、実際には何も証明されないことがわかりました。それにもかかわらず、関数が[[Scope]]として持ち運ぶ(そして最終的には、呼び出されたときに独自の字句環境のベースとして使用される)字句環境は、関数を定義するステートメントが実行されるときに決定されます。これは、関数実際に参照する値やスコープをエスケープするかどうかに関係なく、関数実行スコープの全内容をクローズしていることを意味します。仕様の
Asad Saeeduddin 2013

8
これは、プリミティブ型と参照を説明しようとするまでは良い答えでした。それは完全に間違ってしまい、コピーされたリテラルについて話します。これは実際には何の関係もありません。
Ry-

12
クロージャーは、クラスベースのオブジェクト指向プログラミングに対するJavaScriptの答えです。JSはクラスベースではないため、他の方法では実装できないいくつかのことを実装する別の方法を見つける必要がありました。
パルトロミエザレウスキー

2
これは受け入れられる答えになるはずです。魔法は内部関数では決して起こりません。これは、外部関数を変数に割り当てるときに発生します。これにより、内部関数の新しい実行コンテキストが作成されるため、「プライベート変数」を蓄積できます。もちろん、外部関数に割り当てられた変数がコンテキストを維持しているので、それは可能です。最初の答えは、そこで何が実際に起こっているのか説明せずに、全体をより複雑にするだけです。
Albert Gao

2442

序文:この回答は、質問が次のようなときに書かれたものです。

古いアルバートが言ったように:「あなたがそれを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();

そして、大人は本当の王女を知っていたとしても、ユニコーンやドラゴンを見ることができなかったので、彼らは決して信じませんでした。大人たちは少女たちの想像の中にしか存在しなかったと言いました。

しかし、私たちは真実を知っています。王女が中にいる少女...

...本当に小さな女の子がいるプリンセスです。


340
本当にこの説明が大好きです。それを読んで従わない人にとっては、アナロジーはこれです:princess()関数は、プライベートデータを含む複雑なスコープです。関数の外では、プライベートデータを表示したりアクセスしたりすることはできません。王女はユニコーン、ドラゴン、冒険などを想像力(プライベートデータ)に保ち、大人は自分でそれらを見ることができません。しかし、王女の想像力はstory()littleGirlインスタンスが魔法の世界に公開する唯一のインターフェースである関数のクロージャーに取り込まれます。
Patrick M

そこでここではstory閉鎖ですが、コードがあったvar story = function() {}; return story;後、littleGirl閉鎖となります。少なくともそれは、MDN がクロージャを
icc97 2016

16
はい、@ icc97はのstoryスコープ内で提供される環境を参照するクロージャーですprincessprincessまた別です暗黙のクロージャでます。つまり、princessおよびは、存在およびが定義されている環境/スコープ内に存在littleGirlするparents配列への参照を共有します。littleGirlprincess
Jacob Swartwood 2016年

6
@BenjaminKrupp princess記述されたものよりも本文内に多くの操作があることを示す/暗示する明示的なコードコメントを追加しました。残念ながら、この話は今やこのスレッドでは少し場違いです。元々の質問は、「5歳までのJavaScriptクロージャを説明する」ことでした。私の返答はそれを試みた唯一のものでした。それが無残に失敗したことは間違いありませんが、少なくともこの応答には5歳の興味を抱く機会があったかもしれません。
Jacob Swartwood 2017

11
実際、これは完全に理にかなっています。そして、私は認めざるを得ません。王女と冒険の物語を使ってJSの閉鎖を最後に理解すると、ちょっと変な気分になります。
結晶化

753

質問を真剣に受け止めて、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関数でオブジェクトを返すと、ここでそれが行われます。

6
実際、紛らわしいことに、makeKitchen関数呼び出しは実際のクロージャであり、それが返すキッチンオブジェクトではありません。
dlaliberte 2016年

6
他の人を介して私の方法を持っていると、私はこの答えが、closures.isの内容と理由を説明する最も簡単な方法であることがわかりました。
チェタバハナ

3
メニューと前菜が多すぎ、肉とジャガイモが足りない。たとえば、「クロージャは、クラスによって提供されるスコープメカニズムがないため、関数の封印されたコンテキストです。」という短い文でその答えを改善できます。
Staplerfahrer 2017年

584

ストローマン

ボタンがクリックされた回数を知り、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の考慮事項を含む)


11
閉鎖が何であるかのあなたの定義に同意しません。自己呼び出しでなければならない理由はありません。「返される」必要があると言うのも少し単純です(そして不正確です)(この質問に対するトップアンサーのコメントでこれについての多くの議論)
James Montagne

40
@ジェームズ、あなたが同意しない場合でも、彼の例(および投稿全体)は私が見た中で最高の1つです。質問は古くなく、私にとっては解決されていませんが、完全に+1に値します。
e-satis 2013

84
「ボタンがクリックされた回数を知り、3回クリックするごとに何かをする必要があります...」これが私の注意を引いた。使用例と、クロージャーがそれほど不思議なものではないことを示すソリューションと、多くの人がそれらを作成してきましたが、正式な名前を正確には知りませんでした。
Chris22、2014年

2番目の例の「count」が「count」の値を保持し、「element」がクリックされるたびに0にリセットされないことを示しているため、良い例です。とても有益です!
アダム

クロージャ動作の +1 。クロージャの動作をJavaScriptの関数に制限できますか、それともこの概念を言語の他の構造にも適用できますか?
Dziamid 2015年

493

クロージャは、誰もがとにかく直感的に機能することを期待する動作を機能させるために使用されるため、説明が困難です。それらを説明する最良の方法(および彼らが何をするかを私が学んだ方法)は、それらのない状況を想像することです。

    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にバインドされています。


11
これは、変更可能な変数自体ではなく、返された関数で使用されるであると多くの人に誤解させるような種類の例です。それが「return x + = y」に変更された場合、またはよりよくそれと別の関数「x * = y」の両方に変更された場合、何もコピーされていないことは明らかです。フレームのスタックに慣れている人にとっては、代わりにヒープフレームを使用することを想像してください。ヒープフレームは、関数が戻った後も存在し続けることができます。
Matt

14
@マット私は同意しません。例では、すべてのプロパティを完全に文書化すること想定されていません。それは還元的であることを意味し、概念の顕著な特徴を説明します。OPは簡単な説明を求めました(「6歳の場合」)。受け入れられた答えを取る:それは完全に網羅しようとするからこそ、簡潔な説明を提供することに全く失敗します。(私は、バインディングが値ではなく参照によるものであることはJavaScriptの重要な特性であることに同意します…しかし、繰り返しになりますが、成功した説明は最低限の説明になります。)
Konrad Rudolph

@KonradRudolph私はあなたの例のスタイルと簡潔さを気に入っています。最後の「唯一の解決策は...」が真になるように、少し変更することをお勧めします。現在、実際には別のより簡単な解決策がシナリオにあります。これは、JavaScriptの継続に対応せ、継続と何かについての一般的な誤解に対応しています。したがって、現在の形式の例は危険です。これは、プロパティを完全にリストすることとは関係がなく、返される関数のxが何であるかを理解することと関係があります。
Matt

@マットうーん、私はあなたを完全に理解しているとは思いませんが、あなたが有効なポイントを持っているかもしれないと思い始めます。コメントが短すぎるので、要旨/パスティーやチャットルームであなたが何を意味するのか説明できますか?ありがとう。
Konrad Rudolph

2
@KonradRudolph私はx + = yの目的がはっきりしていなかったと思います。目的は、返された関数への繰り返しの呼び出しが同じ変数 x を使用し続けることを示すことでした(関数が作成されるときに人々が「挿入」されると想像される同じとは対照的です)。これは、フィドルの最初の2つのアラートに似ています。追加の関数x * = yの目的は、返された複数の関数がすべて同じxを共有することを示すことです。
Matt

379

TLDR

クロージャーは、その環境内で定義された識別子(変数、パラメーター、関数宣言など)がいつ、どこからでも関数内から見えるようにする、関数とその外側の字句(つまり、作成時)環境の間のリンクです。関数が呼び出される場所。

詳細

ECMAScript仕様の用語では、クロージャーは、関数が定義され[[Environment]]ている字句環境を指すすべての関数オブジェクトの参照によって実装されると言えます。

内部[[Call]]メソッドを介して関数が呼び出される[[Environment]]と、関数オブジェクトの参照が、新しく作成された実行コンテキスト(スタックフレーム)の環境レコード外部環境参照にコピーされます。

次の例では、関数fはグローバル実行コンテキストの字句環境を閉じます。

function f() {}

次の例では、function hがfunction のレキシカル環境をg閉じ、次にそれがグローバル実行コンテキストのレキシカル環境を閉じます。

function g() {
    function h() {}
}

内部関数が外部関数によって返された場合、外部関数が返された後、外部レキシカル環境が存続します。これは、内部関数が最終的に呼び出される場合、外部レキシカル環境が利用可能である必要があるためです。

次の例では、functionは、function jの字句環境を閉じますi。つまり、functionの実行が完了した後も、function x内から変数が見えるようになります。ji

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

外部環境参照を介して実行コンテキスト間でリンクされたレキシカル環境のチェーンは、スコープチェーンを形成し、特定の関数から可視の識別子を定義します。

わかりやすさと正確さを向上させるために、この回答は元の回答から大幅に変更されていることに注意してください。


56
うわー、console.logそのような場合に文字列置換を使用できることを知りませんでした。誰にも興味を持っている場合は、よりありますdeveloper.mozilla.org/en-US/docs/DOM/...
フラッシュ

7
関数のパラメーターリストにある変数もクロージャーの一部です(たとえば、に限定されませんvar)。
Thomas Eding、2015年

クロージャはオブジェクトやクラスなどに似ています。なぜこれらの2つを多くの人が比較しないのかわからないのです。
almaruf

376

はい、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");


26
よく演奏され、元のポスターに答えます。これが最良の答えだと思います。私も同じように荷物を使うつもりでした。おばあちゃんの家に行って、ニンテンドーDSのケースにゲームカードを入れたまま、バックパックの中に入れ、ゲームカードをバックパックのポケットに入れたとします。次に、すべてを大きなスーツケースに入れ、スーツケースのポケットにゲームカードを追加します。おばあちゃんの家に着いたら、すべての外部のケースが開いている限り、DSで任意のゲームをプレイできます。またはその効果に何か。
slartibartfast 2013

366

これは、他のいくつかの回答に表示される閉鎖に関するいくつかの(可能性のある)誤解を解消するための試みです。

  • 内部関数を返すときにクロージャが作成されるだけではありません。実際、クロージャーを作成するために、囲んでいる関数が戻る必要はまったくありません。代わりに、内側の関数を外側のスコープの変数に割り当てるか、それを引数として別の関数に渡して、すぐにでも後ででも呼び出すことができます。したがって、内部関数が呼び出されるときはいつでも、内部関数がそのクロージャにアクセスできるので、内部関数が呼び出されるとすぐに、囲み関数のクロージャが作成さます。
  • クロージャーは、そのスコープ内の変数の古い値のコピーを参照しません。変数自体はクロージャの一部であるため、これらの変数の1つにアクセスしたときに表示される値は、アクセス時の最新の値です。ループの内部で作成された内部関数は、関数が作成または呼び出されたときに変数のコピーを取得するのではなく、同じ外部変数にアクセスできるため、これが難しい理由です。
  • クロージャの「変数」には、関数内で宣言された名前付き関数含まれます。関数の引数も含まれます。クロージャは、それを含むクロージャの変数にも、グローバルスコープまでアクセスできます。
  • クロージャはメモリを使用しますが、JavaScript自体が参照されない独自の循環構造をクリーンアップするため、メモリリークは発生しません。クロージャを参照するDOM属性値の切断に失敗すると、クロージャを含むInternet Explorerのメモリリークが発生し、循環構造への参照が維持されます。

15
ジェームズ、私は、クロージャーが絶対に必要であると判断するときまで、実装がクロージャーの作成を遅らせる可能性が高いため、クロージャーは「おそらく」囲み関数の呼び出し時に作成されると述べました。囲んでいる関数で定義されている内部関数がない場合、クロージャーは必要ありません。したがって、最初の内部関数が作成されるまで待ってから、囲んでいる関数の呼び出しコンテキストからクロージャを作成することができます。
dlaliberte

9
@ Beetroot-Beetroot 外部関数が戻るに使用される別の関数に渡される内部関数があり、外部関数から同じ内部関数も返すとします。どちらの場合も同じ関数ですが、外側の関数が戻る前に内側の関数がコールスタックに「バインド」されているのに対し、関数が戻った後は、内側の関数が突然クロージャーにバインドされます。どちらの場合も同じように動作します。セマンティクスは同じなので、実装の詳細について話しているだけではありませんか?
dlaliberte 2012年

7
@ Beetroot-Beetroot、あなたのフィードバックをありがとう、私はあなたに考えさせてくれてうれしいです。それがクロージャーになるとき、関数が戻るときに、外部関数のライブコンテキストと同じコンテキストの間に意味的な違いはまだありません(私があなたの定義を理解している場合)。内部関数は関係ありません。内部関数はどちらかの方法でコンテキスト/クロージャへの参照を維持し、外部関数の呼び出し元は呼び出しコンテキストへの参照を削除するだけなので、ガベージコレクションは関係ありません。しかし、それは人々を混乱させ、おそらくそれを単に呼び出しコンテキストと呼ぶ方が良いでしょう。
dlaliberte 2012年

9
その記事は読みにくいですが、私が実際に私が言っていることをサポートしていると思います。それは言う:「クロージャは、関数オブジェクトを返すことによって、またはそのような関数オブジェクトへの参照を、たとえばグローバル変数に直接割り当てることによって形成されます。」GCが無関係であるという意味ではありません。むしろ、GCのため、および内部関数が外部関数の呼び出しコンテキスト(または記事が言うように[[scope]])にアタッチされているため、外部関数呼び出しが戻るかどうかは関係ありません。機能は重要です。
dlaliberte 2012年

3
正解です。追加する必要があることの1つは、すべての関数が、それらが定義されている実行スコープのコンテンツ全体を閉じることです。親スコープの変数の一部を参照するか、まったく参照しないかは関係ありません。親スコープの字句環境への参照は、無条件に[[Scope]]として格納されます。これは、ECMA仕様の関数作成に関するセクションで確認できます。
Asad Saeeduddin 2013

236

しばらく前に、閉鎖について説明するブログ投稿を書きました。クロージャについて、なぜ必要なのという観点から、以下のことを述べました。

クロージャーは、関数に永続的なプライベート変数(つまり、1つの関数のみが認識している変数)を持たせる方法であり、実行された以前の時間からの情報を追跡できます。

その意味で、関数はプライベート属性を持つオブジェクトのように機能します。

完全な投稿:

それで、これらの閉鎖物は何ですか?


この例では、クロージャーの主な利点を強調できますか?たとえば、emailError(sendToAddress、errorString)というdevError = emailError("devinrhode2@googmail.com", errorString)関数があるとしたら、共有されたemailError関数の独自のカスタムバージョンを持つことができますか?
Devin G Rhode

この説明と(clothingthingys)へのリンクの関連する完璧な例は、閉鎖を理解するための最良の方法であり、一番上にあるはずです!
HopeKing、

215

クロージャーはシンプルです:

次の簡単な例は、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。このローカル変数は、返された後もずっと、その計算機addmultiply関数で引き続き使用できmake_calculatorます。

スタックフレームに精通している場合、これらの計算機は奇妙に見えます。どのようにしてnmake_calculator戻ってきた後もアクセスし続けることができますか?答えは、JavaScriptが「スタックフレーム」を使用せず、代わりに「ヒープフレーム」を使用することを想像することです。これは、それらを作成した関数呼び出しが戻った後も存続する可能性があります。

addandなどの内部関数multiplyは、外部関数**で宣言された変数にアクセスし、クロージャーと呼ばれます。

クロージャについては、これでほぼすべてです。



*たとえば、例6を除いて、別の回答で示されている「Closures for Dummies」記事のすべてのポイントをカバーしています。例6は、変数が宣言される前に使用できることを示しています。知っておくと便利ですが、クロージャーとはまったく無関係です。また、(1)関数が引数をローカル変数(名前付き関数の引数)にコピーする点と、(2)数値をコピーすると新しい数値が作成されるが、オブジェクト参照をコピーする点を除いて、受け入れられた回答のすべての点をカバーします同じオブジェクトへの別の参照を提供します。これらも知っておくと良いですが、クロージャーとはまったく無関係です。この回答、が、少し短く、抽象的なものではありません。それはのポイントをカバーしていませんこの回答、またはこのコメントの例にも非常に似ています、つまり、JavaScriptを使用すると、現在の内部関数へのループ変数の値:「プラグイン」ステップは、内部関数を囲み、各ループ反復で呼び出されるヘルパー関数でのみ実行できます。(厳密に言えば、内部関数はプラグインされているものではなく、ヘルパー関数の変数のコピーにアクセスします。)繰り返しになりますが、クロージャーを作成するときに非常に役立ちますが、クロージャーの機能や機能の一部ではありません。MLのような関数型言語ではクロージャーが異なる動作をするため、混乱がさらにあります。変数はストレージスペースではなく値にバインドされ、クロージャーを(つまり、「プラグイン」の方法で)理解している人々の一定の流れを提供します。変数は常にストレージスペースにバインドされ、値には決してバインドされないJavaScriptの場合は単に正しくありません。

** この回答が明確に指摘しているように、いくつかのネストされている場合、またはグローバルコンテキストでさえ、任意の外部関数。


あなたが呼ばれたらどうなるでしょう:second_calculator = first_calculator(); second_calculator = make_calculator();の代わりに ?同じでしょ?
Ronen Festinger 2016年

4
@Ronen:first_calculatorはオブジェクト(関数ではない)second_calculator = first_calculator;であるため、関数呼び出しではなく代入であるため、では括弧を使用しないでください。あなたの質問に答えるには、make_calculatorへの呼び出しは1つだけなので、1つの計算機だけが作成され、変数first_calculatorとsecond_calculatorは両方とも同じ計算機を参照するため、答えは3、403、4433、44330になります。
Matt

204

6歳の子供にどう説明するか:

大人が家を所有する方法を知っていますか?ママが子供を持っているとき、子供は本当に何も所有していませんよね?しかし、その両親は家を所有しているので、誰かが「あなたの家はどこですか?」と子供に尋ねるときはいつでも、彼/彼女は「あの家!」と答えて、両親の家を指すことができます。「クロージャー」とは、たとえ実際に家を所有しているのが親であるとしても、子供がいつでも(海外であっても)家があると言える能力のことです。


200

5歳の子供に閉鎖について説明していただけますか?*

私は今でも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#質問


11
このコードは、outerFunctionが戻った後のクロージャーの使用に関するコメントの一部を扱っていませんが、クロージャーの例としては「正しい」ものです。したがって、それは良い例ではありません。innerFunctionを返すことを必要としない、クロージャーを使用できる他の多くの方法があります。たとえば、innerFunctionを別の関数に渡し、すぐに呼び出すか、保存してしばらくしてから呼び出すことができます。すべての場合で、呼び出されたときに作成されたouterFunctionコンテキストにアクセスできます。
dlaliberte 2011

6
@syockitいいえ、モスは間違っています。関数が定義されているスコープをエスケープするかどうかに関係なくクロージャーが作成され、親の字句環境への無条件に作成された参照により、外部で呼び出されたか内部で呼び出されたかに関係なく、親スコープ内のすべての変数がすべての関数で使用可能になりますそれらが作成されたスコープ
Asad Saeeduddin 2013

176

私は良い/悪い比較でよりよく学ぶ傾向があります。動作しているコードに続いて、誰かが遭遇する可能性のある動作していないコードを見たいと思います。比較を行う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


1
さらに、ありがとう。より明確にするために、「悪い」ループが各繰り返しで「悪い」配列がどのように作成されるかを想像できます。最初の繰り返し:[function(){return 'n =' + 0;}] 2番目の繰り返し:[( function(){return 'n =' + 1;})、(function(){return 'n =' + 1;})] 3回目の反復:[(function(){return 'n =' + 2;}) 、(function(){return 'n =' + 2;})、(function(){return 'n =' + 2;})]など。したがって、インデックス値が変更されるたびに、すべての関数に反映されますすでにアレイに追加されています。
Alex Alexeev

3
を使用letvarて違いを修正します。
Rupam Datta 2017

ここで「クロージャが正しく行われた」が「クロージャ内部のクロージャ」の例ではありませんか?
TechnicalSmile

つまり、すべての関数は技術的にはクロージャですが、重要な部分は、関数が内部で新しい変数を定義することです。get関数nは、新しいクロージャで作成された参照のみを返します。関数を返すだけなので、それを配列に格納して後で呼び出すことができます。
Chev

最初の反復で結果を配列に格納するだけの場合は、次のようにインライン化できますarr[index] = (function (n) { return 'n = ' + n; })(index);。しかし、結果の文字列を、関数の代わりに配列に格納して、私の例のポイントを無効にします。
Chev

164

閉鎖に関するウィキペディア

コンピュータサイエンスでは、クロージャは、その関数の非ローカル名(自由変数)の参照環境を備えた関数です。

技術的には、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つの使用例は、関数を返さない場合ですが、オブジェクトは異なる目的で複数の関数を含み、各関数は同じデータにアクセスできます。


2
これは、JavaScriptクロージャの最良の説明です。選ばれた答えでなければなりません。残りは十分に面白いですが、これは実際に実際のJavaScriptコーダーにとって実用的な方法で役に立ちます。
ジオイデシック2018

136

インタラクティブな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

128

子供たちは、両親がいなくなった後でも、両親と共有した秘密を常に覚えています。これが関数のクロージャです。

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"

伝えるべきことはこれだけです。


15
これは、技術用語の事前の重要な知識を前提としていないため、私にとって最も意味のある説明です。ここでのトップ投票の説明は、クロージャを理解していない人が「レキシカルスコープ」や「実行コンテキスト」などの用語を完全に理解していることを前提としています。これらを概念的に理解することはできますが、私はそれらの細部に私が慣れ親しんでいること、そして専門用語がまったく含まれていないことの説明が、最終的にクロージャがクリックしてくれた理由です。ありがとうございました。おまけとして、スコープが非常に簡潔であることも説明していると思います。
Emma W

103

ここでなぜ答えがそれほど複雑であるのか理解できません。

ここにクロージャーがあります:

var a = 42;

function b() { return a; }

はい。あなたはおそらくそれを1日に何回も使用します。


クロージャが特定の問題に対処するための複雑な設計ハックであると信じる理由はありません。いいえ、クロージャーは、より高いスコープからの変数を使用するだけです、関数が宣言された(実行されていない)視点から見からの

今それあなたにできることはもっと壮観になることができます、他の答えを見てください。


5
この回答は、混乱を招かないようにするためのものではないようです。従来のプログラミング言語での大まかな同等物は、プライベート定数またはプロパティ持つオブジェクトのメソッドとしてb()を作成することaです。驚いたことに、JSスコープオブジェクトaは定数ではなくプロパティとして効果的に提供されます。あなたにして、それを変更した場合、あなたはそれだけで、重要な行動に気付くでしょうreturn a++;
ジョン・クームス

1
まさにジョンが言ったこと。ようやく閉鎖を理解する前に、実用的な例を見つけるのに苦労しました。はい、フロリボンは閉鎖を作成しましたが、私を教育しなければ、これは私に全く何も教えませんでした。
Chev

3
これは、クロージャが何であるかを定義するものではありません-クロージャを使用する単なる例です。また、スコープが終了したときに何が起こるかのニュアンスについては触れていません。すべてのスコープがまだ存在する場合、特にグローバル変数の場合は、字句スコープについて誰も質問しなかったと思います。
Gerard ONeill

92

dlaliberteによる最初のポイントの例:

内部関数を返すときにクロージャが作成されるだけではありません。実際、囲んでいる関数はまったく戻る必要はありません。代わりに、内部関数を外部スコープの変数に割り当てるか、それを引数として別の関数に渡して、すぐに使用することができます。したがって、内部関数が呼び出されるとすぐにその内部関数にアクセスできるため、囲み関数のクロージャーは、おそらくその囲み関数が呼び出されたときにすでに存在しています。

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

可能性のある曖昧さについての小さな説明。「実際、囲んでいる関数はまったく戻る必要がない」と言ったとき。「値を返さない」という意味ではなく、「まだアクティブ」でした。したがって、例ではその側面を示していませんが、内部関数を外部スコープに渡すことができる別の方法を示しています。囲んでいる関数が戻ったときにそれが起こると考える人もいるので、私が作ろうとしていた主なポイントは、(囲んでいる関数の)クロージャの作成についてです。関数が呼び出されたときにクロージャが作成されることを示すには、別の例が必要です。
dlaliberte

89

クロージャーは、内部関数が外部関数の変数にアクセスできる場所です。これはおそらく、クロージャーについて取得できる最も単純な1行の説明です。


35
それは説明の半分にすぎません。クロージャーについて注意すべき重要なことは、外部関数が終了した後でも内部関数が参照されている場合、外部関数の古い値は内部関数で引き続き使用できるということです。
pcorcoran 2008

22
実際、内部関数で使用できるのは外部関数の古いではなく、古い変数であり、一部の関数がそれらを変更できた場合に新しい値を持つ可能性があります。
dlaliberte 2012

86

私はすでに多くの解決策があることを知っていますが、この小さくて単純なスクリプトは概念を実証するのに役立つと思います。

// 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

82

あなたは寝坊していて、ダンを招待します。ダンに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.");

80

著者クロージャは、我々はそれらを必要とする理由を説明しても理解閉鎖することが必要であるLexicalEnvironmentを説明する、かなりよくクロージャを説明しています。
ここに要約があります:

変数がアクセスされたがローカルではない場合はどうなりますか?ここのように:

ここに画像の説明を入力してください

この場合、インタプリタは外側の変数を見つけます LexicalEnvironmentオブジェクトでます。

プロセスは2つのステップで構成されています。

  1. まず、関数fを作成しても、空の空間には作成されません。現在のLexicalEnvironmentオブジェクトがあります。上記の場合、それはウィンドウです(aは関数作成時には未定義です)。

ここに画像の説明を入力してください

関数が作成されると、現在のLexicalEnvironmentを参照する[[Scope]]という名前の非表示プロパティを取得します。

ここに画像の説明を入力してください

変数が読み取られても、どこにも見つからない場合は、エラーが生成されます。

入れ子関数

関数は互いに入れ子にして、スコープチェーンとも呼ばれるLexicalEnvironmentsのチェーンを形成できます。

ここに画像の説明を入力してください

したがって、関数gはg、a、fにアクセスできます。

閉鎖

ネストされた関数は、外側の関数が終了した後も存続する場合があります。

ここに画像の説明を入力してください

LexicalEnvironmentsのマークアップ:

ここに画像の説明を入力してください

見てのとおり、 this.sayはユーザーオブジェクトのプロパティなので、ユーザーが完了した後も存続します。

覚えておくと、this.sayが作成されると、(すべての関数として)内部参照が取得されますthis.say.[[Scope]]現在のLexicalEnvironmentへの。したがって、現在のユーザー実行のLexicalEnvironmentはメモリに残ります。Userのすべての変数もそのプロパティであるため、通常のようにジャンクされることなく、慎重に保持されます。

重要なのは、内部関数が将来外部変数にアクセスしたい場合に確実にアクセスできるようにすることです。

要約する:

  1. 内部関数は、外部のLexicalEnvironmentへの参照を保持します。
  2. 内部関数は、外部関数が終了した場合でも、いつでもそこから変数にアクセスできます。
  3. ブラウザーは、それを参照する内部関数が存在するまで、LexicalEnvironmentとそのすべてのプロパティ(変数)をメモリに保持します。

これはクロージャーと呼ばれます。


78

JavaScript関数は次のものにアクセスできます。

  1. 議論
  2. ローカル(つまり、ローカル変数とローカル関数)
  3. 以下を含む環境:
    • DOMを含むグローバル
    • 外部関数の何でも

関数がその環境にアクセスする場合、関数はクロージャです。

外部関数は必須ではないことに注意してください。ただし、ここでは説明しませんが、利点はあります。環境内のデータにアクセスすることにより、クロージャーはそのデータを存続させます。外部/内部関数のサブケースでは、外部関数はローカルデータを作成し、最終的に終了できますが、外部関数が終了した後も内部関数が残っている場合、内部関数は外部関数のローカルデータを保持します。生きています。

グローバル環境を使用するクロージャーの例:

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つの関数はすべて、環境にアクセスするため、すべてクロージャです。


59

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()関数内にある小さな関数です。大きな工場内の小さな工場。それらにはそれぞれ独自のブレースがあり、それらの内部の変数は外部から見ることができません。変数の名前(creatureresult)はクロージャーで繰り返し使用できますが、値が異なるのはそのためです。これらの変数名をコンソールウィンドウに入力すると、色付きのウィンドウの2つのレイヤーによって非表示になるため、その値は取得されません。

クロージャーはすべて、sing()関数のfirstPartと呼ばれる変数が何であるかを知っています。

閉鎖後、次の行が来る

fly();
spider();
bird();
cat();

sing()関数は、指定された順序でこれらの各関数を呼び出します。次に、sing()関数の作業が行われます。


56

さて、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です。


49

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

それを説明する別の本当に簡単な方法は、スコープの観点からです:

大きいスコープ内に小さいスコープを作成すると、小さいスコープは常に大きいスコープの内容を確認できます。


49

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();

47

おそらく、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に、クロージャが作成されると、クロージャは、それを囲んでいるすべての関数の変数と関数への参照を保持します。選択する必要はありません。ただし、クロージャーはメモリを集中的に使用する可能性があるため、控えめに、または少なくとも慎重に使用する必要があります。含まれている関数の実行が終了した後も、多くの変数をメモリに保持できます。


45

私は、単にMozillaのクロージャーのページを示します。これは、私が見つけたクロージャーの基本と実際の使用法の最も簡潔で簡単な説明です。JavaScriptを学んでいる人には強くお勧めします。

はい、6歳の方にもお勧めします。6歳の方が閉鎖について学んでいるのであれば、記事で提供されている簡潔で簡単な説明を理解する準備ができているのは当然です。


私は同意します:上記のMozillaページは特にシンプルで簡潔です。驚くべきことに、あなたの投稿は他の投稿ほど広く評価されていません。
Brice Coustillas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.