본문 바로가기
Front_End/Inside-JavaScript

5장. 실행 컨텍스트와 클로저

by Havi 2016. 11. 28.

1. 실행 컨텍스트 개념

  • 콜 스택 - 함수를 호출할 때 해당 함수의 호출 정보(지역 변수, 인자값 등)가 차곡차곡 쌓여있는 스택을 의미
  • 즉 실행 컨텍스트는 "실행 가능한 자바스크립트 코드 블록이 실행되는 환경"
  • 실행 컨텍스트가 형성되는 경우
    • eval()함수로 실행되는 코드
    • 함수 안의 코드를 실행할 경우
    • 전역 코드
  • "현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행 코드가 실행되면, 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다."
  • 제일 위에 위치하는 실행 컨텍스트가 현재 실행되고 있는 컨텍스트

2. 실행 컨텍스트 생성 과정

function execute(param1, param2) {
     var a= 1, b = 2;
     function func() {
          return a+b;
     }
     return param1 + param2 + func();
}
execute(3, 4);

1) 활성 객체 생성 - 실행에 필요한 여러 가지 정보를 담을 객체를 생성(활성 객체)

2) arguments 객체 생성 - execute() 함수의 param1, param2가 들어왔을 경우의 활성 객체의 상태를 표현 

3) 스코프 정보 생성 - 현재 컨텍스트의 유효 범위를 나타내는 스코프 정보를 생성한다. 이 스코프 정보는 연결 리스트와 유사한 형태로 만들어진다. 이 리스트로 상위 실행 컨텍스트의 변수도 접근 가능하고 이 리스트에서 찾지 못한 변수는 정의되지 않은 것으로 판단하고 에러를 검출한다. 이 리스트를 스코프 체인이라 하고 [[scope]]프로퍼티로 참조된다. 

4) 변수 생성 - 변수 a, b나 함수 func는 단지 메모리에 생성하고, 초기화는 표현식이 실행되기 전까지는 이루어지지 않는다. 즉, 생성 바로 후에는 undefined가 할당된다. 

5) this 바인딩 - 마지막 단계는 this 키워드를 사용하는 값이 할당된다. 요가소 this가 참조하는 객체가 없으면 전역 객체를 참조한다. 

6) 코드 실행 - 전역 코드가 실행될 때, 전역 실행 컨텍스트가 실행되며 이때 변수 객체 또한 전역 객체로 선언되어 실행된다.

3. 스코프 체인

cf) 활성 객체와 변수 객체는 같은 객체이므로 혼동 X

  • { int a = 10 }처럼 범위 안에 선언된 변수는 블록 밖에서는 사용이 불가능하다. 오직 함수만이 유효범위의 한 단위가 되며, 이 유효 범위를 나타내는 스코프가 [[scope]] 프로퍼티로 각 함수 객체 내에서 연결 리스트 형식으로 관리되며 스코프 체인이라 한다.
  • 각각의 함수는 [[scope]] 프로퍼티로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조한다.
  • 전역 실행 컨텍스트의 스코프 체인
    • 전역 실행 컨텍스트는 자신이 최상위에 위치하는 변수 객체가 되서 스코프 체인은 자기 자신만을 갖는다.
  • 함수 호출시 생성되는 실행 컨텍스트의 스코프 체인
var var1 = 1;
var var2 = 2;
function func() {
     var var1 = 10;
     var var2 = 20;
     console.log(var1); //출력값 10
     console.log(var2); //출력값 20
}
func();
console.log(var1); //출력값 1
console.log(var2); //출력값 2
  • 각 함수 객체는 [[scope]]프로퍼티로 현재 컨텍스트의 스코프 체인을 참조한다.
  • 즉, 현재 실행되는 함수 객체의 [[scope]] 프로퍼티를 복사하고, 새롭게 생성된 변수 객체를 해당 체인의 제일 앞에 추가한다.
  • var1,2는 func 변수 객체를 먼저 탐색하고, 없으면 전역 객체를 탐색한다.
1func 변수 객체
0전역 객체
  • 식별자 인식 - 스코프 체인의 첫번째 변수 객체부터 시작하여 식별자와 대응되는 이름을 가진 프로퍼티가 있는지를 확인한다.

4. 클로저

  • 외부함수의 변수에 접근할 수 있는 내부 함수
  • 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수
function outerFunc() {
     var x = 10;
     var innerFunc = function() { console.log(x); }
     return innerFunc;
}
var inner = outerFunc();
inner(); //10
  • outerFunc에서 선언된 x를 참조하는 innerFunc가 클로저가 된다.
  • 클로저로 참조되는 외부 변수 즉, outerFunc의 x와 같은 변수를 자유 변수라 한다.
  • closure라는 의미는 '자유 변수에 엮여있는 함수'라는 표현이 맞다.
  • 클로저를 사용하면 그렇지 않은 코드보다 메모리의 부담이 많아진다.(적절하고 영리하게 써야한다.)
  • 외부함수가 리턴된 이후에도 여전히 내부함수가 외부함수의 변수에 접근 가능하다.
  • 클로저는 외부 함수의 변수에 대한 참조를 저장(실제 값 저장하지 않는다.)
  • 함수는 파라미터와 외부 함수의 변수뿐만 아니라 해당 스코프 내에 포함된 어떤 변수라도 참조할 수 있다.
  • 함수 캡슐화
var getCompletedStr = (function(){
    var buffAr = [
    'I am ',
    '',
    '. I live in ',
    '',
    '. I\'am ',
    '',
    ' years old.',
    ];


    return (function(name, city, age) { //클로저, 자유변수 buffAr을 스코프 체인에서 참조할 수 있다.
        buffAr[1] = name;
        buffAr[3] = city;
        buffAr[5] = age;


        return buffAr.join('');
    });
})();
var str = getCompletedStr('zzoon', 'seoul', 16);
console.log(str);
  • 변수를 전역변수로 선언하여 사용하는 것보다 단점이 적고 버그 예방
  • 클로저 활용시 주의사항
    • 클로저의 프로퍼티값이 쓰기 가능하므로 그 값이 여러 번 호출로 항상 변할 수 있음에 유의
    • 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 잇는 경우 자유 변수 값이 변화하는데 유의
    • 루프 안에서 클로저를 활용할 때
function countSecounds(howMany) {
     for(var i = 1; i <= howMany; i++) {
          setTimeout(function() {
               console.log(i);
          }, i * 1000);
     }
};
countSeconds(3);
  • 자유 변수 i는 이미 종료된 함수 이고 i는 4가 된 상태이다.
  • 원하는 결과를 얻기 위한 코드
function countSecounds(howMany) {
     for(var i = 1; i <= howMany; i++) {
          (function (currentI) {
          setTimeout(function() {
               console.log(currentI);
          }, currentI * 1000);
          }(i));
     }
};
countSecounds(3);
  • 즉시 실행 함수를 실행시켜 루프 i값을 currentI에 복사해서 사용


댓글0