02 실행컨텍스트

1.실행컨텍스트란?

  • 자바스크립트는 어떤 실행 컨텍스트가 활성화 되는 시점에 선언된 변수를 hosting 하고 외부환경정보, this 값을 정하는등의 동작을 수행함.
  • 실행컨텍스트란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
  • 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하여 이를 Call Stack에 쌓음.
  • es5에서는 하나의 실행 컨텍스트를 구성할 수 있는 방법에는 전역공간, eval() 함수, 함수 등이 있다.
var a = 1; // --------------------- (1)
function outer() {
    function inner() {
        console.log("inner", a); //undefined
        var a = 3; 
    }
    inner();//------------- (2)
    console.log("outer", a); 
}
outer(); // -----------------(3)
console.log("global", a);

// 실행결과
// inner undefined
// outer 1
// global 1

 

순서 1 2 3 4 5 6 7
        inner      
      outer outer outer    
    전역컨텍스트 전역컨텍스트 전역컨텍스트 전역컨텍스트 전역컨텍스트  

 

  • (2) 에선 전역 컨텍스트가 콜스택에 담긴다. 전역컨텍스트 또한 일반적 실행 컨텍스트이다.
  • (3)에서 outer 함수를 호출하면 자바스크립트 엔진은 outer에 대한 환경정보를 수집후 컨텍스트를 콜스택에 올린다. 
  • inner 내부에서 변수 a값을 출력하고 나면, inner 함수가 종료되면서 콜스텍에서 컨텍스트가 제거된다.
  • 나머지 컨텍스트도 순차적으로 Call Stack에서 제거 될 것이다.
  • 즉 스택 맨위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점이다.
  • 실행 컨텍스트에 담기는 정보는 다음과 같다.
    1. VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한정보 + 외부환경정보, 선언시점의 LexicaEnvironment의 스냅샷으로, 변경사항은 반영되지 않음
    2. LexicalEnvironment: 처음에는VariableEnvironment와 같지만, 변경사항이 실시간으로 반영됨
    3. ThisBinding: this 식별자가 바라봐야할 대상객체.

 

2 VariableEnvironment

  • VariableEnvironment의 담기는 정보는 LexicalEnvironment와 타지만, 최초 실행시의 스냅샷을 유지한다는 점이 다르다.
  • 주로 실행컨텍스트를 생성할때  VariableEnvironment에 정보를 먼저 담은뒤, 그대로 복사해서 LexicalEnvironment를 만들고 LexicalEnvironment정보를 활용함

3 LexicalEnvironment

  • lexical environment대한 번역은 '어휘적 환경', '정적 환경' 이나 둘다 애매하므로 '사전적인' 이라는 느낌으로 외우면 편하다
  • 예를 들면 a,b,c 와 같은 식별자가 내부컨텍스트에 있고, 그 외 정보는 D를 참조하도록 구성돼 있다 이런식으로 컨텍스트를 구성하는 환경정보들을 사전에서 접하는 느낌으로 모아놓은 것이다.

3.1 envicronmentRecord와 호이스팅

  • environmentRecord 에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨.
    • 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자(전역은 x)
    • 선언한 함수가 있을경우 그 함수 자체
    • var로 선언된 변수의 식별자
    • 컨텍스트 처음부터 끝까지 한줄한줄 읽어나가며 순서대로 수집함.
 전역 실행 컨텍스트는 변수 객체를 생성하는 대신, 구동환경이 별도로 제공하는 객체 즉 global object를 활용한다. browser에 window 또는 nodejs의 global이 이에 해당됨.
 이들은 nativce object가 아닌 host object로 분류됨. 

 

  • 변수정보를 수집하는 과정을 모두 마쳤더라도, 코드가 실행되기 전의 상태이다.
  • 그러므로 자바스크립트 엔진은 이미 해당 환경에 속한 변수명들을 모두 알고있다.
  • '자바 스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 코드를 실행한다'
  • 여기서 호이스팅이라는 개념이 등장한다. (실제로 끌어올리지는 않지만 편의상 끌어올린다 표현)

 

3.2 호이스팅 규칙

  • environmentRecord에는 매개변수의 이름, 함수 선언, 변수명 등이 담긴다고 했다.
fuction a (x) {     // 수집대상 1(매개변수)
    console.log(x); // (1)
    var x;          // 수집대상 2(변수선언)
    console.log(x); // (2)
    var x = 2;      // 수집대상 3(변수선언)
    console.log(x); //(3)
}
a(1)
  • 호이스팅이 되지 않을때 (1) (2) (3) 에서 어떤값이 나올지 예상해보자.
  • (1) 에는 1이 호출되고
  • (2) 에는 undefined에가 출력되고
  • (3) 에는 아마도 2가 호출될 것 같다... 과연 그럴까?!

 

- 설명을 위해 우리가 자바스크립트 엔진이라 생각하고 위의 코드를 Hoisting 되게 바꿔보자. 

fuction a () {     
  var x; // 수집대상 1(매개변수)
  var x; // 수집대상 2(변수선언)
  var x; // 수집대상 2(변수선언)
  
  x = 1; // (매개변수 대입)
  console.log(x); // (1)    
  console.log(x); // (2)
  x = 2;      // 값 대입
  console.log(x); //(3)
}
a(1)
  • 위와 같이 호이스팅 된 결과이다.
  • 호이스팅이 끝나고 코드가 실행될 차례이다 (스코프체인 수집 및 this 할당과정은 이후 단원에서 나옴)
  • 실제로는 1, 1, 2 라는결과가 나왔다. hoisting 이라는 개념 때문이다.

 

- 한가지 호이스팅 되는 예를 더 살펴보자.

fuction a () {     
  console.log(b);  // (1)
  var b = 'bbb';   // 수집대상 1 (변수선언)
  console.log(b);  // (2)
  function b () {} // 수집대상 2 함수선언
  console.log(b);  // (3)
}
a();
  • 호이스팅에 대해 잘 모른다면 결과는 undefined / bbb / fuction... 이 예상된다.

 

- 그러나 실제로는 아래의 예시처럼 될 것이다.

fuction a () {     
  var b;           // 수집대상 1.변수는 선언부만 끌어올림
  var b = function b () { }// 수집대상 2.함수 선언은 저체를 끌어올림
                           // 호이스팅이 긑난 상태에서 함수 선언문은 
                           // 함수명으로 선언한 변수에 함수를 할당했다고 볼 수있다.
  
  console.log(b); // (1)
  b = 'bbb';
  console.log(b); // (2)
  console.log(b); // (3)
}
a();
  •  위와같이 호이스팅이 예상되며, 실행 결과는 b 함수 / 'bbb' / 'bbb' 가 될 것이다.

 

- 함수 선언문과 함수 표현식

  • 함수 선언문: function의 정의부만 존재. 반드시 함수명이 정의되어 있어야함.
  • 함수 표현식: function을 별도의 변수에 할당하는 것. 함수명이 없어도 됨
  • 함수를 정의한 함수 표현식을 '기명 함수 표현식' 이라하며, 일반적으로 함수 표현식을 '익명함수 표현식' 이라고 한다.
// 함수를 정의하는 3가지 방식
function a() {/* .... */}        //함수 선언문 a가 곧 변수명
a() //실행 ok

var b = function () {/* .... */} //함수 표현식 변수명 b가 곧 함수명
b() //실행 ok

var c = function d () {/* .... */} // 기명함수 표현식, 변수명은 c, 함수명은 d.
c(); // 실행 ok
d(); // 실행 error

 

 

- 둘의 차이는 무엇인지 함수 선언문과 함수 표현식의 차이를 알아보자.

console.log(sum(1,2));
console.log(mul(3,4));

//함수 선언문
function sum (a, b) {
  return a+b;
}


//함수 표현식
var mul = function (a, b) {
 return a*b;
}
  • 예상하건데 우리가 호이스팅에서 배웠듯이 결과는 3 / 12가 나올것같다.
  • 그러나 함수 표현식은 호이스팅이 다르다.

 

//함수 선언문은 전체를 호이스팅 함.
var sum = function (a, b) {
  return a+b;
}

//함수 표현식은 변수 선언부만 끌어올린다.
var mul 

console.log(sum(1,2));
console.log(mul(3,4));


//함수 표현식
mul = function (a, b) {
 return a*b;
}
  • 함수 표현식의 호이스팅 방식때문에 위의 결과는 3 / mul is not function 이라는 결과가 나올것이다.
  • 함수 선언문은 아무 문제없이 잘 실행되지만, 반대로 큰 혼란의 원인이 되기도 한다.

 

- 혼란 상황을 가정해보자. 회사에서 협엽을 하는데 A대리가 이미 SUM이라는걸 만들었다. 근데 만약 B라는 사원이 logging을 위해서 다음과 같이 SUM을 재정의 한 것이다.

console.log(sum(3,4)); // (1)

var sum = function (a, b) {
  return a+b;
}

.......

var a = sum(1,2);    //(2)

.......

function sum (x,y) {
  return x + "+" + y + " : " + x+y;
}

var c = sum(1,2);
console.log(c);     //(3)
  • 첫번째 console. 함수의 결과는 문자열을 반환 할 것이다.
  • 두번째 var a도 원래 의도는 number type의 값이 들어가야 맞지만, string 값이 들어가 있을것이다. (자바스크립트는 형변환이 없기에)
  • 결국, 소스코드의 오류는 없지만 전혀 다른 기대값이 들어가므로 굉장히 찾기 어려운 오류를 야기 할 것이다.
  • 그러므로 되도록 함수 표현식을 사용하는게 낫다.

 

3.3 스코프, 스코프 체인, outerEnvironmentReference

  • 스코프란 식별자(idnetifier)에 대한 유효범위 이다.
  • 어떤 함수 A의 외부에서 선언한 변수는 외부 scope 뿐만 아니라 A내부에서도 접근 가능하다.
  • 반면 A의 scope 내에서 선언된 변수는 A안에서만 접근 가능하다.
  • ES5는 function scope 이다. (Es6 부터는 bracket 스코프도 가능)
  • 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색하는 것을 Scope Chain 이라한다.
  • 이게 가능한 이유는 LexicalEnvironment의 두번째 수집자료인 outerEnvironmentRefrence 이다.

- Scope Chain

  • outerEnvironmentRefrence는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.
  • '선언될 당시' 라는 점을 유의해야한다.
  • '선언하다' 라는 행위가 일어나는 시점은 Call Stack 상에서 실행 컨텍스트가 활성화 된 상태일 때뿐이다.

 

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

outer();
console.log(a);
  • 위의 예에서 풀어서 이야기해보자.
    1. Start: 전역컨텍스트가 활성화 된다. 전역컨텍스트의 environmentRecode에는 {a, outer} 식별자를 저장한다. 전역컨텍스트는 선언시점이 없으므로 outerEnvironmentReference에는 아무것도 담기지않음 (ths: 전역객체)
    2. 2번째줄 outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자를 저장함. outerEnvironmentReference에는 outer 함수가 선언될 당시의 LexicalEnvironment가 담긴다. outer는 전역공간에 선언 되었으므로, 전역공간의 LexicalEnvironment를 참조복사한다. 이를 [ GLOBAL, { a , outer } ] 라고 표현하자 (this: 전역객체)
    3. 3번째줄 inner 실행 컨텍스트의 environmentRecord에 { a } 에 식별자를 저장함. outerEnvironmentRefernce에는 outer 함수의 LexicalEnvironment 즉, [ outer, { inner }] 를 참조복사 할 것이다 (this: 전역객체)
  • inner의 outerEnvironmentReference는 함수 outer의 LexcialEnvironment를 참조한다.
  • 함수 outer의 LexicalEnvironment 내부에 있는 outerEnvironmentReference는 전역 LexicalEnvironment를 참조한다.
  • 즉 무조건 스코프 체인상에서 가장 먼저 발견된 식별자에만 접근가능하다.
  • 위의 코드의 결과는 스코프체인의 특성때문에 undefined / 1 / 1 이라는 결과가 나온다.

 

코어 자바스크립트 57page 그림

 

  • 주의 해야할점은 스코프체인상에 있는 변수라고 해서 무조건 접근 가능한것은 아니다.
  • 위의 코드에서 inner 스코프의 LexicalEnvironment의 a라는 식별자가 존재하므로, 스코프체인 검색을 더이상 진행하지않고, undefined를 print 한것처럼 말이다. 이를 변수 은닉화 (variable shadowing) 라 한다.

 

참고: 크롬 브라우저 환경에서는 스코프체인중 현재 실행컨텍스트를 제외한 상위 스코프 정보들을 개발자 도구의 콘솔을 통해 간단하게 확인 할 수있다.

 

var a = 1;
var outer = function () {
  var b = 2;
  var inner = function () {
    console.dir(inner);
  }
  inner();
};
outer();

 

 

디버거를 이용하면 좀 더 제대로 된 정보를 확인 할 수 있다. 이는 모든브라우저에서 가능하다. (사파리가 가장 자세하다고함)
var a = 1;
var outer = function () {
  var b = 2;
  var inner = function () {
    console.dir(b);
    debugger;
  }
  inner();
};
outer();

 

 

 

3.4 전역변수 (global variable) 와 지역변수(local variable)

  • 여기 까지 왔다면 전역변수와 지역변수는 이해가 가능 할 것이다.
  • 전역객체에 선언된 outer, a는 전역변수이다
  • outer의 함수 스코프 내부에서 선언된 b와 inner는 지역변수이다.
  • 위에 sum이라는 함수의 재표기가 문제가 되는 경우는, 전역변수안에서 선언 했기 때문에 문제가 된것이다.
  • 코드의 안정성을 위해, 전역변수 사용을 최소하 해야한다.

 

3.5 this

  • 실행 컨텍스트가 활성화 당시에 this가 지정되지 않은 경우는 this는 전역객체를 가리키게 된다.
  • 그 밖에는 함수를 호출하는 방법에 따라서 this에 저장되는 대상이 다르다.

 

3.6 정리

  • 실행 컨텍스트는 객체 활성화되는 시점에 VariableEnvrionment, LexicalEnvironment, ThisBinding의 세가지 정보를 수집한다.
  • VariableEnvironment와 LexicalEnvironment는 동일한 내용으로 구성되지만, LexicalEnvironment는 함수 실행도중에 변경되는 사항이 즉시 반영되지만, VariableEnvironment는 초기상태를 유지한다
  • L.E 에는 매개변수명, 변수의 식별자, 선언한 함수의 수명등을 수집하는 environmentRecord와 바로 직전의 컨텍스트의 lexicalEnvironmnet정보를 참조하는 otuerEnvironmentReference로 구성된다.
  • 스코프는 변수의 유효범위를 만한다. outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironmnet를 참조한다. 코드상에서 변수에 접근하려고 할때 현재 컨텍스트 LexicalEnvironment에서 찾아보고 없으면 outerEnvironmentRecord에 담긴 LexicalEnvironment에서 찾는다. Scope Chain 에서 계속 못찾게 된다면 undefined를 반환 한다.

'To be Developer > JavaScript' 카테고리의 다른 글

Core Javascript - 5 Closure  (0) 2020.05.15
Core Javascript 3 - this  (0) 2020.05.14
Core Javascript 1 - Data Type  (0) 2020.05.12
[javascript] Closure와 필요성.  (0) 2019.11.08
[javascript] let 과 var 그리고 hoisting  (0) 2019.11.07

+ Recent posts