본문 바로가기

자바스크립트의 정석 🟡

[자바스크립트] closure 정리 ✨

참고영상: 인프런 코어자바스크립트

 

⭐️ closure

- "닫힘/폐쇄/완결성" 이라는 사전적 의미

- 하지만 자바스크립트에서는 "결합, 조화, 조합"의 의미

- 둘러싸인 lexical environment의 참조

- 한마디로 내부함수와 LexicalEnvironment의 조합 

- 어떤 실행컨텍스트a에서 함수 b를 선언 -> A의 lexical environment와 내부함수 B의 조합의 "특별한 현상" 

 

 

 

 

📚 실행 컨텍스트 그림으로 알아보기

📌 A의 outerEnvironmentReference는 어차피 B도 똑같이 접근이 되니까 B랑은 직접 관련이되지 않음

📌 B의 environmentRecord는 B 내부에서 선언한 식별자만 관련이 있으니까 A랑은 관련이 없음

📌 하지만, B의 outerEnvironmentReference로 A의 environmentRecord에 접근 가능하니,  이 둘 사이의 조합을 다루는 것

✅ 즉, 컨텍스트 A에서 선언한 변수를 내부함수 B에서 참조할 경우에 발생하는 특별한 현상

 

 


 

 

💻 코드 1-1)

var outer = function () {
  var a=1;
  var inner = function() {
    console.log(++a);
  };
  inner();
}
outer();

📌 outer함수 안에 a 변수가 선언되어 있다. outer 컨텍스트에서 선언한 변수(a)를 내부함수 innter에서 참조

 

 

 

 

📚 코드 1-1 실행 컨텍스트 그림으로 알아보기

📌 처음, 전역 컨텍스트가 열리고 아우터 함수를 실행하여 아우터 실행 컨텍스트가 쌓임

📌 outer Context에는 Lexical Environment 안에 environmentRecord가 존재하는데 이 레코드 안에 a는 1이 들어가 있고 inner라는 함수가 들어가있음. 또, outerEnvironmentReference에는 outer 함수가 들어가있음

📌 inner 함수를 실행했을 때, 또 그 위에 inner context가 쌓임. inner Context 안에 Environment Record에는 아무것도 없음(선언한게 없으니까). 그리고 outerEnvironmentReference에는 outer context environment를 참조

📌 이 배경으로 inner 함수를 실행시켰을 때, inner context가 사라지고 그 다음 아우터 함수를 실행시키면 outer context가 사라지고 끝으로 전역 컨텍스트가 사라지면서 끝남

 

 

 

 

💻 코드 1-2 )

var outer = function() {
  var a=1;
  var inner = function () {
    return a++;
  };
  return inner;
}
var outer2 = outer();
console.log(outer2()); //2
console.log(outer2()); //3

 

 

 

 

 

📚 코드 1-2 실행 컨텍스트 그림으로 알아보기

📌 처음 전역 컨텍스트에 outer 함수가 정의되어 있고, outer 함수가 정의되었지만, outer 함수가 아직 호출되기 전에는 outer2 함수는undefined로 존재 ( outer 함수가 호출되어서 outer함수의 정의를 내린 후 outer2 함수의 정의내릴 수 있으니까)

📌 그리고 outer 함수 안에 inner 함수가 존재 (위치는 environmentRecord) 

 

 

 

 

📌 그 후, outer 함수가 호출이 되었다면, outer2에서는 inner 함수가 담기게 됨

📌 outer 함수가 실행이 되었으니까, 그림처럼 밑줄 표시를 해놓았지만 a 라는 변수는 아직 살아있는 상태라 밑줄을 치지 않음

📌 즉, outer 함수는 종료되어  실행 컨텍스트 또한 종료되었지만 이 함수 내부에 있는 변수 a는 죽지 않음. 왜냐하면, 참조 카운트가 0이 되지 않았으니까, 언젠간 저 변수를 쓸 수 있으니까.

📌 그래서, 다시 두번째로 console.log(outer2)를 불러왔을 때, inner 함수 내에서 outer 컨텍스트에 있는 변수 a를 살려놨기 때문에, 두 번째로 outer2 함수를 호출했을 때 2라는 결과값을 출력할 수 있게 됨

📌 만약 이 closure 함수를 끊어내고 싶다면, outer2 함수에서 inner 함수 대신 다른 함수를 대입하면 될 것임. 그러면 연결고리가 끊겨 연쇄적으로 GC 대상이 될 것임

 

 

 

📚 정리

✅ 컨텍스트 A에서 선언한 변수 a를 참조하는 내부함수 B를 A의 외부로 전달할 경우, A가 종료되더라도 a가 사라지지 않는 현상, closure

 즉, 지역변수가 함수 종료 후에도 사라지지 않게 할 수 있다.

 

 

 


 

 

💻 다른 예시를 풀어보자 )

function user(_name){
    var _logged = true;
    return{
        get name(){return _name},
        set name(v){_name=v},
        login(){_logged=true},
        logout(){_logged=false},
        get status(){
            return _logged
            ? 'login'
            :'logout';
        },
    }
}
var roy = user('재남');

//1
console.log(roy.name); //재남

//2
roy.name="제이 ";
console.log(roy.name); //제이

//3
roy._name='로이';
console.log(roy.name); //제이 -> 반영되지 않음

//4
console.log(roy.status); //login

//5
roy.logout(); 
console.log(roy.status); //logout

//6
roy.status=true;
console.log(roy.status); //logout

 

1️⃣ console.log(roy.name);를 반환하려고 하면 roy 객체에는 name 프로퍼티가 없는 대신에 name에 대한 getter는 있음. getter가 호출이 됨. _name은 user함수에서 선언한 변수. 이 _name은 함수가 종료됨과 동시에 함께 사라지는 변수여야 하지만, 종료가 되어도 사라지지 않음. 하지만 return 해주는 과정에서 해당 변수가 계속 사용되어야 하니까 참조 카운트가 0이 아닌 상태. -> 클로저

 

2️⃣ roy.name에 setter로 name이라는 프로퍼티가 재남에서 제이로 변경.

 

3️⃣ roy._name='로이'; roy객체에 _name 프로퍼티가 없음. 이 경우, user 함수에 _name 변수에는 어떠한 영향을 줄 수 없음. 그래서 name getter 호출에 대한 결과는 여전히 "제이" 

 

4️⃣ console.log(roy.status);에서는 status 프로퍼티에  _logged를 리턴하고 이때 _logged는 true이니까 'login'이 출력된다. 즉, _logged 변수 역시 클로저에 의해 사라지지 않고, 남아있음.

 

5️⃣ logout이라는 메소드를 호출하면, 이제는 사라지지 않은 변수(_logged)가 false가 되고, roy.status를 호출하면 logout이 출력됨.

다시 말해, console.log(roy.staus); 에는 logout() 메소드가 실행되고 _logged가 false가 되며 status 프로퍼티에 false가 반영되어 logout이라는 결과 출력하게 되는 것이다. 

 

6️⃣ 하지만, roy.status=true;

console.log(roy.status); 를 하면  getter는 있지만, setter가 없어서 roy.status에 true를 할당해도 똑같이 logout으로 출력됨.

즉, true가 반영되지 않음. 

 

 

📚 정리

 _name, _logged 함수가 종료되도 사라지지 않고 값을 유지하는 변수

✅ 외부로부터 내부 변수를 보호할 수 있는 기능(캡슐화)

여기서 캡슐화란❓
📌 위에 코드에서 외부에 노출된 status 프로퍼티는 getter로서만 역할을 하며, 실제 _logged 값과는 별개로 문자열(login/logout)만 반환해줌
📌 직접적인 _logged의 변화에 영향을 주는 건 login(), logout() 메소드일 뿐
📌 이것을 캡슐화, 클로저 기능이 외부로부터 내부 변수를 보호할 수 있는 기능을 가짐