때때로 나는 "폐쇄 물"이 언급되는 것을보고, 그것을 찾아 보려고 시도했지만 Wiki는 내가 이해하고있는 설명을하지 않았다. 누군가 나를 도와 줄 수 있습니까?
때때로 나는 "폐쇄 물"이 언급되는 것을보고, 그것을 찾아 보려고 시도했지만 Wiki는 내가 이해하고있는 설명을하지 않았다. 누군가 나를 도와 줄 수 있습니까?
답변:
(면책 조항 : 이것은 기본적인 설명입니다. 정의가있는 한, 조금 단순화하고 있습니다)
클로저를 생각하는 가장 간단한 방법 은 변수 ( "일류 함수"라고 함)로 저장 될 수 있는 함수이며 ,이 변수는 작성된 범위 내에서 다른 변수에 액세스 할 수있는 특별한 기능을 가지고 있습니다.
예 (자바 스크립트) :
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();
기능 1 에 할당 document.onclick
하고는 displayValOfBlack
폐쇄된다. 둘 다 부울 변수를 참조 black
하지만 해당 변수가 함수 외부에 할당되어 있음을 알 수 있습니다. 왜냐하면 black
인 함수가 정의 된 범위에 국부적으로 ,이 변수에 대한 포인터는 보존된다.
이것을 HTML 페이지에 넣으면 :
이것은 둘 다 same에 액세스 할 black
수 있으며 랩퍼 오브젝트 없이 상태를 저장하는 데 사용될 수 있음을 보여줍니다 .
에 대한 호출 setKeyPress
은 어떤 변수처럼 함수를 전달할 수 있는지 보여줍니다. 범위 폐쇄 보존 여전히 함수가 정의 된 하나이다.
클로저는 일반적으로 이벤트 핸들러, 특히 JavaScript 및 ActionScript에서 사용됩니다. 클로저를 잘 사용하면 객체 래퍼를 만들지 않고도 변수를 이벤트 핸들러에 암시 적으로 바인딩 할 수 있습니다. 그러나 부주의하게 사용하면 메모리 누수가 발생합니다 (예 : 사용되지 않지만 보존 된 이벤트 핸들러가 메모리의 큰 오브젝트, 특히 DOM 오브젝트를 보유하여 가비지 콜렉션을 방지하는 유일한 경우).
1 : 실제로 JavaScript의 모든 함수는 클로저입니다.
black
함수 내에서 선언 된 스택이 풀릴, 그이 ... 파괴되지 것?
black
함수 안에 선언되었으므로 파괴되지 않을 것 "이라고 말했을 때 참조한 것 같습니다 . 함수에서 객체를 선언 한 다음 다른 곳에 존재하는 변수에 할당하면 해당 객체에 대한 다른 참조가 있기 때문에 해당 객체가 유지된다는 점을 기억하십시오.
클로저는 기본적으로 객체를 보는 다른 방법입니다. 객체는 하나 이상의 함수가 바인딩 된 데이터입니다. 클로저는 하나 이상의 변수가 바인딩 된 함수입니다. 둘은 구현 수준에서 최소한 기본적으로 동일합니다. 실제 차이점은 어디에서 왔는지에 있습니다.
객체 지향 프로그래밍에서는 멤버 변수와 메서드 (멤버 함수)를 미리 정의하여 객체 클래스를 선언 한 다음 해당 클래스의 인스턴스를 만듭니다. 각 인스턴스에는 생성자에 의해 초기화 된 멤버 데이터의 사본이 제공됩니다. 그런 다음 객체 유형의 변수가 있고 데이터의 성격에 중점을두기 때문에 데이터로 전달합니다.
반면 클로저에서는 객체가 객체 클래스처럼 미리 정의되거나 코드에서 생성자 호출을 통해 인스턴스화되지 않습니다. 대신 클로저를 다른 함수 내부의 함수로 작성합니다. 클로저는 외부 함수의 로컬 변수를 참조 할 수 있으며 컴파일러는이를 감지하여 이러한 변수를 외부 함수의 스택 공간에서 클로저의 숨겨진 개체 선언으로 이동합니다. 그런 다음 클로저 유형의 변수가 있으며 기본적으로 후드 아래의 객체이지만 포커스는 그 특성에 초점을 맞추기 때문에 함수 참조로 전달합니다.
클로저 라는 용어 는 코드 조각 (블록, 함수) 이 코드 블록이 정의 된 환경에 의해 닫히는 (즉, 값에 바인딩 된) 자유 변수를 가질 수 있다는 사실에서 비롯 됩니다.
예를 들어 스칼라 함수 정의를 보자.
def addConstant(v: Int): Int = v + k
함수 본문에는 두 개의 이름 (변수)이 v
있으며 k
두 개의 정수 값을 나타냅니다. 이름 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)
}
이제 우리는 정의 addConstant
된 컨텍스트에서 k
정의 했으므로 모든 자유 변수가 이제 닫혀 있기 때문에 클로저addConstant
가되었습니다 (값에 바인딩 됨). 마치 함수처럼 호출되고 전달 될 수 있습니다. 자유 변수 는 클로저가 정의 될 때 값에 바인딩되는 반면 인수 변수 는 클로저가 호출 될 때 바인딩 됩니다 .addConstant
k
v
따라서 클로저는 기본적으로 컨텍스트에 의해 바인딩 된 후 자유 변수를 통해 로컬이 아닌 값에 액세스 할 수있는 함수 또는 코드 블록입니다.
많은 언어에서 클로저를 한 번만 사용하면 익명으로 만들 수 있습니다 ( 예 :
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
자유 변수가없는 함수는 빈 경우 (빈 자유 변수 세트가있는) 특수한 경우입니다. 마찬가지로 익명 함수 는 익명 클로저 의 특별한 경우입니다. 즉, 익명 함수는 자유 변수가없는 익명 클로저입니다.
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
함수는 항상 variable 값을 읽고 쓸 수 있습니다 closure
.
이 코드를 실행하면 첫 번째 반복에서 값 0을 closure
변수에 할당 하고 함수를 다음과 같이 다시 작성합니다.
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
함수를 실행 alertValue
하려면 로컬 변수 closure
가 필요 하기 때문에 이전에 할당 된 로컬 변수의 값과 자신을 바인딩합니다 closure
.
이제 closure_example
함수 를 호출 할 때마다 바인딩 된 closure
변수 의 증분 값을 씁니다 alert(closure)
.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
"클로저"는 본질적으로 패키지에 결합 된 일부 로컬 상태 및 코드입니다. 일반적으로 로컬 상태는 주변 (어휘) 범위에서 나오며 코드는 본질적으로 내부 함수이며 외부로 반환됩니다. 그러면 클로저는 내부 함수가 보는 캡처 된 변수와 내부 함수의 코드의 조합입니다.
불행히도 익숙하지 않기 때문에 설명하기가 어려운 것들 중 하나입니다.
제가 과거에 성공적으로 사용한 한 유추는 "우리가 '책'이라고 부르는 것을 상상해보십시오. 방을 닫을 때 '책'은 TAOCP의 모퉁이, 탁자에 있습니다. , 그것은 드레스덴 파일 책의 사본입니다. 따라서 폐쇄 된 내용에 따라 '책을주세요'라는 코드로 인해 다른 일이 발생합니다. "
static
지역 변수가 있는 C 함수를 클로저로 간주 할 수 있습니까? Haskell의 폐쇄는 국가와 관련이 있습니까?
static
로컬 변수를 사용하면 단일 함수에서 여러 클로저를 만들 수 있기를 정말로 원합니다 ).
'상태'의 개념을 정의하지 않고 폐쇄가 무엇인지 정의하는 것은 어렵습니다.
기본적으로 함수를 일급 값으로 취급하는 완전한 어휘 범위가있는 언어에서는 특별한 일이 발생합니다. 내가 다음과 같은 일을해야한다면 :
function foo(x)
return x
end
x = foo
변수 x
는 참조 function foo()
뿐만 아니라 foo
마지막에 반환 된 상태를 참조합니다 . 실제 마술 foo
은 다른 기능이 그 범위 내에서 더 정의 될 때 발생합니다 . 그것은 자체 미니 환경과 같습니다 (글로벌 환경에서 함수를 정의하는 '일반적으로'와 같습니다).
기능적으로 여러 함수 호출에서 로컬 변수의 상태를 유지하는 C ++ (C?)의 '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
까다로울 수 있으며 언어마다 다를 수 있지만 루아에서는 함수가 실행될 때마다 상태가 재설정되는 것으로 보입니다. 나는 우리가 myclosure
함수 / 상태에 직접 액세스한다면 (익명 함수를 통해 반환하지 않고) pvalue
10으로 다시 재설정 되는 경우 위 코드의 결과가 다르기 때문에 이것을 말한다 . 그러나 x (익명 함수)를 통해 myclosure의 상태에 액세스하면 pvalue
메모리의 어딘가에 살아 있음을 알 수 있습니다 . 나는 그것에 조금 더 있다고 생각합니다. 아마도 누군가 구현의 본질을 더 잘 설명 할 수 있습니다.
추신 : 나는 C ++ 11 (이전 버전의 것 이외)을 핥지 못하므로 이것이 C ++ 11과 Lua의 클로저 사이의 비교가 아니라는 점에 유의하십시오. 또한 Lua에서 C ++ 로의 모든 '라인'은 정적 변수와 클로저가 100 % 동일하지 않기 때문에 유사합니다. 그것들이 때때로 비슷한 문제를 해결하는데 사용 되더라도.
내가 확실하지 않은 것은 위의 코드 예제에서 익명 함수 또는 상위 함수가 클로저로 간주되는지 여부입니다.
클로저는 상태가 관련된 함수입니다.
펄에서는 다음과 같이 클로저를 만듭니다.
#!/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 함수는 "Scope Chain" 이라는 객체 목록을 자체와 연관시킵니다 . 이 범위 체인은 정렬 된 객체 목록입니다. 이러한 각 객체는 일부 변수를 정의합니다.
최상위 기능에서 스코프 체인은 단일 객체 인 전역 객체로 구성됩니다. 예를 들어, f1
위 함수 에는 모든 전역 변수를 정의하는 단일 객체가 포함 된 범위 체인이 있습니다. (여기서 "object"라는 용어는 JavaScript 객체를 의미하는 것이 아니라 JavaScript가 변수를 "찾을"수있는 변수 컨테이너 역할을하는 구현 정의 객체 일뿐입니다.)
이 함수가 호출되면 JavaScript는 "Activation object" 라는 것을 생성하고이를 스코프 체인의 맨 위에 놓습니다. 이 객체는 모든 로컬 변수를 포함합니다 (예 : x
여기). 이제 스코프 체인에 두 개의 객체가 있습니다. 첫 번째 객체는 활성화 객체이고 그 아래는 전역 객체입니다.
두 개체는 서로 다른 시간에 스코프 체인에 배치됩니다. 전역 객체는 함수가 정의 될 때 (즉, JavaScript가 함수를 구문 분석하고 함수 객체를 생성 할 때) 놓이고, 활성화 객체는 함수가 호출 될 때 들어갑니다.
이제 우리는 이것을 알고 있습니다 :
중첩 함수를 처리 할 때 상황이 흥미로워집니다. 자, 하나를 만들어 봅시다 :
function f1(x) {
function f2(y) {
// ... something
}
}
f1
정의 되면 전역 객체 만 포함하는 범위 체인을 얻습니다.
이제 f1
호출되면 범위 체인이 f1
활성화 객체 를 가져옵니다. 이 활성화 객체는 변수 x
와 f2
함수 인 변수 를 포함합니다 . 그리고 그것은 f2
정의되고 있습니다. 따라서 현재 JavaScript는에 대한 새로운 범위 체인을 저장합니다 f2
. 이 내부 기능에 대해 저장된 범위 체인은 현재 범위 체인입니다. 현재 스코프 체인은입니다 f1
. 그러므로 f2
의 범위 체인은 f1
S ' 현재 의 활성 객체 포함 - 범위 체인 f1
글로벌 개체.
f2
이 호출 되면을 포함하는 자체 활성화 객체를 y
가져와 이미 범위의 활성화 객체 f1
와 전역 객체를 포함하는 범위 체인에 추가됩니다 .
내에 정의 된 다른 중첩 함수가있는 f2
경우 해당 범위 체인에는 정의시 3 개의 객체 (두 외부 함수의 활성화 객체 2 개와 전역 객체)가 있고 호출 시간에는 4 개가 포함됩니다.
이제 스코프 체인의 작동 방식을 이해했지만 클로저에 대해서는 아직 언급하지 않았습니다.
함수 객체와 함수 변수의 범위가 결정되는 범위 (변수 바인딩 세트)의 조합을 컴퓨터 과학 문헌의 폐쇄라고합니다. JavaScript는 David Flanagan의 최종 안내서입니다.
대부분의 함수는 함수가 정의되었을 때 유효한 것과 동일한 범위 체인을 사용하여 호출되며 클로저가 관련되어 있는지는 중요하지 않습니다. 클로저는 정의되었을 때 유효한 것과 다른 범위 체인에서 호출 될 때 흥미로워집니다. 중첩 함수 오브젝트가 정의 된 함수에서 리턴 될 때 가장 일반적으로 발생합니다 .
함수가 반환되면 해당 활성화 개체가 범위 체인에서 제거됩니다. 중첩 함수가 없으면 활성화 객체에 대한 참조가 더 이상 없으며 가비지 수집됩니다. 중첩 함수가 정의 된 경우 해당 함수 각각에 범위 체인에 대한 참조가 있으며 해당 범위 체인은 활성화 오브젝트를 나타냅니다.
그러나 중첩 된 함수 객체가 외부 함수 내에 남아 있으면 참조한 활성화 객체와 함께 가비지 수집됩니다. 그러나 함수가 중첩 함수를 정의하여 반환하거나 어딘가에 속성에 저장하면 중첩 함수에 대한 외부 참조가 있습니다. 가비지 수집되지 않으며 참조하는 활성화 객체도 가비지 수집되지 않습니다.
위의 예에서는 f2
에서 반환하지 않으므로 반환 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는 모두 거의 같은 방식으로 클로저를 만듭니다.