[JS] #5 실행 콘텍스트

simoniful·2021년 4월 22일
0

ES5 JS - Advanced

목록 보기
5/8
post-thumbnail

실행 콘텍스트(Execution Context)

함수가 실행되는 영역이자 묶음입니다. 함수 코드를 실행하고 그 결과를 저장합니다.(변수 값, 다른 함수를 통한 리턴값 등) 스펙상의 사양으로 엔진이 컨트롤하며 개발자가 임의적인 조종은 불가능합니다.

function music(title) {
  var musicTitle = title;
};
music("음악");

music("음악")으로 함수를 호출하면 엔진은 실행 콘텍스트를 생성하고 실행 콘텍스트 안으로 이동합니다. 준비 단계, 초기화 단계, 코드 실행 단계의 과정을 거쳐 실행합니다. 앞에선 초기화 단계와 코드 실행의 단계에 초점을 맞추었다면 이번 장에선 준비 단계에 대하여 이야기 해볼까 합니다.

Execution Context 는 실행 가능한 코드를 만났을 때 생성됩니다. 함수 코드, 글로벌 코드, eval 코드 3가지 유형의 코드를 실행 가능합니다. 각각의 코드 유형은 실행 콘텍스트에서 처리 방법과 실행 환경이 다르기 때문에 구분하여 이해해야 합니다.

  • 함수 코드: Lexical Environment
  • 글로벌 코드: Global Environment
  • eval 코드: Dynamic Environment

상태 컴포넌트

실행 콘텍스트 상태를 위한 오브젝트로 실행 콘텍스트 안에 생성됩니다.

  • 상태 컴포넌트 유형
    • 렉시컬 환경 컴포넌트(LEC):
      Lexical Environment Component
    • 변수 환경 컴포넌트(VEC):
      Variable Environment Component
    • this 바인딩 컴포넌트(TBC):
      This Binding Component
실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC): { },
  변수 환경 컴포넌트(VEC): { },
  this 바인딩 컴포넌트(TBC): { }
}

렉시컬 환경 컴포넌트

함수와 변수의 식별자 해결을 위한 환경 설정하는 것을 의미합니다. 함수 초기화 단계에서 해석한 함수와 변수를 {name: value} 형태로 저장합니다. 이를 통하여 이름으로 함수와 변수를 검색(식별자 해결)할 수 있게 됩니다. 또한, 함수 밖의 함수와 변수를 참조 환경 설정하여 함수 밖의 함수와 변수를 사용할 수 있게 됩니다.
메모리 안에 실행 콘텍스트가 올라와 있는 상황에서 이동없이 모두 한 콘텍스트 안에서 처리하는 것을 목적으로 합니다.

구성

function, with, try-catch에서 렉시컬 환경 컴포넌트 가 생성됩니다.

컴포넌트 구성

  • 환경 레코드
    ER: Environment Record
  • 외부 렉시컬 환경 참조
    OLER: Outer Lexical Environment Reference

설정

실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC): {
    환경 레코드(ER): {
      point:100
    },
    외부 렉시컬 환경 참조(OLER): {
      title:"책",
      getTitle: function() {}
    }
  }
}
  • 환경 레코드 : 함수 안의 함수와 변수를 기록
  • 외부 렉시컬 환경 참조 : function 오브젝트의 [[Scope]]설정
  • 함수 안과 밖의 함수와 변수를 사용할 수 있게 됩니다.

따라서, 하나의 콘텍스트의 관점에 맞추어 코딩을 구성하다 보면 엔진의 처리가 심플해지므로 효율성이 높습니다.

외부 렉시컬 환경 참조(OLER)

스코프와 실행 중인 함수가 Context(묶음) 형태이기에 스코프의 변수와 함수를 별도의 처리 없이 즉시 사용할 수 있습니다. 실행 콘텍스트에서 함수 안과 밖의 함수, 변수를 사용할 수 있으므로 함수와 변수를 찾기 위해 실행 콘텍스트를 벗어나지 않아도 됩니다.

변수 환경 컴포넌트(VEC)

실행 콘텍스트 초기화 단계에서 렉시컬 환경 컴포넌트와 같게 설정합니다. 초기값을 복원할 때 사용하기 위함입니다. 함수 코드가 실행되면 실행 결과를 렉시컬 컴포넌트에 설정합니다. 할당을 통해 초기값이 변하게 되므로 이를 유지하기 위한것입니다. with문에서 주로 사용합니다.


실행과정 예제(1)

var base = 200;
function getPoint(bonus) {
  var point = 100;
  return point + base + bonus;
};
console.log(getPoint(70));
// 370
  1. getPoint 오브젝트의 [[Scope]]에 글로벌 오브젝트 설정
  2. 마지막 줄에서 getPoint()함수 호출
  3. 엔진은 실행 콘텍스트 생성 후 안으로 이동

<준비 단계>

실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC) = {
    환경 레코드(ER): {}
  },
  변수 환경 컴포넌트(VEC): {},
  this바인딩 컴포넌트(TBC): {}
}
  1. 컴포넌트를 생성하여 실행 콘텍스트에 첨부
    • 렉시컬 환경 컴포넌트
    • 변수 환경 컴포넌트
    • this바인딩 컴포넌트
  2. 환경 레코드를 생성하여 렉시컬 환경 컴포넌트에 첨부합니다.
    • 이 후, 여기에 함수 안의 함수, 변수를 바인딩합니다.
실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC) = {
    환경 레코드(ER): {},
    외부 렉시컬 환경 참조(OLER): {
      base: 200
    }
  },
  변수 환경 컴포넌트(VEC) : {},
  this바인딩 컴포넌트(TBC) : {}
}
  1. 외부 렉시컬 환경 참조를 생성하여 렉시컬 환경 컴포넌트에 첨부하고 function 오브젝트의 [[Scope]]참조(설정)합니다.

<초기화 단계>

실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC) = {
    환경 레코드(ER): {
      bonus: 70,
      point: undefined
    },
    외부 렉시컬 환경 참조(OLER): {
      base: 200,
    }
  },
  변수 환경 컴포넌트(VEC): {},
  this바인딩 컴포넌트(TBC): {}
}
  1. 호출한 함수의 파라미터 값을 호출된 함수의 파라미터 이름에 매핑하고 환경 레코드에 작성합니다.
  2. 함수 선언문을 function 오브젝트로 생성합니다.
  3. 함수 표현식과 변수에 초기값을 설정합니다.
  4. 여기까지는 외부에 실행 상태를 제공하지 않습니다. 개발자가 JS 코드로 설정, 접근하는 것이 불가능한 내부처리 단계입니다.

<실행 단계>

var base = 200;
function getPoint(bonus) {
  var point = 100;
  return point + base + bonus;
};
console.log(getPoint(70));
// 370
  1. 함수 안의 코드를 실행합니다.
    • var point = 100;
  2. 실행 콘텍스트 안에서 관련된 함수와 변수를 사용할 수 있습니다.

실행과정 예제(2)

function book() {
  function get() {
     return point;
  };
  var point = 123;
  return get();
};
console.log(book());
  1. function 키워드를 만나게 되고 book 오브젝트 생성합니다.
  2. [[scope]]에 글로벌 오브젝트 설정
  3. 변수 확인, 해당사항 없음
  4. book() 으로 함수를 호출하면 엔진은 실행 콘텍스트를 생성하고, 실행 콘텍스트 안으로 이동합니다.

<준비 단계> - 환경조성

  1. 컴포넌트(LEC, VEC, TBC)를 생성하여 실행 콘텍스트에 첨부합니다.
  2. 환경레코드(ER)를 생성하여 렉시컬 환경 컴포넌트에 아래 첨부
    • 함수 안의 함수, 변수를 환경레코드에 바인딩
  3. 외부 렉시컬 환경 참조(OLER)를 생성하고 렉시컬 환경 컴포넌트 내 첨부
  4. 글로벌 오브젝트의 [[scope]] 를 외부 렉시컬 환경 참조에 설정(참조)합니다.
실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC) = {
     환경레코드(ER): {
        get: function 오브젝트, 
        point: undefined;
     },
     외부 렉시컬 환경 참조(OLER): {}
  },
  변수 환경 컴포넌트(VEC): {},
  this 바인딩 컴포넌트(TBC): {}
}
  1. function 키워드를 만나게 되고 get function 오브젝트 생성합니다.
  2. [[Scope]]에 get 스코프를 설정합니다.
  3. point 변수에 123 값이 할당 됩니다.
  4. get() 으로 함수를 호출하면 엔진은 실행 콘텍스트를 생성하고, 실행 콘텍스트 안으로 이동합니다.
  5. 환경 레코드에서 먼저 찾고 없으면 외부 렉시컬 환경 참조에서 point를 찾습니다.
실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC) = {
    환경레코드(ER): {},
    외부 렉시컬 환경 참조(OLER): {
      point: 123
    }
  },
  변수 환경 컴포넌트(VEC): {},
  this 바인딩 컴포넌트(TBC): {}
}

<실행단계>

  1. 함수 내 코드 실행하여 point 값을 return 합니다.

환경레코드(ER)

구성

실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC): {
    환경 레코드(ER): {
      선언적 환경 레코드(DER): {
        point: 123
      },
      오브젝트 환경 레코드(OER):{}
    },
    외부 렉시컬 환경 참조(OLER): {}
  },
  변수 환경 컴포넌트(VEC): {},
  this 바인딩 컴포넌트(TBC): {}
}

환경 레코드를 구분하는 이유는 기록 대상에 따라 다르기 때문입니다.

  • 선언적 환경 레코드

    • DER: Declarative Environment Record
    • function, 변수, catch 문에서 사용합니다.
    • 앞 절에서 환경 레코드에 설정한다고 한 것이 실제로 여기에 설정됩니다.(함수 안의 함수와 변수를 바인딩)
  • 오브젝트 환경 레코드

    • OER: Object Environment Record
    • 글로벌 함수와 변수, with문에서 사용
    • 정적이 아니라 동적이기 때문

글로벌 환경

실행 콘텍스트(EC): {
  글로벌 환경(GE): {
    환경 레코드(ER): {
      오브젝트 환경 레코드: 글로벌 오브젝트
    },
    외부 렉시컬 환경 참조(OLER): null
  }
}
  • Global Environment
    • 글로벌 오브젝트에서 사용하며, 렉시컬 환경 컴포넌트와 형태가 같습니다.
  • 동적으로 함수와 변수 바인딩
    • 선언적 환경 레코드가 없음
    • 함수에서 var 키워드를 사용하지 않고 변수를 선언하면 글로벌 오브젝트에 설정되기 때문
    • 이런 이유로 오브젝트 환경 레코드 사용
  • 외부 렉시컬 환경 참조 값은 null

This 바인딩 컴포넌트

  • 목적
    • this로, 함수를 호출한 오브젝트의 프로퍼티에 엑세스
      Ex: this.propertyName
  • 엑세스 매커니즘
    • obj.book()형태에서 this로 obj를 참조할 수 있도록 this 바인딩 컴포넌트에 obj참조를 설정합니다.
    • obj의 프로퍼티가 변경되면 동적으로 참조를 합니다. + 설정이 아닌 참조, 복사의 개념 X
  • 내부의 프로퍼티의 유동성(동적으로 변경 가능)
    • this에 이런 프로퍼티를 바인딩하게되면 사용 시점에 해당 프로퍼티가 없을 수 있기 때문에
    • 참조할 수 있는 obj객체만 바인딩합니다.

실행과정 예제

var obj = {point: 100};
obj.getdPoint = function() {
  return this.point;
};
obj.getPoint();
실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC): {
    환경 레코드(ER): {
      선언적 환경 레코드(DER): {},
      오브젝트 환경 레코드(OER):{}
    },
    외부 렉시컬 환경 참조(OLER): {}
  },
  변수 환경 컴포넌트(VEC): {},
  this 바인딩 컴포넌트(TBC): {
    point: 100,
    getPoint: function(){}
  }
}
  1. 마지막 줄에서 obj.getPoint()함수 호출
  2. 실행 콘텍스트 생성
  3. 3개의 컴포넌트 생성
    • 렉시컬 환경, 변수 환경, this바인딩
  4. this바인딩 컴포넌트에 getPoint()에서 this로 obj의 프로퍼티를 사용할 수 있게 바인딩
  5. 파라미터, 함수 선언문, 변수 선언 없음
  6. return this.point; 실행
  7. this.바인딩 컴포넌트에서 point 검색
    • getPoint()함수를 호출한 오브젝트가 this 바인딩 컴포넌트에 설정(참조)된 상태
  8. this 바인딩 컴포넌트에 point 프로퍼티가 있기에 100 반환
  9. obj.getPoint()에서 obj의 프로퍼티가 this 바인딩 컴포넌트에 바인딩되도록 의도적으로 설계해야 합니다.

호출 스택(call stack)

function one(){
  two();
  console.log(1);
};
function two(){
  three();
  console.log(2);
};
function three(){
  console.log(3);
};
one();
// 3
// 2
// 1
  • call stack
    • 실행 콘텍스트의 논리적 구조
  • First In Last Out 순서
    • 함수가 호출되면 스택의 가장 위에 실행 콘텍스트가 위치하게 됩니다.
    • 다시 함수안에서 함수를 호출하면 호출된 함수의 실행 콘텍스트가 스택의 가장 위에 놓이게 됩니다.
    • 함수가 종료되면 스택에서 빠져 나옵니다.(FILO순서)
    • JS의 싱글스레드 언어로서 특징
  • 가장 아래는 글로벌 오브젝트 함수가 위치합니다.

파라미터 매핑

함수 호출

함수가 호출되면 3개의 파라미터 값을 실행 콘텍스트로 넘겨줍니다.

  • 함수를 호출한 오브젝트
  • 함수 코드
  • 호출한 함수의 파라미터 값
  • 함수를 호출한 오브젝트 : this바인딩 컴포넌트에서 설정하여 this로 참조
  • 함수 코드 : function 오브젝트의 [[Code]]에 설정
  • 호출한 함수의 파라미터 값 : 호출된 함수의 Argument 오브젝트에 설정

파라미터 값 매핑

호출한 함수에서 넘겨 준 파라미터 값을 호출 된 함수의 파라미터 작성 순서에 맞춰 값을 매핑하는 것.

  • 엔진 처리 관점
    실행 콘텍스트로 넘겨 준 파라미터 값
    function 오브젝트의 [[FormalParameters]]에 작성된 이름에 값
    이 두 가지 값을 매핑하고 결과를 선언적 환경 레코드(DER)에 설정하는 것입니다.
    • [[FormalParameters]]
      호출된 함수의 파라미터에 작성된 이름이 있는 곳

매핑 방법

var obj = {};
obj.getTotal = function(one, two) {
  return one + two;
}
console.log(obj.getTotal(11,22,77));
// 33
  1. 여기서 실행 콘텍스트로 넘겨준 파라미터 값을 param이라 지칭합니다.
  2. getTotal 오브젝트의 [[FormalParameters]]에서
    호출된 함수의 파라미터 이름을 구합니다.
    (설명 편의를 위해 name이라 합니다.)
    • name은 ["one", "two"]형태입니다.
    • [[FormalParameters]]는 function 오브젝트를 생성할 때 설정합니다.
  3. name 배열을 하나씩 읽습니다.
  4. param에서 index번째의 값을 구합니다.
    • 인덱스에 값이 없을 경우 undefined 반환
  5. name의 파라미터 이름과 4번에서 구한 값을 선언적 환경 레코드에 설정
    • {one: 11, two: 22} 형태로 설정합니다.
    • 같은 이름이 있으면 값이 대체됩니다.
  6. name을 전부 읽을 때까지 3번에서 5번까지 반복

파라미터 값 할당 기준

var obj = {};
obj.getTotal = function(one, two) {
  var one; 
  console.log(one + two);
  two = 77;
  console.log("two:"+two);
}                         
obj.getTotal(11, 22);    

<초기화 단계>

  1. obj.getTotal(11, 22) 함수가 호출되면 파라미터 값을 실행 컨텍스트에 넘겨줍니다.
  2. 파라미터 이름에 값을 매핑하여 선언적 환경 레코드에 설정합니다.
    → {one: 11, two: 22}
  3. var one;
    ⇒ 선언적 환경 레코드에서 one의 존재를 체크 후, 파라미터 이름을 설정하였으므로 존재하여 one을 기록하지 않습니다.
  4. two = 77;
    ⇒ 선언적 환경 레코드에서 two의 존재를 체크 후, 파라미터 이름을 설정하였으므로 존재하며 two를 기록하지 않습니다.
  5. 그렇기에 함수에 초기화할 코드가 없습니다 (빈 오브젝트나 undefined로 초기화 등록할 코드가 없다는 의미)

<실행 단계>

  1. 선언적 환경 레코드는 {one: 11, two: 22}상태가 됩니다.
  2. var one;
    ⇒ 단지, 변수 선언이기에 처리하지 않습니다.
  3. console.log(one + two);
    ⇒ 선언적 환경 레코드에서 one과 two의 값을 구해 연산하여 33을 출력합니다.
  4. two = 77;
    ⇒ 값을 할당하는 코드이며 실행단계이므로 two에 77을 할당하여 {two: 22}가 {two: 77}로 변경됩니다.
  5. console.log("two:" + two);
    ⇒ 선언적 환경 레코드에서 two의 값을 구하며 4번 항목에서 설정된 77이 출력됩니다.

예제

var obj = {};
obj.getTotal = function(one, two, two) {
  console.log(one + two);
};
obj.getTotal(11, 22, 55);
// 66

<초기화 단계>

  1. obj.getTotal(11, 22, 55) 함수가 호출되면
    파라미터 값을 실행 콘텍스트에 넘겨줍니다.
  2. 파라미터 이름에 값을 매핑
    getTotal 오브젝트의 [[FormalParameter]]에서 호출된 함수의 파라미터 이름을 구함
    ["one", "two"]
    생성된 argu 오브젝트의 인덱스에 따라 차례대로 값을 구하며 two를 덮어쓰게 됩니다.
    선언적 환경 레코드에 {one: 11, two: 55} 형태로 설정합니다.
  3. 함수를 호출한 obj를 this 바인딩 컴포넌트에 설정
  4. 내부 함수 선언문을 검색 - 해당사항 없음
  5. 내부 변수 검색 - 해당사항 없음
  6. 초기화할 코드가 없기에 다시 첫 줄로 이동하여 함수 코드를 실행합니다.

<실행 단계>

  1. 선언적 환경 레코드는 {one: 11, two: 55} 상태
  2. console.log(one + two)로 이동
    선언적 환경 레코드에서 one, two 값을 구하고 그 결과 값인 66 출력합니다.
실행 콘텍스트(EC): {
  렉시컬 환경 컴포넌트(LEC) = {
    환경레코드(ER) : {
      선언적 환경 레코드(DER) : {
        argument : {one: 11, two: 55}
      },
      오브젝트 환경 레코드(OER) : {}
    },
    외부 렉시컬 환경 참조(OLER) : {
    }
  },
  변수 환경 컴포넌트(VEC) : {},
  this 바인딩 컴포넌트(TBC) : {
    obj : {}
  }
}
profile
소신있게 정진합니다.

2개의 댓글

comment-user-thumbnail
2021년 4월 23일

아.. 쉽지 않은 실행컨텍스트입니다요.. 정리 굳입니다🙆‍♂️

1개의 답글