실행 컨텍스트, 클로저 / JavaScript

aljongjong·2022년 4월 20일
0

인사이드JS

목록 보기
3/5

실행 컨텍스트(Execution context)

실행 컨텍스트의 개념

  • C 언어에서 함수가 호출될 때마다 해당 함수의 호출 정보가 기존 함수의 호출 정보 위에 스택형태로 하나씩 쌓인다. -> 콜 스택(Call Stack) / 따라서 개발자는 콜 스택의 호출 정보 등으로 코드의 실행 과정을 추적하여 디버깅과 같은 작업을 수행한다.
  • 실행 컨텍스트는 콜 스택에 들어가는 실행 정보 하나와 비슷하다.
  • 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념 -> 실행가능한 자바스크립트 코드 블록(함수)이 실행되는 환경
  • 현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행 코드가 실행되면, 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다.
  • 전역 코드, eval() 함수로 실행되는 코드, 함수 안의 코드를 실행할 경우 실행 컨텍스트가 형성된다.
<script>
console.log("This is global context");

function ExContext1() {
    console.log("This is ExContext1");
};

function ExContext2() {
    ExContext1();
    console.log("This is ExContext2");
};

ExContext2();
</script>

-- 실행 컨텍스트 스택이 전역 컨텍스트 -> ExContext2 -> ExContext1 순으로 쌓이게 된다.

실행 컨텍스트 생성 과정

활성 객체 생성

  • 실행 컨텍스트가 생성되면 자바스크립트 엔진은 해당 컨텍스트에서 실행에 필요한 여러 가지 정보를 담을 객체를 생성하는데, 이를 활성 객체라고 한다.
  • 활성 객체에 사용하게 될 매개변수나 사용자가 정의한 변수 및 객체를 저장하고, 새로 만들어진 컨텍스트로 접근 가능하게 된다.(사용자의 접근 X, 엔진 내부에서 접근 O)

arguments 객체 생성

  • 다음 단계에서 arguments 객체를 생성한다. 앞서 만들어진 활성 객체는 arguments 프로퍼티로 arguments 객체를 참조한다.

스코프 정보 생성

  • 다음 단계에서 현재 컨텍스트의 유효 범위를 나타내는 스코프 정보를 생성한다.
  • 스코프 정보는 현재 실행 중인 실행 컨텍스트 안에서 연결 리스트와 유사한 형식으로 만들어진다. 현재 컨텍스트에서 특정 변수에 접근해야 할 경우, 이 리스트를 활용한다. 이 리스트로 현재 컨텍스트의 변수뿐 아니라, 상위 실행 컨텍스트의 변수도 접근이 가능하다.
  • 이 리스트를 스코프 체인이라 하고 [[scope]] 프로퍼티로 참조된다. 현재 생성된 활성 객체가 스코프 체인의 제일 앞에 추가된다.

변수 생성

  • 다음 단계에서 현재 실행 컨텍스트 내부에서 사용되는 지역 변수의 생성이 이루어진다. 생성되는 변수를 저장하는 변수 객체는 활성 객체가 변수 객체로 사용되는 것으로 둗 객체는 같은 객체이다.
  • 변수 객체 안에서 호출된 함수 인자는 각각의 프로퍼티가 만들어지고 그 값이 할당된다. 만약 값이 넘겨지지 않았다면 undefined가 할당된다. 그리고 함수 안에 정의된 변수, 함수가 생성된다. 이 과정에서는 변수나 내부 함수를 단지 메모리에 생성하고, 초기화는 각 변수나 함수에 해당하는 표현식이 실행되기 전까지는 이루어지지 않는다는 점이다. 따라서 변수에 먼저 undefined가 할당된다.

this 바인딩

  • 마지막 단계에서 this 키워드를 사용하는 값이 할당된다. 여기서 this가 참조하는 객체가 없으면 전역 객체를 참조한다.

코드 실행


스코프 체인(Scope chain)

  • 자바스크립트에서는 오직 함수만이 유효 범위의 한 단위가 된다. 이 유효 범위를 나타내는 스코프가 [[scope]] 프로퍼티로 각 함수 객체 내에서 연결 리스트 형식으로 관리되는데, 이를 스코프 체인이라고 한다.
  • 스코프 체인은 실행 컨텍스트의 변수 객체(활성 객체)가 구성 요소인 리스트와 같다. -> 스코프 체인[(3, ...), (2, 변수 객체 2), (1, 변수 객체 1), (0, 변수 객체 0)]
  • 각각의 함수는 [[scope]] 프로퍼티로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조한다. 함수가 실행되는 순간 실행 컨텍스트가 만들어지고, 이 실행 컨텍스트는 실행된 함수의 [[scope]] 프로퍼티를 기반으로 새로운 스코프 체인을 만든다.

전역 실행 컨텍스트의 스코프 체인

<script>
var var1 = 1;
var var2 = 2;
console.log(var1); // (출력값) 1
console.log(var2); // (출력값) 2
</script>

1) 위 코드는 전역 코드이다. 함수가 선언되지 않아 함수 호출이 없고, 실행 가능한 코드들만 나열되어 있다.
2) 위 코드가 실행되면, 먼저 전역 실행 컨텍스트가 생성되고, 변수 객체가 만들어진다.
3) 현재 전역 실행 컨텍스트 단 하나만 실행되고 있어 참조할 상위 컨텍스트가 없다. 자신이 최상위에 위치하는 변수 객체이다.
4) 따라서, 이 변수 객체의 소코프 체인은 자기 자신만을 가진다.
5) 변수 객체의 [[scope]]는 변수 객체 자신을 가리킨다.

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

<script>
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
</script>

1) 코드 실행시 전역 실행 컨텍스트가 생성되고, func() 함수 객체가 만들어진다.
2) 함수 객체가 생성될 때, 그 함수 객체의 [[scope]]는 현재 실행되는 컨텍스트의 변수 객체에 있는 [[scope]]를 그대로 가진다. 따라서, func 함수 객체의 [[scope]]는 전역 변수 객체가 된다.
3) 스코프 체인[(1, func 변수 객체), (0, 전역 객체)]
  • 각 함수 객체는 [[scope]] 프로퍼티로 현재 컨텍스트의 스코프 체인을 참조한다.
  • 한 함수가 실행되면 새로운 실행 컨텍스트가 만들어지는데, 이 새로운 실행 컨텍스트는 자신이 사용할 '스코프 체인'을 현재 실행되는 함수 객체의 [[scope]] 프로퍼티를 복사하고, 새롭게 생성된 변수 객체를 해당 체인의 제일 앞에 추가하는 방법으로 만든다.
  • 스코프 체인 = 현재 실행 컨텍스트의 변수 객체 + 상위 컨텍스트의 스코프 체인
  • 스코프 체인으로 식별자 인식이 이루어진다. 스코프 체인의 첫 번째 변수 객체부터 시작하며 식별자와 대응되는 이름을 가진 프로퍼티가 있는지를 확인한다. 변수 객체의 공식 인자, 내부 함수, 지역 변수를 확인하고 다음 객체로 이동하여 찾는다.

클로저(Closure)

클로저의 개념

  • 이미 생명주기가 끝난 외부 함수의 변수를 참조하는 함수를 클로저라고 한다.
<script>
function outerFunc() {
    var x = 10;
    var innerFunc = function () { console.log(x); }
    return innerFunc;
}

var inner = outerFunc();
inner();    // (출력값) 10
</script>
1) outerFunc에서 선언된 x를 참조하는 innerFunc가 클로저
2) 클로저로 참조되는 외부 변수 즉, outerFunc의 x와 같은 변수를 '자유 변수'라고 한다. 클로저는 자유 변수에 엮여 있는 함수이다.
<script>
function  outerFunc(arg1, arg2) {
    var local = 8;
    function innerFunc(innerArg) {
        console.log((arg1 + arg2) / (innerArg + local))
    }
    return innerFunc;
}
var exam1 = outerFunc(2, 4);
exam1(2);
</script>

클로저 활용

<script>
var getCompletedStr = (function () {
    var buffAr = [
        'I am ',
        '',
        '. I live in ',
        '',
        '. I am ',
        '',
        ' years old.',
    ];

    return (function (name, city, age) {
        buffAr[1] = name;
        buffAr[3] = city;
        buffAr[5] = age;

        return buffAr.join('');
    });
})();

var str = getCompletedStr('zzoon','seoul', 16);
console.log(str);
</script>
--> I am zzoon. I live in seoul. I am 16 years old.
<script>
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, 2000);
</script>
--> 2초 뒤, 3
<script>
function outerFunc(argNum) {
    var num = argNum;
    return function (x) {
        num += x;
        console.log('num: ' + num);
    }
}
var exam = outerFunc(40);
exam(5);
exam(-10);
</script>
--> num : 45, num : 35
- 클로저의 프로퍼티값이 쓰기 가능하므로 그 값이 여러 번 호출로 항상 변경할 수 있음
<script>
function func() {
    var x = 1;
    return {
      func1 : function () { console.log(++x); },
      func2 : function () { console.log(-x); }
    };
};

var exam = func();
exam.func1();
exam.func2();
</script>
--> 2, -2
- 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있는 경우
<script>
function countSeconds(howMany) {
    for (var i = 1; i <= howMany; i++) {
        (function (currentI) {
            setTimeout(function () {
                console.log(currentI);
            }, currentI * 1000);
        })(i);
    }
};
countSeconds(3);
</script>
--> 1, 2, 3

0개의 댓글