時々「クロージャ」が言及されているのを見て、調べてみましたが、Wikiで理解できる説明がありません。誰かここで私を助けてくれませんか?
コメント
- Java / C#を知っているなら、このリンクが役立つことを願っています- http://www.developerfusion.com/article/8251/the-beauty-of-closures/
- クロージャを理解するのは難しいです。そのWikipedia記事の最初の文にあるすべてのリンクをクリックして、それらを理解してみてください。最初の記事。
- stackoverflow.com/questions/36636/what-is-a-closure
- 何’クロージャとクラスの根本的な違いはありますか?わかりました。パブリックメソッドが1つしかないクラスです。
- @biziclop:できますクラスを使用してクロージャをエミュレートします(’ Java開発者が行う必要があることです)。ただし、通常、’は少し冗長ではありません。作成し、あなたは ‘持ち歩いているものを手動で管理する必要はありません。 (ハードコアなlispersも同様の質問をしますが、おそらく他の結論に到達します。クロージャがある場合、言語レベルのOOサポートは不要です。)
回答
(免責事項:これは基本的な説明です。定義に関しては、少し簡略化しています)
クロージャを考える最も簡単な方法は、変数として格納できる関数です(「最初の」と呼ばれます-クラス関数 “)、作成されたスコープにローカルな他の変数にアクセスする特別な機能があります。
例(JavaScript):
var setKeyPress = function(callback) { document.onkeypress = callback; }; var initialize = function() { var black = false; document.onclick = function() { black = !black; document.body.style.backgroundColor = black ? "#000000" : "transparent"; } var displayValOfBlack = function() { alert(black); } setKeyPress(displayValOfBlack); }; initialize();
document.onclick
およびividに割り当てられた関数 1 = “3fee5ad5ad”>
はクロージャです。どちらもブール変数black
を参照していることがわかりますが、その変数は関数の外部に割り当てられています。black
は関数が定義されたスコープに対してローカルです、この変数へのポインタは保持されます。
これをHTMLページに配置すると:
- クリックして黒に変更
- [Enter]を押して「true」を表示
- もう一度クリックすると、白に戻ります
- [入力]を押して「false」を表示します
これは、両方が同じ black
にアクセスできることを示しています。ラッパーオブジェクトなしで 状態を保存するために使用できます。
setKeyPress
の呼び出しは、関数を渡す方法を示すためのものです。他の変数と同じように。クロージャに保存されている scope は、関数が定義されたものです。
クロージャは一般的に特にJavaScriptとActionScriptでイベントハンドラーとして使用されます。クロージャを適切に使用すると、オブジェクトラッパーを作成しなくても、変数をイベントハンドラーに暗黙的にバインドできます。ただし、不注意に使用するとメモリリークが発生します(たとえば、未使用であるが保存されているイベントハンドラが、メモリ内の大きなオブジェクト、特にDOMオブジェクトを保持し、ガベージコレクションを妨げる場合など)。
1:実際、JavaScriptのすべての関数はクロージャです。
コメント
- あなたの答えを読んでいたとき、私は電球が点灯するのを感じました。とても有難い! 🙂
-
black
は関数内で宣言されているため、スタックがほどけるときに破壊されることはありません’。 ..? - @gablin、それがクロージャのある言語のユニークな点です。ガベージコレクションを使用するすべての言語はほぼ同じように機能します。オブジェクトへの参照がなくなると、オブジェクトが破棄される可能性があります。 JSで関数が作成されると、その関数が破棄されるまで、ローカルスコープはその関数にバインドされます。
- @gablin、それは’良い質問です。 ‘彼らが ‘ t &できないと思いますmdash;しかし、JSが使用し、’ “と言ったときに参照しているように見えたので、ガベージコレクションのみを取り上げました。
black
は関数内で宣言されており、’破壊されることはありません”。また、関数でオブジェクトを宣言し、それを別の場所にある変数に割り当てると、そのオブジェクトへの他の参照があるため、そのオブジェクトは保持されることにも注意してください。 - Objective-C(およびCの下のC) clang)は、ガベージコレクションなしで、本質的にクロージャであるブロックをサポートします。ランタイムサポートと、メモリ管理に関する手動の介入が必要です。
回答
クロージャは、基本的にオブジェクトを見る別の方法です。オブジェクトは、1つ以上の関数がバインドされているデータです。クロージャは、1つ以上の変数がバインドされている関数です。この2つは、少なくとも実装レベルでは基本的に同じです。本当の違いは、それらがどこから来ているかです。
オブジェクト指向プログラミングでは、メンバー変数とそのメソッド(メンバー関数)を事前に定義してオブジェクトクラスを宣言してから、次のインスタンスを作成します。そのクラス。各インスタンスには、コンストラクターによって初期化されたメンバーデータのコピーが付属しています。次に、オブジェクトタイプの変数があり、データとしての性質に焦点が当てられているため、データの一部として渡します。
一方、クロージャでは、オブジェクトはそうではありません。オブジェクトクラスのように事前に定義するか、コード内のコンストラクター呼び出しを介してインスタンス化します。代わりに、クロージャを別の関数内の関数として記述します。クロージャは外部関数のローカル変数のいずれかを参照でき、コンパイラはそれを検出して、これらの変数を外部関数のスタックスペースからクロージャの非表示オブジェクト宣言に移動します。これで、クロージャタイプの変数が作成されます。 、そしてそれは基本的に内部のオブジェクトですが、関数としての性質に焦点が当てられているため、関数参照として渡します。
コメント
- +1:良い答えです。クロージャは、メソッドが1つしかないオブジェクトとして表示され、任意のオブジェクトは、いくつかの一般的な基になるデータ(object ‘のメンバー変数)に対するクロージャのコレクションとして表示されます。これらの2つのビューは非常に対称的だと思います。
- 非常に良い答えです。実際には、クロージャの洞察を説明しています。
- @Mason Wheeler:クロージャデータはどこに保存されますか?関数のようにスタックにありますか?または、オブジェクトのようにヒープ内にありますか?
- @RoboAlex:ヒープ内にあるのは、’が関数のように見えるオブジェクトだからです。 。
- @RoboAlex:クロージャーとそのキャプチャされたデータが格納される場所は、実装によって異なります。 C ++では、ヒープまたはスタックに格納できます。
回答
用語クロージャは、コードの一部(ブロック、関数)が closed (つまり、値にバインドされている)コードのブロックが定義されている環境によって閉じられています。
Scala関数の定義を例にとってみましょう。 :
def addConstant(v: Int): Int = v + k
関数本体には、2つの名前(変数)v
と 2つの整数値を示します。名前v
は、関数addConstant
の引数として宣言されているため、バインドされています(関数宣言を見ると、v
には、関数が呼び出されたときに値が割り当てられます)。 k
という名前は、関数addConstant
では無料です。これは、関数にk
は(およびその方法で)バインドされます。
次のような呼び出しを評価するには、次のようにします。
val n = addConstant(10)
を割り当てる必要がありますk
値。これは、名前k
がaddConstant
が定義されています。例:
def increaseAll(values: List[Int]): List[Int] = { val k = 2 def addConstant(v: Int): Int = v + k values.map(addConstant) }
これで、iv id = “2e5c2029cc”のコンテキストでaddConstant
を定義しました。 >
が定義され、addConstant
が閉鎖になりました。自由変数は閉じられました(値にバインドされています):addConstant
できます関数であるかのように呼び出され、渡されます。クロージャが定義されている場合、自由変数k
が値にバインドされることに注意してください一方、クロージャが呼び出されたの場合、引数変数v
がバインドされます。
つまり、クロージャは基本的に、自由変数を介して非ローカル値にアクセスできる関数またはコードブロックです。これらの値は、コンテキストによってバインドされた後です。
多くの言語では、クロージャは、匿名にできる場合にのみ使用してください。例:
def increaseAll(values: List[Int]): List[Int] = { val k = 2 values.map(v => v + k) }
自由変数のない関数は、(自由変数の空のセットを持つ)クロージャの特殊なケースであることに注意してください。同様に、無名関数は、無名クロージャの特殊なケースです、つまり無名関数は自由変数のない無名クロージャです。
コメント
- このジャイブは、ロジック内の閉じた式と開いた式に適しています。回答ありがとうございます。
- @RainDoctor:自由変数は、論理式とラムダ計算式で同様の方法で定義されます。ラムダ式のラムダは、自由変数/束縛変数を使用した論理式の数量詞のように機能します。 。
回答
JavaScriptでの簡単な説明:
var closure_example = function() { var closure = 0; // after first iteration the value will not be erased from the memory // because it is bound with the returned alertValue function. return { alertValue : function() { closure++; alert(closure); } }; }; closure_example();
alert(closure)
は、以前に作成されたclosure
の値を使用します。返されたalertValue
関数の名前空間は、closure
変数が存在する名前空間に接続されます。関数全体を削除すると、 closure
変数の値は削除されますが、それまでは、alertValue
関数は常に変数の値を読み書きできます。 closure
。
このコードを実行すると、最初の反復で値0がclosure
変数に割り当てられます。関数を次のように書き直します。
var closure_example = function(){ alertValue : function(){ closure++; alert(closure); } }
また、alertValue
にはローカル変数closure
関数を実行するには、以前に割り当てられたローカル変数closure
の値にバインドします。
これで、
関数の場合、alert(closure)
はであるため、closure
変数の増分値を書き出します。バインドされています。
closure_example.alertValue()//alerts value 1 closure_example.alertValue()//alerts value 2 closure_example.alertValue()//alerts value 3 //etc.
コメント
- ありがとう、私はしませんでした’ tはコードをテストします=)今はすべて問題ないようです。
回答
「閉鎖」とは、本質的に、いくつかのローカル状態といくつかのコードがパッケージに結合されます。通常、ローカル状態は周囲の(字句)スコープから取得され、コードは(本質的に)内部関数であり、その後外部に返されます。クロージャは、内部関数が認識するキャプチャされた変数と内部関数のコードの組み合わせです。
残念ながら、これは説明が少し難しいものの1つです。なじみがない。
私が過去にうまく使ったアナロジーの1つは、「私たちが「本」と呼ぶものがあると想像してください。部屋のクロージャーに、「本」はそのコピーが隅にあります。 、TAOCPの、しかしテーブルクロージャでは、それはドレスデンファイルの本のコピーです。したがって、どのクロージャにいるかに応じて、コード「give methebook」はさまざまな結果になります。」
コメント
- これを忘れました: en.wikipedia.org/wiki/Closure_(computer_programming)あなたの答えに。
- いいえ、私はそのページを閉じないことを選択しました。
- “状態と機能。”:
static
ローカル変数を持つC関数はクロージャと見なすことができますか?クロージャはありますか? Haskellでは状態が関係していますか? - Haskellでの@Giorgioクロージャは、’で定義されている字句スコープの引数を閉じます(私は信じています)。 ‘ d say “はい”(私はHaskellに精通していませんが)。静的変数を使用するAC関数は、せいぜい非常に限定されたクロージャです(
static
ローカル変数を使用して、単一の関数から複数のクロージャを作成できるようにする必要があります。 - 静的変数を持つC関数はクロージャではないと思うので、意図的にこの質問をしました。静的変数はローカルで定義され、クロージャ内でのみ認識され、アクセスしません。環境。また、100%確信はありませんが、逆にステートメントを作成します。クロージャメカニズムを使用して、さまざまな関数を作成します(関数は、クロージャ定義+自由変数のバインディングです)。
回答
「状態」の概念を定義せずに、クロージャとは何かを定義することは困難です。
基本的に、関数をファーストクラスの値として扱う完全な字句スコープを持つ言語では、何か特別なことが起こります。次のようなことをした場合:
function foo(x) return x end x = foo
変数x
はですが、最後に戻ったときにfoo
が残っていた状態も参照します。本当の魔法は、foo
がそのスコープ内でさらに定義された他の関数を持っているときに起こります。それはそれ自身のミニ環境のようなものです(「通常」私たちがグローバル環境で関数を定義するのと同じように)。
機能的には、C ++(C?) “s” static “キーワード。これは、複数の関数呼び出しを通じてローカル変数の状態を保持します。ただし、関数はファーストクラスの値であるため、同じ原則(静的変数)を関数に適用するのに似ています。クロージャは、関数全体の状態を保存するためのサポートを追加します(C ++の静的関数とは関係ありません)。
関数をファーストクラスの値として扱い、クロージャのサポートを追加することは、メモリ内に同じ関数の複数のインスタンスを持つことができることも意味します(クラスと同様)。これは、を再利用できることを意味します。関数内のC ++静的変数を処理するときに必要な、関数の状態をリセットする必要のない同じコード(これについて間違っている可能性がありますか?)
Luaのクロージャサポートのテストをいくつか示します。 。
--Closure testing --By Trae Barlow -- function myclosure() print(pvalue)--nil local pvalue = pvalue or 10 return function() pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed) print(pvalue) pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls) return pvalue end end x = myclosure() --x now references anonymous function inside myclosure() x()--nil, 20 x() --21, 31 x() --32, 42 --43, 53 -- if we iterated x() again
結果:
nil 20 31 42
注意が必要な場合があり、おそらく変動します言語ごとに異なりますが、Luaでは、関数が実行されるたびにその状態がリセットされるようです。iv id = “f9bf13にアクセスした場合、上記のコードの結果が異なるため、これを言います。 pvalue
は10にリセットされるため、f69c “>
関数/状態を直接(匿名関数を介してではなく)。しかし、x(匿名関数)を介してmyclosureの状態にアクセスすると、pvalue
が生きていて、メモリのどこかにあることがわかります。おそらく、もう少しあると思います。誰かが実装の性質をよりよく説明できます。
PS:私はC ++ 11のなめらかさを知らないので(以前のバージョンのものを除いて)、これは比較ではないことに注意してくださいC ++ 11とLuaのクロージャの間。また、LuaからC ++に描画されるすべての「線」は、静的変数とクロージャが100%同じではないため、類似しています。同様の問題を解決するために使用されることもあります。
上記のコード例では、無名関数と高階関数のどちらがクロージャと見なされるのかわかりません。
回答
クロージャは、状態が関連付けられている関数です。
perlでは、次のようなクロージャを作成します。
#!/usr/bin/perl # This function creates a closure. sub getHelloPrint { # Bind state for the function we are returning. my ($first) = @_;a # The function returned will have access to the variable $first return sub { my ($second) = @_; print "$first $second\n"; }; } my $hw = getHelloPrint("Hello"); my $gw = getHelloPrint("Goodby"); &$hw("World"); // Print Hello World &$gw("World"); // PRint Goodby World
C ++で提供される新機能を見ると、
現在の状態をオブジェクトにバインドすることもできます。
#include <string> #include <iostream> #include <functional> std::function<void(std::string const&)> getLambda(std::string const& first) { // Here we bind `first` to the function // The second parameter will be passed when we call the function return [first](std::string const& second) -> void { std::cout << first << " " << second << "\n"; }; } int main(int argc, char* argv[]) { auto hw = getLambda("Hello"); auto gw = getLambda("GoodBye"); hw("World"); gw("World"); }
回答
簡単な関数を考えてみましょう:
function f1(x) { // ... something }
この関数は、他の関数内にネストされていないため、トップレベル関数と呼ばれます。すべて JavaScript関数は、“スコープチェーン “と呼ばれるオブジェクトのリストをそれ自体に関連付けます。このスコープチェーンはオブジェクトの順序付きリスト。Eこれらのオブジェクトはそれぞれ、いくつかの変数を定義します。
トップレベルの関数では、スコープチェーンは単一のオブジェクトであるグローバルオブジェクトで構成されます。たとえば、上記の関数f1
には、すべてのグローバル変数を定義する単一のオブジェクトを含むスコープチェーンがあります。 (ここでの「オブジェクト」という用語はJavaScriptオブジェクトを意味するのではなく、JavaScriptが変数を「検索」できる変数コンテナーとして機能する実装定義のオブジェクトにすぎないことに注意してください。)
この場合関数が呼び出されると、JavaScriptは「アクティベーションオブジェクト」と呼ばれるものを作成し、スコープチェーンの最上位に配置します。これはオブジェクトにはすべてのローカル変数が含まれます(たとえば、ここではx
)。したがって、スコープチェーンに2つのオブジェクトがあります。1つ目はアクティベーションオブジェクトで、その下にグローバルオブジェクトがあります。
2つのオブジェクトは異なる時間にスコープチェーンに配置されることに注意してください。グローバルオブジェクトは、関数が定義されたとき(つまり、JavaScriptが関数を解析して関数オブジェクトを作成したとき)に配置されます。関数が呼び出されると、アクティベーションオブジェクトが入ります。
これで、次のことがわかります。
- すべての関数にはスコープチェーンが関連付けられています
- いつ関数が定義されると(関数オブジェクトの作成時に)、JavaScriptはその関数とともにスコープチェーンを保存します
- トップレベル関数の場合、スコープチェーンには関数定義時にグローバルオブジェクトのみが含まれ、さらに追加されます呼び出し時に最上位のアクティベーションオブジェクト
ネストされた関数を処理すると、状況が興味深いものになります。それでは、1つ作成しましょう:
function f1(x) { function f2(y) { // ... something } }
f1
が定義されると、それを含むスコープチェーンを取得しますグローバルオブジェクトのみ。
f1
が呼び出されると、f1
のスコープチェーンがアクティベーションオブジェクトを取得します。このアクティベーションオブジェクトには、変数x
と関数である変数f2
が含まれています。また、f2
が定義されています。したがって、この時点で、JavaScriptはf2
の新しいスコープチェーンも保存します。 この内部関数用に保存されたスコープチェーンは、現在有効なスコープチェーンです。 現在のスコープ有効なチェーンはf1
“のチェーンです。したがって、f2
のスコープチェーンはf1
“s current スコープチェーン-f1
のアクティベーションオブジェクトが含まれていますおよびグローバルオブジェクト。
f2
が呼び出されると、y
、f1
のアクティベーションオブジェクトとグローバルオブジェクトがすでに含まれているスコープチェーンに追加されました。
f2
の場合、そのスコープチェーンには、定義時に3つのオブジェクト(2つの外部関数の2つのアクティブ化オブジェクトとグローバルオブジェクト)が含まれ、呼び出し時に4つ含まれます。
つまり、今、私たちはアンダースコープチェーンがどのように機能するかについては説明しますが、クロージャについてはまだ説明していません。
関数オブジェクトとスコープの組み合わせ(変数バインディングのセット)関数の変数が解決される場所は、コンピューターサイエンスの文献ではクロージャと呼ばれています- JavaScriptのDavidFlanaganによる決定的なガイド
ほとんどの関数は、関数が定義されたときに有効だったのと同じスコープチェーンを使用して呼び出され、クロージャが関係していることは実際には問題ではありません。クロージャは、定義されたときに有効だったスコープチェーンとは異なるスコープチェーンで呼び出されたときに興味深いものになります。これは、ネストされた関数オブジェクトが、それが定義された関数から返されたの場合に最も一般的に発生します。
関数が戻ると、そのアクティベーションオブジェクトはスコープチェーンから削除されます。ネストされた関数がなかった場合、アクティベーションオブジェクトへの参照はなくなり、ガベージコレクションが行われます。ネストされた関数が定義されている場合、それらの各関数にはスコープチェーンへの参照があり、そのスコープチェーンはアクティベーションオブジェクトを参照します。
ただし、これらのネストされた関数オブジェクトが外部関数内に残っている場合。次に、それら自体が、参照したアクティベーションオブジェクトとともにガベージコレクションされます。ただし、関数がネストされた関数を定義して返すか、プロパティのどこかに格納する場合は、ネストされた関数への外部参照があります。ガベージコレクションは行われず、参照するアクティベーションオブジェクトもガベージコレクションされません。
上記の例では、f2
from f1
したがって、f1
の呼び出しが返されると、そのアクティベーションオブジェクトはスコープチェーンから削除され、ガベージコレクションが行われます。ただし、次のようなものがある場合:
function f1(x) { function f2(y) { // ... something } return f2; }
ここで、返されるf2
にはスコープチェーンがあります。 f1
のアクティベーションオブジェクトが含まれているため、ガベージコレクションは行われません。この時点で、f2
を呼び出すと、f1
の変数x
「f1
から外れていても。
したがって、関数がスコープチェーンを保持していることがわかります。外部関数のすべてのアクティベーションオブジェクトが付属しています。これがクロージャの本質です。JavaScriptの関数は「字句スコープ」、つまり、呼び出されたときにアクティブだったスコープではなく、定義されたときにアクティブだったスコープを保存します。
プライベート変数の近似などのクロージャを含む強力なプログラミング手法がいくつかあります。 、イベント駆動型プログラミング、部分アプリケーションなど。
また、これはすべて、クロージャをサポートするすべての言語に適用されることに注意してください。たとえば、 PHP(5.3以降)、Python、Rubyなど
回答
クロージャはコンパイラの最適化です(別名シンタックスシュガー?)。これを貧乏人のオブジェクトと呼ぶ人もいます。
Eric Lippertによる回答を参照してください :(以下の抜粋)
コンパイラは次のようなコードを生成します:
private class Locals { public int count; public void Anonymous() { this.count++; } } public Action Counter() { Locals locals = new Locals(); locals.count = 0; Action counter = new Action(locals.Anonymous); return counter; }
意味がありますか?
また、比較を求めました。VBとJScriptはどちらも、ほぼ同じ方法でクロージャを作成します。
コメント
- 私は’ Eric ‘の素晴らしい答え。必要に応じて賛成してください。 HTH
- -1:あなたの説明はC#に根付いています。 クロージャは多くの言語で使用されており、これらの言語の糖衣構文をはるかに超えており、機能と状態の両方を網羅しています。
- いいえ、クロージャは単なる”コンパイラの最適化”またはシンタックスシュガー。 -1