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


1. 실행 컨텍스트란?

→ 자바스크립트 코드 블록이 실행되는 환경

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

  • 활성 객체 생성 → arguments 객체 생성 → 스코프 정보 생성 → 변수 생성 → this 바인딩 → 코드실행
    1) 활성객체
    변수, 인자, 객체등 여러 정보를 저장하고 있는 객체로 엔진 내부에서만 접근 가능
    2) arguments 객체
    활성객체의 arguments 프로퍼티가 참조하는 객체로, 활성객체의 상태 확인 가능
    3) 스코프 정보 생성
    컨텍스트의 유효범위를 나타내는 스코프 정보로, 연결 리스트와 유사하며 이 리스트의 명칭은 스코프 체인
    4) 변수 생성
    실행 컨텍스트 내 사용되는 지역 변수의 생성이 이루어짐, 단 메모리에 올라가거나 초기화는 진행되지 않음(활성객체 생성이 다 이루어진 후 시작됨)
    5) this 바인딩
    this 키워드를 사용하는 값이 할당됨
    6) 코드 실행
    실행 컨텍스트 생성, 활성객체 생성 후 코드 블록이 진행되며 변수의 초기화 및 연산, 메모리 할당, 함수 등의 실행

3. 스코프 체인

  • 자바스크립트는 오직 함수만이 유효범위의 한 단위
  • 이 유효범위를 나타내는 스코프는 [[scope]] 프로퍼티로 연결 리스트의 형태로써 관리된다.
    1) 전역 실행 컨텍스트의 스코프 체인
    ▶ 실행 - 전역 컨텍스트 실행 후 변수 객체 생성 즉 자신이 최상위에 위치하는 변수 객체이며, 스코프 체인은 자기 자신 뿐
    var var1 = 1;
    var var2 = 2;
    console.log(var1);//1
    console.log(var2);//2

    2) 함수 호출 시 생성되는 실행 컨텍스트의 스코프 체인

    ▶ 실행 - 전역 컨텍스트 생성 후 func() 함수 객체 생성, func() 객체의 스코프 체인은 현재 컨텍스트의 스코프 체인을 참조해 실행중인 함수의 [[scope]] 프로퍼티를 복사하고 새롭게 생성된 변수 객체를 스코프체인 맨 앞에 추가한다.
    ∴ 스코프 체인 = 현재 실행 컨텍스트의 변수 객체 + 상위 컨텍스트의 스코프 체인
    var var1 = 1;
    var var2 = 2;
    function func(){
     	var var1 = 1;
    	var var2 = 2;
       console.log(var1);//10
    	console.log(var2);//20
    }
    console.log(var1);//1
    console.log(var1);//2
    func();
    console.log(var1);//1
    console.log(var2);//2
    

4. 클로저

function outerFunc(){
	var x = 1;
    var innerFunc = function() { 
    	console.log(x);
    }
    return innerFunc;
}

var inner = outerFunc();
inner();//1
  • outerFunc의 실행 이후 innerFunc가 실행되는데 outerFunc의 실행 컨텍스트는 사라지지만, innerFunc()의 스코프 체인은 outerFunc 변수 객체를 참조할 수 있다.
    1) 클로저의 개념
    함수가 종료되어 외부 함수의 컨텍스트가 반환되더라도 변수 객체는 반환되는 내부 함수의 스코프 체인에 그대로 남아야만 접근할 수 있다. 즉 이미 생명주기가 끝난 외부 함수의 변수를 참조하는 함수를 클로저라고 한다. 위 코드에서는 생명주기가 끝난 outerFunc의 x를 참조하는 innerFunc가 클로저다.
    ● 자유변수
    클로저로 참조되는 외부변수 즉 x와 같은 변수를 자유변수(Free variable)라고 한다.
    cf) 클로저의 의미
    클로저(closer)라는 이름은 함수가 자유 변수에 대해 닫혀있다(closed bound)라는 뜻으로,
    '자유 변수에 엮여있는 함수'로 이해할 수 있다.
    2) 클로저의 활용
    | 클로저는 성능과 자원적인 측면에서 무차별적으로 사용 시 문제를 야기하므로 활용이 중요하다.
    활용1. 특정 함수에 사용자가 정의한 객체의 메서드 연결
    function HelloFunc(func){
    	this.greeting = 'hello';
    }
    HelloFunc.prototype.call = function(func){
    	func ? func(this.greeting) : this.func(this.greeting);
    }
    var userFunc = function(greeting){
    	console.log(greeting);
    }
    var objHello = new HelloFunc();
    objHello.func = userFunc;
    objHello.call();//hello

    
    function saySomething(obj, methodName, name){
    	return (function(greeting){
       	return obj[methodName](greeting, name);
       });
    }
    
    function newObj(obj, name){
    	obj.func = saySomething(this, "who", name);
       return obj;
    }
    
    newObj.prototype.who = function(greeting, name){
    	console.log(greeting + ' ' + (name || "everyone!") );
    }
    
    var obj1 = new newObj(objHello, 'deepsea');//hello deepsea
    
    ▶ 여기서 클로저는 saySomething()에서 반환되는 function(greeting){}, 자유변수는 obj, methodName, name이다.
    활용2. 함수의 캡슐화
    var buffArr = [
    	'I am',
       '',
       '. I live in ',
       '',
       '. I\'am ',
       '',
       '. years old.',
    ];
    
    function getCompletedStr(name, city, age){
    	buffArr[1] = name;	
    	buffArr[3] = city;
    	buffArr[5] = age;
       
       return buffArr.join('');
    }
    
    var str = getCompletedStr('deepsea', 'seoul', 29);
    console.log(str);
    ▶ 이 코드는 buffArr이 전역 배열로 외부에 노출이 되어있어 값의 변경이나 같은 이름의 변수 생성 가능성이 있다. 이를 해결하기 위해 추가적인 스코프를 지정해 사용하면 해결이 가능하다.
     var getCompletedStr = (function(){ 
     	var buffArr = [
    	'I am',
       '',
       '. I live in ',
       '',
       '. I\'am ',
       '',
       '. years old.',
    ];
    
    return (function(name, city, age){
    	buffArr[1] = name;	
    	buffArr[3] = city;
    	buffArr[5] = age;
       
       return buffArr.join('');
    });
    })();
    ▶ getCompletedStr에 익명의 함수를 즉시 실행시켜 반환되는 함수를 할당해서 해결한다. 이 함수가 클로저가 되며 자유변수는 buffArr이다.
    활용3. setTimeout()에 지정되는 함수의 사용자정의
    function callLater(obj, a, b){
    	return (function(){
       	obj["sum"] = a + b;
           	console.log(obj["sum"]);
       })
    }
    
    var sumObj = {
    	sum : 0
    }
    
    var func = callLater(sumObj, 1, 2);
    setTimeout(func, 500);
    ▶ 사용자가 정의한 함수 callLater를 setTimeout함수로 호출하려면 변수 func에 함수를 반환받아 setTimeout 함수의 첫번째 인자로 넣는다. 반환받는 함수가 클로저로, 사용자는 원하는 인자에 접근할 수 있다.
    3) 클로저 활용 시 주의사항
    1. 클로저의 프로퍼티값은 그 값이 여러번 호출로 항상 변할 수 있음에 유의한다.
    function outerFunc(argNum){
    	var num = argNum;
       return function(x) {
       	num += x;
           console.log('num: '+ num);
       }
    }
    
    var exam = outerFunc(40);
    exam(5);
    exam(-10);
    ▶ 자유변수 num이 exam호출 시 마다 변한다.
    2. 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있는 경우도 있다.
    function func(){
    	var x = 1;
       return {
       	func1: function(){ console.log(++x); },
           func2: function(){ console.log(-x); }
    	};
    };
    
    var exam = func();
    exam.func1();
    exam.func2();
    ▶ 반환되는 함수 func1, func2가 자유변수 x를 참조하며 x값이 각각의 함수 호출 시 변화한다.
    3. 루프안에서 클로저를 활용 시 주의한다.
    function countSeconds(howMany){
    	for(var i=1; i<= howMany; i++){
       	setTimeout(function () {
           	console.log(i);
           }, i * 1000);
       }
    };
    countSeconds(3);
    ▶ 위 코드는 1, 2, 3이 1초 간격으로 출력되는 것을 기대하고 만든 코드이지만 countSeconds() 함수의 실행 이후 setTimeout() 함수가 실행되므로 4가 1초 간격으로 3변 출력된다. 이를 해결하기 위해 루프 i의 복사본을 함수에 넘겨주고, 즉시 실행 함수를 사용한다.
    function countSeconds(howMany){
    	for(var i=1; i<= howMany; i++){
       	(function(currentI) {
             setTimeout(function () {
                 console.log(i);
             }, i * 1000);
           }(i));
       }
    };
    countSeconds(3);

좋은 웹페이지 즐겨찾기