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 함수 내부의 변수를 참조할 수 있게 된다.