「閉鎖」とは何ですか?


432

カレーについて質問したところ、クロージャーについて触れられていました。閉鎖とは何ですか?それはカレーとどのように関係していますか?


22
今閉鎖は正確には何ですか?いくつかの答えは、閉鎖は機能であると言います。スタックだと言う人もいます。一部の回答は、それは「隠された」値であると言います。私の理解では、これは関数+囲まれた変数です。
Roland、

3
:クロージャが何であるかを説明しstackoverflow.com/questions/4103750/...
dietbuddha

また、クロージャーとは何ですか?softwareengineering.stackexchangeで
B12Toaster

クロージャとは何か、および一般的な使用例について説明します。trungk18.com
experience

回答:


744

変数スコープ

ローカル変数を宣言すると、その変数にはスコープがあります。通常、ローカル変数は、宣言したブロックまたは関数内にのみ存在します。

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

ローカル変数にアクセスしようとすると、ほとんどの言語は現在のスコープでそれを探し、次にルートスコープに到達するまで親スコープを調べます。

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

ブロックまたは関数の処理が完了すると、そのローカル変数は不要になり、通常はメモリから削除されます。

これが通常の動作を期待する方法です。

クロージャーは永続的なローカル変数スコープです

クロージャーは、コード実行がそのブロックの外に移動した後でも、ローカル変数を保持する永続的なスコープです。クロージャーをサポートする言語(JavaScript、Swift、Rubyなど)を使用すると、それらの変数が宣言されたブロックの実行が終了した後でも、参照を保持していれば、スコープ(その親スコープを含む)への参照を保持できます。そのブロックまたはどこかに機能します。

スコープオブジェクトとそのすべてのローカル変数は関数に関連付けられており、その関数が持続する限り持続します。

これにより、関数の移植性が得られます。関数が最初に定義されたときにスコープ内にあったすべての変数は、完全に異なるコンテキストで関数を呼び出した場合でも、後で関数を呼び出すときにまだスコープ内にあると予想できます。

例えば

ポイントを示すJavaScriptの非常に単純な例を次に示します。

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

ここでは、関数内で関数を定義しました。内部関数は、を含むすべての外部関数のローカル変数にアクセスできますa。変数aは内部関数のスコープ内にあります。

通常、関数が終了すると、そのすべてのローカル変数が吹き飛ばされます。ただし、内部関数を返し、それを変数に割り当てて、終了fnc後に永続化するとが定義されたときにスコープ内にあったすべての変数も永続化されます。変数が閉じられました-クロージャ内にあります。outerinnera

変数aは完全にプライベートであることに注意してくださいfnc。これは、JavaScriptなどの関数型プログラミング言語でプライベート変数を作成する方法です。

ご想像のとおり、私がfnc()それを呼び出すとa、「1」であるの値が出力されます。

クロージャーのない言語では、変数aはガベージコレクションされ、関数がouter終了すると破棄されます。fncを呼び出すとエラーがスローされます。a存在しない。

JavaScriptでは、変数aスコープは関数が最初に宣言されたときに作成され、関数が存在し続ける限り存続するため、変数は存続します。

aのスコープに属していますouter。のスコープにinnerは、のスコープへの親ポインタがありますouterfncはを指す変数ですinnera持続する限りfnc持続します。a閉鎖内にあります。


116
これはかなり良い例でわかりやすいと思いました。
user12345613

16
素晴らしい説明をありがとう、私はたくさん見ましたが、これは私が本当にそれを手にした時です。
Dimitar Dimitrov

2
2番目から最後の段落で述べたように、これがJQueryのようなライブラリでどのように機能するかの例を教えてもらえますか?私はそれを完全に理解していませんでした。
DPM 2013年

6
こんにちはJubbatです。jquery.jsを開いて、最初の行を見てください。関数が開いていることがわかります。最後までスキップすると、window.jQuery = window。$ = jQueryが表示されます。次に、関数が閉じられ、自己実行されます。これで$関数にアクセスできるようになり、クロージャーで定義された他の関数にもアクセスできるようになります。それはあなたの質問に答えますか?
超光速

4
ウェブ上での最良の説明。思ったよりもずっと簡単
Mantis

95

(JavaScriptで)例を示します。

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

この関数makeCounterが行うことは、xと呼ばれる関数が返されることで、呼び出されるたびに1ずつカウントアップします。xにパラメーターを提供していないので、何とかカウントを覚えておく必要があります。それは字句スコープと呼ばれるものに基づいてそれを見つける場所を知っています-それは値を見つけるためにそれが定義されている場所を探す必要があります。この「隠された」値は、いわゆるクロージャーです。

これが私のカレーの例です。

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

ご覧のとおり、パラメーターa(3)を指定してaddを呼び出すと、その値は、add3として定義している返された関数のクロージャーに含まれています。このようにして、add3を呼び出すと、加算を実行するa値の場所がわかります。


4
IDK、上記の言語で使用した言語(おそらくF#)。上記の例を疑似コードで教えてもらえますか?これを理解するのに苦労しています。
ユーザー


3
@KyleCronin素晴らしい例、ありがとう。Q:「隠された値はクロージャと呼ばれます」、または「値を隠す関数はクロージャです」と言った方が正しいですか?それとも「価値を隠すプロセスは閉鎖だ」?ありがとう!

2
@RobertHumeいい質問です。意味的に、「閉鎖」という用語はややあいまいです。私の個人的な定義は、隠された値とそれを囲む関数の使用の両方の組み合わせがクロージャーを構成するということです。
カイル・クロニン2013

1
@KyleCroninありがとう-私は月曜日に中間期にスキームを持っています。:)「閉鎖」のコンセプトをしっかり頭に入れたかった。OPの質問にこの素晴らしい答えを投稿してくれてありがとう!

58

カイルの答えはかなり良いです。追加の説明は、クロージャーは基本的にラムダ関数が作成された時点でのスタックのスナップショットであることだけです。その後、関数が再実行されると、スタックは関数を実行する前の状態に復元されます。したがって、Kyleが言及しているcountように、ラムダ関数が実行されると、その隠された値()が使用可能になります。


14
スタックだけではなく、スタックに保存されているか、ヒープ(またはその両方)に保存されているかに関係なく、保持されているレキシカルスコープです。
Matt Fenwick、

38

まず第一に、ここにいるほとんどの人々があなたに言うこととは反対に、閉鎖は機能ではありません!だから何であるそれは?
これは、関数の「周囲のコンテキスト」(その環境と呼ばれます)で定義された一連のシンボルであり、これをCLOSED式(つまり、すべてのシンボルが定義され、値を持つため、評価できる式)にします。

たとえば、JavaScript関数がある場合:

function closed(x) {
  return x + 3;
}

そこに出現するすべてのシンボルが定義されているため(それらの意味は明確です)、これは閉じた式であり、評価できます。つまり、自己完結型です。

しかし、次のような関数がある場合:

function open(x) {
  return x*y + 3;
}

定義されていないシンボルが含まれているため、オープンな式です。すなわち、y。この関数を見ると、yその意味と意味がわかりません。その値がわからないため、この式を評価できません。つまり、何yを意味するのかがわかるまで、この関数を呼び出すことはできません。これy自由変数と呼ばれます

これyは定義を要求しますが、この定義は関数の一部ではありません。「周囲のコンテキスト」(「環境」とも呼ばれます)のどこかで定義されています。少なくともそれが私たちが望むことです:P

たとえば、グローバルに定義できます。

var y = 7;

function open(x) {
  return x*y + 3;
}

または、それをラップする関数で定義することもできます。

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

式の中の自由変数にそれらの意味を与える環境の部分は、クロージャです。それはこのように呼ばれます、なぜならそれは自由な変数のすべてにこれらの欠けている定義を提供することによって開いた式を閉じたものに変えるからそれを評価できるからです。

上記の例では、内部関数(必要がないため名前を付けていません)はオープンな式ですy。これは、その中の変数が自由であるためです。その定義は、関数の外側、それをラップする関数内にあります。 。その無名関数の環境は、変数のセットです。

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

ここで、クロージャはこの環境の一部であり、すべての自由変数の定義を提供することによって内部関数を閉じます。私たちの場合、内部関数の唯一の自由変数はyだったので、その関数のクロージャはその環境のこのサブセットです:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

環境で定義された他の二つのシンボルがありませんの一部閉鎖、それは実行するためにそれらを必要としないため、その関数の。彼らはそれを閉じる必要はありません。

その背後にある理論の詳細はこちら:https : //stackoverflow.com/a/36878651/434562

上記の例では、ラッパー関数がその内部関数を値として返すことに注意してください。この関数を呼び出す瞬間は、関数が定義(または作成)された瞬間から離れていてもかまいません。特に、そのラッピング関数は実行されておらず、呼び出しスタックにあったパラメーターはもうありません:Pこれは問題yです。これは、呼び出されたときに内部関数がそこにある必要があるためです。言い換えると、ラッパー関数を何とか存続させ、必要なときにそこに存在するためには、クロージャーからの変数が必要です。したがって、内部関数はスナップショットを作成する必要がありますこれらの変数のを作成して、クロージャーを作成し、後で使用するために安全な場所に保存する必要があります。(呼び出しスタックの外のどこか。)

そして、このため、クロージャーという用語を、使用する外部変数のスナップショットを実行できる特別なタイプの関数、またはこれらの変数を後で使用するために格納するために使用されるデータ構造と混同することがよくあります。しかし、これらがクロージャ自体ではないことを理解していただければ幸いです。これらは、プログラミング言語でクロージャを実装する方法、または必要に応じて関数のクロージャからの変数が存在できるようにする言語メカニズムです。クロージャーに関しては多くの誤解があり、(不必要に)この主題を実際よりもはるかに混乱させ、複雑にします。


1
初心者がこれを理解するのに役立つ可能性のある類推は、クロージャーがすべてのルーズエンドを結びつけることです。これは、クロージャーを求めるときに人が行うことです(または必要なすべての参照を解決するか、...)。まあ、それは私がそれをそのように考えるのを助けました:o)
ウィル・クロフォード

私は長年にわたって多くの閉鎖の定義を読みましたが、私はこれがこれまでのところ私のお気に入りだと思います。私たちは皆、このようなコンセプトをメンタルにマッピングする独自の方法を持っていると思います。
Jason S.

29

クロージャーは、別の関数の状態を参照できる関数です。たとえば、Pythonでは、これはクロージャ「inner」を使用します。

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

23

クロージャの理解を容易にするために、手続き型言語でクロージャがどのように実装されるかを調べることが役立つ場合があります。この説明は、Schemeでのクロージャーの単純化された実装に従います。

まず、名前空間の概念を紹介する必要があります。コマンドをSchemeインタープリターに入力すると、式内のさまざまなシンボルを評価してその値を取得する必要があります。例:

(define x 3)

(define y 4)

(+ x y) returns 7

定義式は、xのスポットに値3を、yのスポットに値4を格納します。次に(+ xy)を呼び出すと、インタープリターは名前空間の値を検索し、操作を実行して7を返すことができます。

ただし、Schemeには、シンボルの値を一時的に上書きできる式があります。次に例を示します。

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

letキーワードが行うことは、xを値5とする新しい名前空間を導入することです。yが4であることは引き続き確認でき、合計は9に戻ります。また、式が終了するとxこの意味では、xはローカル値によって一時的にマスクされています。

手続き型言語とオブジェクト指向言語には同様の概念があります。関数内でグローバル変数と同じ名前の変数を宣言すると、同じ効果が得られます。

これをどのように実装しますか?簡単な方法は、リンクリストを使用することです。ヘッドには新しい値が含まれ、テールには古い名前空間が含まれます。シンボルを調べる必要があるときは、頭から始めて尾まで進みます。

とりあえず、ファーストクラスの関数の実装にスキップしましょう。関数は、多かれ少なかれ、関数が呼び出されたときに実行される一連の命令であり、戻り値が最大になります。関数を読み込むとき、これらの命令をバックグラウンドで保存し、関数が呼び出されたときに実行できます。

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

xを3と定義し、plus-xをそのパラメーターyにxの値をプラスしたものと定義します。最後に、xが新しいxでマスクされている環境でplus-xを呼び出します。この値は5です。関数plus-xの演算(+ xy)を格納するだけの場合は、コンテキスト内にあるためxが5の場合、返される結果は9になります。これは、動的スコープと呼ばれるものです。

ただし、Scheme、Common Lisp、および他の多くの言語には、字句スコープと呼ばれるものがあります。操作(+ xy)の格納に加えて、その特定の時点で名前空間も格納します。このようにして、値を調べると、このコンテキストではxが本当に3であることがわかります。これはクロージャです。

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

要約すると、リンクされたリストを使用して、関数定義時の名前空間の状態を格納できます。これにより、囲んでいるスコープから変数にアクセスでき、残りの変数に影響を与えずに変数をローカルでマスクできます。プログラム。


さて、あなたの答えのおかげで、私はついに閉鎖が何であるかについていくつかの考えを持っていると思います。しかし、大きな疑問が1つあります。「リンクリストを使用して、関数の定義時に名前空間の状態を保存し、スコープ内にない変数にアクセスできるようにすることです。」Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer、

@レーザー:すみません、その文はあまり意味がなかったので、更新しました。私はそれが今より理にかなっていると思います。また、リンクされたリストを実装の詳細として考えないでください(非常に非効率的であるため)。しかし、それを行う方法を概念化する簡単な方法として考えてください。
Kyle Cronin、

10

クロージャーがお尻を蹴る理由の実例は次のとおりです...これは私のJavaScriptコードから直接抜粋したものです。説明させてください。

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

そして、これはあなたがそれをどのように使うかです:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

たとえば、このコードスニペットが実行されてから5秒後など、再生を遅延して開始したいとします。まあそれは簡単でdelay、それは閉鎖です:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

msで呼び出すdelay5000、最初のスニペットが実行され、渡された引数がクロージャーに格納されます。その後5秒後、setTimeoutコールバックが発生しても、クロージャーはそれらの変数を維持しているため、元のパラメーターを使用して元の関数を呼び出すことができます。
これはカレーの一種、または機能の装飾です。

クロージャーがないと、関数の外部でこれらの変数の状態を何らかの形で維持する必要があるため、論理的にその内部に属しているもので関数の外部のコードを散らかす必要があります。クロージャーを使用すると、コードの品質と可読性が大幅に向上します。


1
言語またはホストオブジェクトの拡張は、グローバルネームスペースの一部であるため、一般的に悪いことと見なされていることに注意してください
Jon Cooke

9

自由変数を含まない関数は、純粋関数と呼ばれます。

1つ以上の自由変数を含む関数は、クロージャーと呼ばれます。

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src:https : //leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure


なぜこれがマイナスになるのですか?それは実際には、ここにある他のほとんどの無知な答えよりも、自由変数と束縛変数、および純粋/閉関数と不純/開関数に区別される「正しい道」にあります:(関数とクロージャを混同することの割引き)閉じている)。
SasQ 2016

本当にアイデアはありません。これがStackOverflowが悪い理由です。私の回答のソースを見てください。誰がそれについて議論することができますか?
soundyogi

SOは悪くないし、「自由変数」という言葉を聞いたことがない
カイ

自由変数について言及せずにクロージャーについて話すのは難しいです。それらを調べてください。標準のCS用語。
ComDubh

「1つ以上の自由変数を含む関数はクロージャと呼ばれます」は正しい定義ではありません-クロージャは常にファーストクラスのオブジェクトです。
ComDubh

7

tl; dr

クロージャは関数であり、そのスコープは変数に割り当てられた(または変数として使用された)です。したがって、名前のクロージャ:スコープと関数は、他のエンティティと同じように囲まれて使用されます。

ウィキペディアスタイルの詳細な説明

ウィキペディアによると、閉鎖は次のとおりです。

第一級の機能を備えた言語でレキシカルスコープの名前バインディングを実装するためのテクニック。

どういう意味ですか?いくつかの定義を見てみましょう。

この例を使用して、クロージャーとその他の関連定義を説明します。

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

ファーストクラスの機能

基本的には、他のエンティティと同じように関数を使用できることを意味します。それらを変更したり、引数として渡したり、関数から返したり、変数に割り当てたりすることができます。技術的に言えば、彼らは一流の市民ですであり、そのため、名前は一流の機能です。

上記の例でstartAtは、とに割り当てられる(無名)関数を返します。ご覧のとおり、JavaScriptは他のエンティティ(ファーストクラスの市民)と同じように関数を扱います。closure1closure2

名前バインディング

名前バインディングとは、変数(識別子)が参照するデータを見つけることです。バインディングがどのように解決されるかを決定するものなので、ここではスコープが本当に重要です。

上記の例では:

  • 内部の無名関数のスコープでyは、にバインドされ3ます。
  • startAt範囲、xに結合している1、または5(閉鎖に応じて)。

無名関数のスコープ内でxは、値にバインドされていないため、上位(startAt)のスコープで解決する必要があります。

字句スコープ

ウィキペディアは言う、スコープ:

バインディングが有効であるコンピュータープログラムの領域です。名前はエンティティを参照するために使用できます

2つの手法があります。

  • 字句(静的)スコープ:変数の定義は、それを含むブロックまたは関数を検索することによって解決され、その後、外側の包含ブロックの検索に失敗した場合などに解決されます。
  • 動的スコープ:呼び出し元の関数が検索され、次にその呼び出し元の関数を呼び出した関数などが検索され、呼び出しスタックが上に向かって進みます。

詳細については、この質問確認し、ウィキペディアをご覧ください

上記の例では、JavaScriptがレキシカルにスコープされていることがわかります。これは、x解決されると、バインディングがstartAtソースコード(xを検索する無名関数が内部で定義されているstartAt)に基づいて上部('s)スコープで検索されるためです。呼び出しスタック、関数が呼び出された方法(スコープ)に基づいていません。

まとめる(閉じる)

私たちが呼ぶとき、私たちの例では、startAtそれはに割り当てられます(ファーストクラス)関数を返しますclosure1closure2変数を渡すので、これクロージャが作成される1と、5内部に保存されますstartAt返さに同封されている、の範囲を匿名関数。私たちは経由で、この無名関数を呼び出すときclosure1closure2同じ引数(と3)の値がy(それはその関数のパラメータであると)すぐに見つかりますが、x解像度がで続くので、匿名関数のスコープにバインドされていません(クロージャに保存された)(字句)上部関数スコープxのいずれかに結合することが見出されています1またはに5。これで合計のすべてがわかったので、結果を返して印刷することができます。

次に、クロージャーと、クロージャーの動作を理解する必要があります。これは、JavaScriptの基本的な部分です。

カレー

ああ、そしてカリーについても学びました:複数のパラメーターを持つ1つの関数を使用する代わりに、関数(クロージャー)を使用して操作の各引数を渡します。


5

クロージャはJavaScriptの機能であり、関数は独自のスコープ変数へのアクセス、外部関数変数へのアクセス、およびグローバル変数へのアクセスを持ちます。

クロージャーは、外部関数が戻った後でも、その外部関数スコープにアクセスできます。つまり、クロージャーは、関数が終了した後でも、その外部関数の変数と引数を記憶してアクセスできます。

内部関数は、自身のスコープ、外部関数のスコープ、およびグローバルスコープで定義された変数にアクセスできます。また、外部関数は、独自のスコープとグローバルスコープで定義された変数にアクセスできます。

閉鎖の例

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

出力は、その内部関数自身の変数、外部関数変数、およびグローバル変数値の合計である20になります。


4

通常の状況では、変数はスコープ規則によってバインドされます。ローカル変数は、定義された関数内でのみ機能します。クロージャーは、便宜上、このルールを一時的に破る方法です。

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

上記のコードでlambda(|n| a_thing * n}a_thing、ラムダ(匿名関数の作成者)によって参照されるため、クロージャーです。

次に、結果の無名関数を関数変数に入れます。

foo = n_times(4)

fooは通常のスコープ規則を破り、内部で4の使用を開始します。

foo.call(3)

12を返します。


2

つまり、関数ポインタは、プログラムコードベース内の場所へのポインタ(プログラムカウンタなど)にすぎません。一方、クロージャ=関数ポインタ+スタックフレーム


1

•クロージャーはサブプログラムであり、それが定義された参照環境です。

–サブプログラムをプログラム内の任意の場所から呼び出すことができる場合、参照環境が必要です。

–ネストされたサブプログラムを許可しない静的スコープの言語は、クロージャーを必要としません

–クロージャーは、サブプログラムがネストされたスコープ内の変数にアクセスでき、どこからでも呼び出すことができる場合にのみ必要です。

–クロージャーをサポートするために、実装は一部の変数に無制限の範囲を提供する必要がある場合があります(サブプログラムが、通常は生存していない非ローカル変数にアクセスする可能性があるため)。

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

これは別の実際の例であり、ゲームで人気のあるスクリプト言語であるLuaを使用しています。stdinが使用できないという問題を回避するために、ライブラリ関数の動作を少し変更する必要がありました。

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

old_dofileの値は、このコードブロックがそのスコープを終了すると(ローカルであるため)消えますが、値はクロージャーで囲まれているため、新しい再定義されたdofile関数はそれにアクセスできます。 「upvalue」。


0

Lua.orgから:

関数が別の関数で囲まれて記述されている場合、その関数は、囲んでいる関数からローカル変数に完全にアクセスできます。この機能は字句スコープと呼ばれます。それは明白に聞こえるかもしれませんが、そうではありません。字句スコープとファーストクラスの関数はプログラミング言語の強力な概念ですが、その概念をサポートする言語はほとんどありません。


0

Javaの世界から来ている場合は、クロージャーをクラスのメンバー関数と比較できます。この例を見てください

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

関数gはクロージャです:g閉じるagメンバー関数aと比較したり、クラスフィールドと比較したり、クラスを持つ関数を比較したりできますf


0

クロージャー別の関数の内部で関数が定義されている場合は常に、内部関数は外部関数で宣言された変数にアクセスできます。クロージャは、例で最もよく説明されます。リスト2-18では、内側の関数が外側のスコープから変数(variableInOuterFunction)にアクセスできることがわかります。外側の関数の変数が内側の関数によって閉じられている(またはバインドされている)。したがって、用語の閉鎖。コンセプト自体は非常にシンプルで、かなり直感的です。

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

ソース:http : //index-of.es/Varios/Basarat%20Ali%20Syed%20(auth .) -Beginning%20Node.js-Apress%20(2014).pdf


0

閉鎖をより深く理解するには、以下のコードをご覧ください。

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

ここでは何が出力されますか?0,1,2,3,4それは5,5,5,5,5閉鎖のためではありません

では、どのように解決するのでしょうか?答えは以下の通りです:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

簡単に説明します。関数が最初のコードで5回呼び出されてforループを呼び出すまで何も起こらなかった場合、すぐには呼び出されませんでした。つまり、1秒後に呼び出された場合、これは非同期なので、forループが終了して値5を格納します。 var iで、最後にsetTimeout関数を5回実行して印刷します5,5,5,5,5

ここでは、IIFE、つまり即時呼び出し関数式を使用してどのように解決するかを示します。

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

詳しくは、実行コンテキストを理解してクロージャを理解してください。

  • let(ES6機能)を使用してこれを解決するもう1つの解決策がありますが、上記の機能の下で機能します

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=>詳細な説明:

メモリ内で、forループを実行すると、以下のようになります。

ループ1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

ループ2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

ループ3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

ループ4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

ループ5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

ここで私は実行されず、完全なループの後、var iはメモリに値5を格納しましたが、そのスコープはその子関数で常に表示されているため、関数がsetTimeout5回裏返しに実行されると、印刷されます5,5,5,5,5

これを解決するには、上記のIIFEを使用してください。


ご回答有難うございます。コードを説明から分離すると、読みやすくなります。(コードではない行はインデントしないでください)
eMBee

0

カリー化:引数のサブセットを渡すだけで、関数を部分的に評価できます。このことを考慮:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

クロージャー:クロージャーは、関数のスコープ外の変数にアクセスすることに他なりません。関数内の関数またはネストされた関数はクロージャーではないことを覚えておくことが重要です。クロージャーは、関数スコープ外の変数にアクセスする必要がある場合に常に使用されます。

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

0

閉鎖は非常に簡単です。次のように考えることができます:クロージャ=関数+その語彙環境

次の関数を考えてみましょう:

function init() {
    var name = “Mozilla”;
}

上記の場合の閉鎖はどうなりますか?関数init()とそのレキシカル環境の変数、つまり名前。 クロージャ = init()+名前

別の関数を考えてみましょう:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

ここの閉鎖は何ですか?内部関数は外部関数の変数にアクセスできます。displayName()は、親関数init()で宣言された変数名にアクセスできます。ただし、displayName()の同じローカル変数が存在する場合は、それらが使用されます。

クロージャ1: init関数+(名前変数+ displayName()関数)->字句スコープ

クロージャ2: displayName関数+(名前変数)->字句スコープ


0

クロージャーはJavaScriptに状態を提供します。

プログラミングの状態は、単に物事を覚えていることを意味します。

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

上記の場合、状態は変数 "a"に格納されます。続いて、「a」に1を数回追加します。私たちができるのは、その値を「記憶」できるからです。状態保持者 "a"はその値をメモリに保持します。

多くの場合、プログラミング言語では、物事を追跡し、情報を覚えておき、後でアクセスしたい場合があります。

これは、他の言語で、一般的なクラスを使用することにより達成されます。クラスは、変数と同様に、その状態を追跡します。そして、そのクラスのインスタンスもまた、その中に状態を持っています。状態とは、単に保存して後で取得できる情報を意味します。

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

「レンダー」メソッド内から「ウェイト」にアクセスするにはどうすればよいですか?まあ、状態のおかげで。クラスBreadの各インスタンスは、その情報を格納できるメモリ内の場所である「状態」からそれを読み取ることにより、独自の重みをレンダリングできます。

今、JavaScriptは非常にユニークな言語ですあり、これまではクラスがありませんでした(現在はありますが、内部では関数と変数しかありません)。クロージャーは、JavaScriptが物事を記憶して後でアクセスする方法を提供します。

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

上記の例では、変数を使用して「状態を維持する」という目標を達成しました。これは素晴らしい!ただし、これには変数(「状態」ホルダー)が公開されるという欠点があります。私たちはもっとうまくやることができます。クロージャーを使用できます。

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

これは素晴らしいです。

これで「カウント」関数がカウントできるようになりました。状態を「保持」できるため、そうすることしかできません。この場合の状態は変数「n」です。この変数は現在閉じています。時間と空間は閉鎖されています。そのうちに、それを回復したり、変更したり、値を割り当てたり、直接操作したりすることができないためです。「countGenerator」関数内で地理的にネストされているため、空間内。

なぜこれが素晴らしいのですか?他の洗練された複雑なツール(クラス、メソッド、インスタンスなど)を使用しなくても、1。隠蔽2.遠くからの制御

状態、つまり変数 "n"を隠し、プライベート変数にします!また、この変数を事前定義された方法で制御できるAPIも作成しました。特に、「count()」のようにAPIを呼び出すと、「距離」から「n」に1が追加されます。形や形は、API以外では誰も「n」にアクセスできません。

JavaScriptは、そのシンプルさに本当に驚かされます。

クロージャーは、これがなぜかという大きな理由です。


0

参照用のGroovyの簡単な例:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.