본문 바로가기
👩‍💻 Programming/JavaScript

코어 자바스크립트_2장 실행 컨텍스트

by codingBear 2022. 10. 26.
728x90
반응형

01. 실행 컨텍스트란?

스택

👉 출입구가 하나뿐인 우물 같은 데이터 구조. FILO(선입후출) or LIFO(후입선출) 동작. 

👉 콜스택 초과 시 스택 오버플로(stack overflow) 발생.

 

👉 양쪽이 열려 있는 파이프 같은 데이터 구조. FIFO(선입선출) or LILO(후입후출) 동작.

❔ 실행 컨텍스트

👉 실행할 코드에 제공할 환경 정보들을 모아놓은 객체. 활성화되는 시점에 호이스팅(hoisting), 외부 환경 정보 구성, this 값 설정 등의 동작을 수행한다. 이로 인해 자바스크립트만의 특이한 현상들이 발생.

 

🔷 동작 순서

  1. 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성(🔸 동일한 환경이란 하나의 실행 컨텍스트를 구성할 수 있는 eval() 함수, 함수 등. eval을 제외하면 함수가 실행 컨텍스트를 구성하는 유일한 방법.)
  2. 이를 콜 스택(call stack)에 쌓음. 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 현재 실행할 코드에 관여하게 되는 시점.
  3. 가장 위에 쌓인 컨텍스트와 관련된 코드부터 실행하는 식으로 전체 코드의 환경과 순서를 보장

 

🔷 실행 컨텍스트를 구성하는 환경 정보 종류


02. VariableEnvironment

👉 구성하는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 것이 차이점. 실행 컨텍스트 생성 시 VariableEnvironment에 정보를 먼저 담고 이를 복사하여 LexicalEnvironment를 만들고 이후 LexicalEnvironment를 활용.


03. LexicalEnvironment

👉 '어휘적 환경' 혹은 '정적 환경'이라는 말로 주로 번역되나, 수시로 변하는 환경 정보라는 뜻에서 보자면 '사전(辭典)적인 환경'이 더 어울리는 표현.

 

2-3-1. environmentRecord와 호이스팅

🔷 environmentRecord

👉 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수의 식별자 등 현재 컨텍스트와 관련된 코드의 식별자 정보들이 순서대로 저장됨.

👉 따라서 관련된 코드가 실행되기 전임에도 자바스크립트 엔진은 이미 해당 코드의 변수명을 모두 인지함. 즉 '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행'함. 이것이 바로 호이스팅(hoisting)임.(실제로 끌어올리는 것이 아니라 그렇게 간주하는 것임.)

 

🔷 호이스팅 규칙

🔹 매개변수와 변수에 대한 호이스팅

👉 예상 출력값: (1) // 1, (2) // undefined, (3) // 2

👉 위 예제 2-2처럼 인자들과 함께 함수를 호출한 경우 전달된 인자를 arguments에 담는 것을 제외하면 다음 예제 2-3처럼 코드 내부에서 변수를 선언한 것과 동일함.

👉 environmentRecord는 식별자만 수집하고 식별자에 할당된 값은 수집하지 않음. 따라서 변수만 호이스팅하고 할당 과정은 원래 자리에 놔둠. 호이스팅을 마친 상태는 다음 예제와 같다.

👉 실제 출력값: (1), (2) // 1, (3) // 2

 

🔹 함수 선언에 대한 호이스팅

👉 예상 출력값: (1) // 에러 or undefined, (2) // 'bbb', (3) // 함수 b

👉 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올림.

👉 호이스팅이 끝난 상태에서 함수 선언문은 변수에 함수를 할당한 것으로 생각할 수 있음.

👉 실제 출력값: (1) // 함수 b, (2), (3) // 'bbb'

 

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

👉 함수 선언문(function declaration): function OOO () {}. function 정의부만 존재하고 별도의 할당 명령이 없는 함수.

👉 함수 표현식(function expression): const OOO = function () {}. 정의한 function을 별도의 변수에 할당하는 함수. 함수명이 꼭 없어도 됨. 함수명이 없는 함수를 '익명 함수 표현식', 있는 함수를 '기명 함수 표현식'이라고 함.

 

🔹 함수 선언문과 함수 표현식의 차이

👉 함수 선언문은 함수 전체를 호이스팅. 함수 표현식은 변수 선언부만 호이스팅. 함수를 다른 변수에 값으로써 '할당'한 것이 함수 표현식.

 

🔹 함수 선언문의 위험성과 상대적으로 안전한 함수 표현식

👉 전역 컨텍스트가 활성화될 때 전역공간에 선언된 함수 모두가 호이스팅됨. 동일한 변수명에 서로 다른 값을 할당할 경우 나중에 할당한 값이 먼저 할당한 값을 덮어씌움(override). 따라서 실제로 호출되는 함수는 맨 만지막에 선언되는 함수. 위 예제에서는 숫자의 합이 아닌 문자열이 반환됨.

 

👉 함수 표현식은 선언과 호출이 따로 이뤄지기 때문에 호출을 선언보다 먼저 할 경우 에러를 반환함.


2-3-2. 스코프, 스코프 체인, outerEnvironmentReference

❔ 스코프(scope)

👉 식별자에 대한 유효 범위. ES5까지는 함수에 의해서만 스코프가 생성되었으나, ES6에서는 블록에 의해서도 생김. 다만 var에는 적용되지 않고 ES6에서 도입된 let, const, class, strict mode에서 함수 선언에만 적용.

 

❔ 스코프 체인(scope chain)

👉 outerEnvironmentReference를 통해 식별자의 유효 범위를 안에서 바깥으로 차례로 검색해나가는 것.

👉 outerEnvironmentReference는 '선언될 당시'의 LexicalEnvironment를 참조.

👉 A 함수 내부에 B 함수를 선언하고 다시 B 함수 내부에 C 함수를 선언한 경우

  1. C의 outerEnvironmentReference -> 함수 B의 LexicalEnvironment 참조
  2. B의 outerEnvironmentReference -> 함수 A의 LexicalEnvironment 참조
  3. A의 outerEnvironmentReference -> 전역 컨텍스트의 LexicalEnvironment 참조

👉 각 outerEnvironmentReference는 자신이 선언되던 때의 상위 LexicalEnvironment를 그대로 참조. 선언된 시점의 LexicalEnvironment만 참조하므로 가장 가까운 요소부터 차례대로만 접근 가능. 여러 스코프에서 동일한 식별자를 선언하면 스코프 체인상에서 가장 먼저 발견된 식별자에만 접근 가능.

 

👉 위 표를 보면 전역 컨텍스트 → outer 컨텍스트 → inner 컨텍스트 순으로 규모는 작아지나, 스코프 체인을 타고 접근 가능한 변수는 늘어남을 볼 수 있다. 

  • 접근 가능한 변수 목록
    • 전역 공간: 전역 스코프에서 생성된 변수
    • outer 함수 내부: outer 스코프, 전역 스코프
    • inner 함수 내부: inner, outer, 전역 스코프

👉 스코프 체인상에 있는 변수라고 모두 접근할 수 있지는 않음. 특정 식별자에 접근하려고 하면 스코프의 LexicalEnvironment부터 검색함. 위 예제를 보면 inner 함수 내부에서는 전역 공간의 식별자 a와 inner 함수 내부의 식별자 a 모두 접근 가능하나, inner 스코프의 LexicalEnvironment에 식별자 a가 존재하므로 전역 공간에서 선언한 동일한 이름의 식별자 a에는 접근할 수 없음. 이를 변수 은닉화(variable shadowing)이라고 함.

 

❕Tips

👉 개발자 도구에서 debugger 키워드를 활용하면 스코프 체인과 this 정보를 확인할 수 있다.

 

🔷 전역변수와 지역변수

  • 전역변수: 전역 공간에서 선언한 변수
  • 지역변수: 함수 내부에서 선언한 변수

👉 예제 2-11과 2-12에서 함수가 엉뚱한 결괏값을 반환하는 문제도 함수 sum을 감싸는 별도의 함수를 선언하여 함수 sum을 지역변수로 만들면 쉽게 해결할 수 있는 문제이다.

👉 코드의 안정성을 위해 가급적 전역변수를 최소한으로 사용하자.

(🔸 즉시실행함수(IFE, immediately invoked function expression), 네임 스페이스(name space), 모듈 패턴(module pattern), 샌드박스 패턴(sandbox pattern)이나 모듈 관리도구인 AMD, CommonJS, ES6의 모듈 등을 활용하면 전역변수 최소화를 할 수 있음.)


04 This

👉 실행 컨텍스트 활성화 당시 this가 지정되지 않은 경우 this에는 전역 객체가 저장됨. (3장에서 깊게 다룰 예정.)


05 정리

👉 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체. 전역 공간에 자동으로 생기는 전역 컨텍스트eval 및 함수 실행에 의한 컨텍스트 등이 있음. 활성화되는 시점에 VariableEnvironment, LexicalEnvironment, ThisBinding의 세 가지 정보를 수집.

👉 VariableEnvironment와 LexicalEnvironment는 동일한 내용으로 구성. 차이점은 LexicalEnvironment는 함수 실행 중 변경사항이 즉시 반영되나 VariableEnvironment는 초기 상태 유지한다는 것. 이 둘은 구성하는 요소는 environmentRecord와 outerEnvironmentReference임. environmentRecord매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집. outerEnvironmentReference직전 컨텍스트의 LexicalEnvironment 정보를 참조.

👉 호이스팅environmentRecord의 수집 과정에서 일어남. 변수 선언부를 코드 최상단으로 끌어올리는 현상. 함수 선언문과 함수 표현식의 차이를 생각하면 됨.

👉 스코프변수의 유효 범위. outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment를 참조. 어떤 변수에 접근 시 현재 컨텍스트의 LexicalEnvironment를 탐색해서 발견되면 그 값을 반환하고, 없으면 outerEnvironmentReference에 담긴 LexicalEnvironment를 탐색. 전역 컨텍스트의 LexicalEnvironment에도 해당 변수가 없다면 undefined 반환.

👉 전역 컨텍스트의 LexicalEnvironment에 담긴 변수가 전역변수. 그 밖의 함수에 의해 생성된 실행 컨텍스트 내의 변수를 지역변수라 함.

👉 this에는 실행 컨텍스트를 활성화하는 당시에 지정된 this가 저장. 지정되지 않으면 전역 객체가 저장. 함수를 호출하는 방법에 따라 값이 달라짐.

728x90
반응형

댓글