Closure 란 함수(inner function)가 본인이 속한 렉시컬 환경을 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때도 그 스코프에 접근할 수 있게 하는 기능을 말한다. (scope chain)
즉, 함수 밖에서 선언된 변수를 함수 내부에서 사용할 때 클로저가 생겨난다.
프로토타입 기반의 언어인 자바스크립트는 클로저를 통해서 클래스 기반 언어처럼 캡슐화, 모듈화 작업을 수행할 수 있다.
❗
렉시컬 스코핑(Lexical scoping)이란, 함수를 호출할 때가 아닌 함수를 어디에선언하였는지에 따라 결정된다. 함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위에서)에 있는 변수를 계속 참조하게 된다.예제 (1) // 전역 변수 참조 let name = '전역'; function log() { // log함수가 "선언"되는 순간 변수는 스코프를 타고 참조 시작 console.log(name); // '전역' 참조 // 출려값: 지역 } function wrapper() { name = '지역'; // '지역' 으로 값이 바뀜 log(); // log함수가 "호출"되는 순간과는 관련X } wrapper(); 예제 (2) let name = '전역'; function log() { console.log(name); // '전역' 참조 (log함수가 선언되는 시점에 가리키는 변수 참조) // 출려값: 전역 } function wrapper() { let name = '지역'; log(); } wrapper(); 예제 (3) // 외부 변수(name)를 내부(inner)에서 사용 function outer(){ let name = 'ladygaga'; // name은 outer에 의해 생성된 지역 변수 function inner(){ console.log(name); // 부모 함수에서 선언된 변수(name) 사용 } return inner; } let innerFunc = outer(); // inner 함수를 반환(return)받게 된다 innerFunc(); // innerFunc()를 호출하면 name에 대한 참조는 메모리에서 소멸하는데, 클로저가 외부변수 name을 통으로 기억함 // 이러한 이유로 innerFunc는 여전히 자신의 외부에서 생성된 name이라는 변수를 참조할 수 있게 됨. ❗ 즉, 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수 ❗ 외부함수(outer)가 이미 반환되었어도 외부함수 내의 변수(name)는 이를 필요로 하는 내부함수(inner)가 하나 이상 존재하는 경우 계속 유지된다. 이때 내부함수가 외부함수에 있는 변수의 복사본이 아니라 "실제 변수"에 접근한다는 것에 주의!
// 은닉 사례 function Counter() { // 카운트를 유지하기 위한 자유 변수 var counter = 0; // 클로저 (counter를 기억) this.increase = function () { return ++counter; }; // 클로저 (counter를 기억) this.decrease = function () { return --counter; }; } const counter = new Counter(); console.log(counter.increase()); // 1 console.log(counter.decrease()); // 0 // 이 메소드(increase, decrease)들은 모두 자신이 생성됐을 때의 렉시컬 환경인 // 생성자 함수 Counter의 스코프에 속한 변수 counter를 기억하는 클로저이며 렉시컬 환경을 공유한다. ----------------------------------------- <script> var incleaseBtn = document.getElementById('inclease'); var count = document.getElementById('count'); var increase = (function () { // 즉시실행함수 // 카운트 상태를 유지하기 위한 자유 변수 (전역에 선언하는 것 비추) var counter = 0; // 클로저를 반환 return function () { return ++counter; }; }()); incleaseBtn.onclick = function () { count.innerHTML = increase(); }; </script> // 스크립트가 실행되면 '즉시실행함수'가 호출되고, 변수 increase에는 "함수"(= function(){return ++counter} )가 할당된다. // "이 함수"는 자신이 생성됐을 때의 렉시컬환경을 기억하는 클로저이다. // 이때 클로저인 "이 함수"는 자신이 선언됐을 때의 렉시컬 환경인 즉시실행함수의 스코프에 속한 지역변수 counter를 기억한다
(클로저는 어떤 데이터와 그 데이터를 조작하는 함수를 연관시켜주기 때문에 유용하다. 이것은 객체가 어떤 데이터와(그 객체의 속성) 하나 혹은 그 이상의 메소드들을 연관시킨다는 점에서 객체지향 프로그래밍과 분명히 같은 맥락에 있다.)
모든 클로저에는 세가지 스코프(범위)가 있다
💡 스코프체인이란?
스코프(scope, 변수가 유효하는 범위)간의 연결.예시 (1) function outer(){ var a = 1; function inner(){ var b = 2; console.log(a+b); } inner(); } outer() // 3 // a 는 inner 의 스코프에서는 없지만 outer 의 스코프에서 a 의 값을 가져와서 더한다. // 내부함수에서 찾을 수 없는 변수이면 상단의 외부함수로간다.
예시 (2) var c = 4; function outer() { var a = 1; var b = 3; function inner() { var a = 2; console.log(a); // 2 console.log(b); // 3 console.log(c); // 4 } inner(); } outer(); // console.log(a)에서 a의 값을 찾으려고 할 때 들여다보는 곳이 스코프이고 스코프는 함수 단위이다. // 이 때, inner 함수 스코프, outer 함수 스코프, global 스코프가 존재하는데 // inner 스코프 안에서 먼저 a가 존재하는지 찾는다. // 마찬가지로 console.log(b)에서 b의 값을 inner 스코프 안에서 먼저 찾는다. // inner 스코프에는 b가 없기 때문에 그 다음 outer 스코프에 b가 있는지 찾아본다. // inner 함수가 생성된 곳이 outer 함수 범위 안에 있기 때문이다. // 마찬가지로 console.log(c)에서 c의 값을 inner 스코프 안에서 먼저 찾는다. // inner 스코프에는 c가 없기 때문에 그 다음 outer 스코프에 c가 있는지 찾아본다. // outer 스코프에도 c가 없기 때문에 global 스코프에서 찾는다. // inner 함수가 생성된 곳이 outer 함수 범위 안에 있고, outer 함수가 생성된 곳이 global 범위 안에 있기 때문이다. // 이런식으로 스코프끼리 연결된 것이 바로 스코프 체인이다.
💡 스코프 관점에서 클로저란?
함수와 그 안의 내부함수를선언할시 결정되어진 함수의 스코프가 정해지면 바로 클로저가 되는 것이다.var c = 4; function outer() { var a = 1; var b = 3; return function inner() { var a = 2; console.log(b); // 3 } } var someFunc = outer(); someFunc(); // var someFunc = outer(); 에서 outer 함수를 호출해서 실행이 완료되면 outer 함수 내 지역 변수들은 사라질 것 같지만 사라지지 않는다. // 자바스크립트는 함수를 리턴하고 리턴하는 함수가 클로저를 형성하기 때문이다. // 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. // 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다. // someFunc는 outer이 실행될 때 생성된 inner 함수의 인스턴스에 대한 참조다. // inner의 인스턴스는 변수 b가 있는 어휘적 환경에 대한 참조를 유지한다. // 이런 이유로 someFunc가 호출될 때 b는 사용할 수 있는 상태로 남게된다. // 즉, 원래는 함수 내부에 선언한 변수는 함수가 끝나면 사라지지만, // 클로저가 스코프 체인을 계속 들고 있으므로 outer 함수 내부의 변수를 참조할 수 있게 된다.