나는 카레에 관한 질문을하고 폐쇄가 언급되었다. 폐쇄 란 무엇입니까? 카레와는 어떤 관계가 있습니까?
답변
변수 범위
지역 변수를 선언하면 해당 변수에 범위가 있습니다. 일반적으로 지역 변수는 선언 한 블록이나 함수 내에 만 존재합니다.
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
후에도 지속되도록 변수에 할당하면 정의 되었을 때 범위 내에 있던 모든 변수 도 유지됩니다 . 변수 가 닫혔습니다 . 변수 가 닫힙니다.outer
inner
a
변수 a
는 전적으로 개인용 fnc
입니다. 이는 JavaScript와 같은 기능적 프로그래밍 언어로 개인 변수를 작성하는 방법입니다.
당신이 짐작할 수 있듯이, 호출 fnc()
하면 a
“1” 의 값을 인쇄합니다 .
클로저가없는 언어에서는 변수 a
가 가비지 수집되어 함수 outer
가 종료 될 때 버려졌습니다 . a
더 이상 존재하지 않으므로 fnc를 호출하면 오류가 발생했습니다 .
JavaScript a
에서 변수 범위는 함수가 처음 선언 될 때 작성되고 함수가 계속 존재하는 한 지속되므로 변수 가 지속됩니다.
a
의 범위에 속합니다 outer
. 의 범위에는의 범위에 inner
대한 부모 포인터가 outer
있습니다. fnc
를 가리키는 변수입니다 inner
. a
지속되는 한 fnc
지속됩니다. a
폐쇄 내에 있습니다.
답변
JavaScript를 사용하여 예를 들어 보겠습니다.
function makeCounter () {
var count = 0;
return function () {
count += 1;
return count;
}
}
var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...
이 함수 인 makeCounter는 x를 호출 한 함수가 호출 될 때마다 하나씩 카운트되는 함수를 반환합니다. x에 매개 변수를 제공하지 않기 때문에 어떻게 든 카운트를 기억해야합니다. 어휘 범위 지정을 기반으로 찾을 위치를 알고 있습니다. 값을 찾기 위해 정의 된 지점을 찾아야합니다. 이 “숨겨진”값을 클로저라고합니다.
여기 내 커리 예가 있습니다.
function add (a) {
return function (b) {
return a + b;
}
}
var add3 = add(3);
add3(4); returns 7
당신이 볼 수있는 것은 매개 변수 a (3)로 add를 호출하면 add3으로 정의 된 반환 된 함수의 클로저에 해당 값이 포함된다는 것입니다. 이런 식으로 add3을 호출하면 덧셈을 수행 할 값을 찾을 위치를 알 수 있습니다.
답변
카일의 대답 은 꽤 좋습니다. 유일한 추가 설명은 클로저가 기본적으로 람다 함수가 생성되는 시점에서 스택의 스냅 샷이라는 것입니다. 그런 다음 기능이 다시 실행될 때 기능을 실행하기 전에 스택이 해당 상태로 복원됩니다. 따라서 Kyle이 언급했듯이 count
람다 함수가 실행될 때 숨겨진 값 ( )을 사용할 수 있습니다.
답변
우선, 대부분의 사람들이 말한 것과는 반대로, 폐쇄는 기능 이 아닙니다 ! 그래서 입니다 그것은?
그것은 인 세트 (그 알려진 함수의 “주위 환경”의 정의 심볼 환경 그것 (즉, 각 심볼을 정의하는 값을 갖는다되는 표현식이 때문에이 평가 될 수있다) 밀폐 식 확인).
예를 들어, 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
가 호출 될 때 내부 함수가 있어야 하기 때문에 문제 가됩니다! 다시 말해, 랩퍼 함수 보다 오래 지속 되고 필요할 때 거기에 있도록 변수의 클로저에서 변수 가 필요합니다. 따라서 내부 함수는 이러한 변수 의 스냅 샷 을 만들어서 닫히고 나중에 사용할 수 있도록 안전한 곳에 보관해야합니다. (콜 스택 외부의 어딘가에 있습니다.)
그렇기 때문에 사람들은 종종 폐쇄 라는 용어 를 그들이 사용하는 외부 변수의 스냅 샷을 만들 수있는 특수한 유형의 함수 또는 나중에 이러한 변수를 저장하는 데 사용되는 데이터 구조로 혼동하는 이유 입니다. 그러나 나는 당신이 그들이 이니까 이해 희망 하지 폐쇄 자체 – 그들은 단지 방법입니다 구현 필요할 때 함수의 폐쇄에서 변수가 될 수있는 프로그래밍 언어 또는 언어 메커니즘에 클로저를. 클로저에 대한 많은 오해가 있습니다.이 주제는 (불필요하게)이 주제를 실제로보다 혼란스럽고 복잡하게 만듭니다.
답변
클로저는 다른 함수의 상태를 참조 할 수있는 함수입니다. 예를 들어, 파이썬에서는 “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
답변
클로저에 대한 이해를 돕기 위해 프로 시저 언어로 어떻게 구현되는지 조사하는 것이 유용 할 수 있습니다. 이 설명은 Scheme에서 클로저를 간단하게 구현 한 후 따릅니다.
시작하려면 네임 스페이스 개념을 소개해야합니다. Scheme 인터프리터에 명령을 입력하면 표현식의 다양한 기호를 평가하고 해당 값을 가져와야합니다. 예:
(define x 3)
(define y 4)
(+ x y) returns 7
정의 표현식은 x의 자리에 값 3을, y의 자리에 값 4를 저장합니다. 그런 다음 (+ xy)를 호출하면 인터프리터는 네임 스페이스에서 값을 찾고 작업을 수행하고 7을 반환 할 수 있습니다.
그러나 구성표에는 기호 값을 임시로 재정의 할 수있는 표현식이 있습니다. 예를 들면 다음과 같습니다.
(define x 3)
(define y 4)
(let ((x 5))
(+ x y)) returns 9
x returns 3
let 키워드의 기능은 x가 값 5 인 새로운 네임 스페이스를 도입하는 것입니다. y가 4라는 것을 여전히 볼 수 있으며, 합계는 9로 반환됩니다. 이런 의미에서 x는 일시적으로 로컬 값으로 가려져 있습니다.
절차 언어와 객체 지향 언어는 비슷한 개념을 가지고 있습니다. 전역 변수와 이름이 같은 함수에서 변수를 선언 할 때마다 동일한 효과를 얻습니다.
우리는 이것을 어떻게 구현할 것입니까? 간단한 방법은 연결된 목록을 사용하는 것입니다. 머리에는 새로운 값이 포함되고 꼬리에는 오래된 네임 스페이스가 포함됩니다. 기호를 찾아야 할 때 머리부터 시작하여 꼬리를 따라 내려갑니다.
이제는 일류 함수 구현으로 건너 뛰겠습니다. 대체로 함수는 함수가 반환 값에서 절정이라고 할 때 실행할 명령 집합입니다. 함수를 읽을 때 이러한 명령어를 씬 뒤에 저장하고 함수가 호출 될 때 실행할 수 있습니다.
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns ?
x를 3으로 정의하고 더하기 x를 매개 변수 y로 정의하고 x 값을 정의합니다. 마지막으로 우리는 x가 새로운 x에 의해 가려진 환경에서 더하기 -x를 호출합니다.이 값은 5입니다. 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
요약하면, 링크 된리스트를 사용하여 함수를 정의 할 때 네임 스페이스의 상태를 저장할 수 있으며, 둘러싸는 범위에서 변수에 액세스 할 수있을뿐만 아니라 나머지 부분에 영향을주지 않고 변수를 로컬로 마스킹 할 수 있습니다. 프로그램.
답변
Closures가 엉덩이를 걷어차는 이유에 대한 실제 예는 다음과 같습니다. 이것은 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
당신이 호출 할 때 delay
와 5000
MS, 최초의 조각 실행 및 저장 그것의 폐쇄에 인수 전달. 그런 다음 5 초 후에 setTimeout
콜백이 발생할 때 클로저는 여전히 해당 변수를 유지하므로 원래 매개 변수를 사용하여 원래 함수를 호출 할 수 있습니다.
이것은 일종의 카레 또는 기능 장식입니다.
클로저가 없으면 함수 외부에서 변수의 상태를 어떻게 유지해야하므로 함수 외부에 코드가 논리적으로 속합니다. 클로저를 사용하면 코드의 품질과 가독성이 크게 향상 될 수 있습니다.