실행할 코드에 제공할 환경 정보들을 모아놓은 객체
클로저를 지원하는 대부분의 언어에서 유사 or 동일한 개념이 적용됨.
JS가 어떤 실행 컨텍스트가 활성화되는 시점에 수행하는 동작들
호이스팅 (선언된 변수를 위로 끌어올림)
외부 환경 정보를 구성
this 값 설정
→ 특이한 현상 발생 (다른 언어에서 발견 불가)
하나의 실행 컨텍스트를 구성할 수 있는 방법
전역공간
자동으로 생성됨
eval() 함수
함수, 블록 ({}
)
흔히 실행 컨텍스트를 구성하는 방법
한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간 ⇒ 현재 실행할 코드에 관여하게 되는 시점
동일한 환경에 있는 코드 실행 시, 필요한 환경 정보들을 모아 컨텍스트 구성
이를 콜 스택(call stack)에 쌓아 올림
가장 위에 쌓여 있는 컨텍스트와 관련된 코드들을 실행
→ 전체 코드의 환경 & 순서 보장
예시
처음 JS 코드를 실행하는 순간(1) 전역 컨텍스트가 콜 스택에 담김.
최상단의 공간: 브라우저에서 자동 실행 (별도의 실행 명령 없어도) → JS 파일이 열리는 순간 전역 컨텍스트가 활성화됨.
전역 컨텍스트 관련 코드 순차로 진행
(3)에서 outer 함수 호출
JS 엔진이 outer에 대한 환경 정보를 수집 → outer 실행 컨텍스트 생성 → 콜 스택에 담음. → 콜 스택 맨 위: outer 실행 컨텍스트 → 전역 컨텍스트 관련 코드 실행 일시중단 → outer 실행 컨텍스트 관련 코드 (outer 함수 내부 코드들) 순차 실행
(2)에서 inner함수의 실행 컨텍스트가 콜 스택의 맨 위에 담김
outer 컨텍스트 관련 코드 실행 중단 → inner 함수 내부의 코드를 순서대로 진행
inner함수 실행 종료 후, inner 실행 컨텍스트를 콜 스택에서 제거
아래의 outer실행 컨텍스트 실행
콜 스택에 전역 컨텍스트만 남음
콜 스택에 남은 것 無 → 종료
어떤 실행 컨텍스트가 활성화될 때, JS엔진은 환경 정보들(해당 컨텍스트 관련 코드들을 실행하는 데 필요한)을 수집 → 실행 컨텍스트 객체에 저장
활성화된 실행 컨텍스트의 수집 정보
VariableEnvironment
LexicalEnvironment
Thisbinding
- 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보
- 선언 시점의
LexicalEnvironment
의 스냅 샷 (변경 사항 반영 X)
실행 컨텍스트 생성 시
VariableEnvironment
에 정보를 먼저 담음
그대로 복사해서 LexicalEnvironment
제작 (이후 주로 활용)
처음에는
VariableEnvironment
와 같음 (변경 사항 → 실시간 반영)
한국어 번역
사전적 환경
환경 정보들(컨텍스트를 구성하는)을 사전에서 접하는 느낌으로 모아놓은 것
ex. “현재 컨텍스트 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성”
통용되는 번역은 아님
표기법
LexicalEnvironment
: lexical environment
(일반 추상적인 개념)를 JS에서 구현한 구체적인 대상
VariableEnvironment
& variable environment
도 마찬가지
식별자 정보들(현재 컨텍스트와 관련된 코드의) 저장
식별자에 해당하는 것들
컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
함수 자체 (선언한 함수가 있을 경우)
var로 선언된 변수의 식별자
컨텍스트 내부 전체를 처음부터 끝까지 훑으며 순서대로 수집함.
[참고] 전역 실행 컨텍스트
전역 객체를 활용함. (변수 객체를 생성하는 대신)
→ 브라우저의 window
, Node.js의 global
객체 등 (호스트 객체로 분류)
hoist(끌어올리다) + ing
JS 엔진은 식별자들을 최상단으로 끌어 올려놓은 뒤, 실제 코드를 실행 한다
원리: 변수 정보 수집 과정을 모두 마쳤더라도 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태. 그럼에도 불구하고 JS는 이미 해당 환경에 속한 코드의 변수명들을 모두 아는 셈.
JS 엔진이 실제로 끌어 올리지는 않음. but, 편의상 끌어올린 것으로 간주.
예상 (호이스팅 되지 않았을 때) | 실제 |
---|---|
(1) 1, (2) undefined, (3) 2 | (1) 1, (2) 1, (3) 2 |
예제 2-2
인자들과 함께 함수를 호출한 경우
인자를 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이뤄진 것으로 간주 가능
코드 내부에서 변수를 선언한 것과 다른 점 無
arguments에 전달된 인자를 담는 것 제외
LexicalEnvironment 입장에서 완전 같음.
[참고] arguments
실행 컨텍스트 생성 시점에 함께 만드는 정보 중 하나
전달한 인자 모두 (지정한 매개변수의 갯수와 무관) arguments 정보에 담김
→ 함수의 자율성을 높이는 측면에서 광범위하게 활용했었음.
문제점
유사 배열 객체
→ 배열처럼 활용하려면 별도 처리 필요
함수 내부에서 매개변수의 값 바꾸면 arguments 값도 함께 바뀜
→ 본래의 개념(‘전달된 인자를 모두 저장한 데이터’)과 상이
대안
나머지 파라미터 (rest parameter. ES6 등장)
호이스팅 처리
environmentRecord
변수명만 끌어올림. 할당 과정은 원래 자리에 남겨둠.
매개변수도 마찬가지
수집 대상 1, 2, 3 순서 대로 끌어올림
→ 예제 2-4
실제 코드 실행 (예제 2-4)
2번째 줄 : 변수 x 선언
메모리에서 저장할 공간 미리 확보 → 확보한 공간의 주솟값을 변수 x에 연결
3, 4번째 줄 : 다시 변수 x 선언 → 무시 (이미 선언)
6번째 줄 : x에 1 할당
1을 별도의 메모리에 담음 → x와 연결된 메모리 공간에 1을 가리키는 주솟값 입력
7, 8번째 줄 : x 출력
1 출력
9번째 줄 : x에 2 할당
2를 별도의 메모리에 할당 → 그 주솟값을 들고 x와 연결된 메모리 공간으로 감 → 기존의 주솟값 (1 가리키는)을 대치함 → 변수 x는 2를 가리킴
10번째 줄 : x 출력
(3)에서 2출력 → 함수 내부 모든 코드가 실행됨 → 실행 컨텍스트가 콜 스택에서 제거 됨
원본 코드
function a() {
console.log(b) //(1)
var b = 'bbb' //수집대상 1
console.log(b) //(2)
function b() {} //수집대상 2
console.log(b) //(3)
}
a()
예상 (호이스팅 되지 않았을 때) | 실제 |
---|---|
(1) 에러 또는 undefined, (2) ‘bbb’, (3) b 함수 | (1) b 함수, (2) ‘bbb’, (3) ‘bbb’ |
호이스팅을 마친 상태 (함수 선언문 → 함수 표현식)
function a() {
var b // 수집 대상 1. 변수: 선언부만 끌어올림
var b = function b() {} // 수집 대상 2. 함수 선언: 전체 끌어 올림
console.log(b) //(1)
var b = 'bbb' // 변수의 할당부는 원래 자리에 남겨둠
console.log(b) //(2)
console.log(b) //(3)
}
a()
동작 원리
a 함수 실행하는 순간 → a 함수의 실행 컨텍스트가 생성됨
이 때, 호이스팅 발생변수명 & 함수 선언 정보를 순서대로 위로 끌어올림 (수집)
호이스팅 끝난 상태에서의 함수 선언문 : 함수 선언문 → 함수 표현식
함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음.
실행 컨텍스트 내부의 코드 순서대로 실행
2번째 줄 : 변수 b 선언
메모리에서 저장할 공간 미리 확보 → 확보한 공간의 주솟값을 변수 b에 연결
3번 째 줄: 변수 b 재선언. 함수 b를 변수 b에 할당하라
선언 과정 무시 (이미 선언). 함수는 별도의 메모리에 담김 → 함수가 저장된 주솟값을 b와 연결된 공간에 저장 → 변수 b는 함수를 가리킴
5번째 줄 : 변수 b에 할당된 함수 b 출력
6번째 줄 : 변수 b에 ‘bbb’ 할당
b와 연결된 메모리 공간에 ‘bbb’가 담긴 주솟값으로 덮어씀 → 변수 b는 ‘bbb’를 가리킴
7, 8번째 줄 : (2), (3) 모두 ‘bbb’ 출력
함수 내부의 모든 코드가 실행됨 → 실행 컨텍스트가 콜 스택에서 제거됨
공통점
함수를 새롭게 정의할 때 쓰이는 방식
차이점
코드 형태
function a () { } // 함수 선언문. 함수명 a가 곧 변수명.
a(); // 실행 OK
var b = function () { } // (익명) 함수 표현식. 변수명 b가 곧 함수명.
b(); // 실행 OK
var c = function d() { } // 기명 함수 표현식. 변수명은 c, 함수명은 d.
c(); // 실행 OK
d(); // 에러!
예시 | 실질적 차이
원본 코드
console.log(sum(1, 2));
console.log(multiply(3, 4));
function sum (a, b) { // 함수 선언문 sum
return a + b;
}
var multiply = function (a, b) { // 함수 표현식 multiply
return a + b;
};
호이스팅을 마친 상태
var sum = function sum(a, b) { // 함수 선언문은 전체를 호이스팅함.
return a + b
}
var multiply // 변수 선언부만 끌어올림
console.log(sum(1, 2))
console.log(multiply(3, 4))
multiply = function (a, b) { // 변수의 할당부는 원래 자리에 남겨둠.
return a * b
}
내부 코드들 순서대로 실행
1번째 줄: 메모리 공간 확보. 확보된 공간의 주솟값을 변수 sum에 연결
4번째 줄: 또 다른 메모리 공간 확보. 그 공간의 주솟값을 변수 multiply에 연결
1번째 줄 (다시): sum 함수를 또 다른 메모리 공간에 저장 → 그 주솟값을 변수 sum 공간에 할당 → 변수 sum은 함수 sum을 바라보는 상태
5번째 줄: sum 실행 → 정상 실행 → 3 출력
6번째 줄: multiply에 값 할당 X → 비어있는 대상을 함수로 여겨 실행하라고 명령 → multiply is not a function
에러 msg 출력 → 8번째 줄은 6번째 줄의 에러로 실행 X. 런타임 종료
함수의 호이스팅 원리
전역 컨텍스트가 활성화될 때 전역 공간에 선언된 함수들이 모두 가장 위로 끌어올려짐.
동일한 변수명에 서로 다른 값을 할당 시, 나중에 할당한 값이 먼저 할당한 값을 덮어씌움 (override
)
코드 실행 중 실제 호출되는 함수 ⇒ 마지막에 할당한 함수
문제점
함수(문제의 원인) 에러 반환 無
이 함수를 활용하는 다른 함수에서도
결론
전역 공간에서 함수 선언 or 동명의 함수 중복 선언 → X
함수 표현식이 (상대적으로) 안전
의도대로 잘 동작 & 빠르고 쉬운 디버깅 (바로 에러 검출)
식별자에 대한 유효범위
접근 가능 범위
어떤 경계 A의 __에서 선언한 변수의 접근 가능 범위 | 외부 | 내부 |
---|---|---|
A의 | 외/내부 | 내부 |
ES5 vs ES6
ES5 | ES6 | |
---|---|---|
~에 의해서 scope 생성 | 함수 (전역 공간 제외) | 함수 & 블록 |
블록
새로 생긴 let
, const
, class
, strict mode
에서의 함수 선언 등에 대해서만 범위로서의 역할 수행 (var
로 선언한 변수에 대해서는 적용 X)
함수 스코프, 블록 스코프 용어 사용
둘 구분을 위해
‘식별자의 유효범위’를 안에서 바깥으로 차례로 검색해나가는 것
스코프 체인을 가능케 하는 것
현재 호출된 함수가 선언될 당시의 LexicalEnvironment
를 참조
‘선언하다’라는 행위가 실제로 일어날 수 있는 시점
콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때
특징
연결리스트(linked list)형태를 띔
‘선언 시점의 LexicalEnvironment
’를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvironment
가 있을 것
변수 은닉화 (variable shadowing)
무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능
가장 가까운 요소부터 차례대로만 접근 가능 (다른 순서로 접근 불가)
→ 여러 스코프에서 동일한 식별자 선언 시, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능
표기
[ 실행 컨텍스트의 이름,
environmentRecord
의 객체 ]
ex. [ GLOBAL, { a, outer } ]
var a = 1
var outer = function () {
var inner = function () {
console.log(a)
var a = 3
}
inner()
console.log(a)
}
outer()
console.log(a)
동작 원리 | 설명 요약 표
단어
L.E
: LexicalEnvironment
e
: environmentRecord
o
: outerEnvironmentReference
[숫자]
: 코드 줄 번호표
특징
왼쪽에서 오른쪽으로 바라보면
규모 작아짐. But 접근 가능한 변수의 수 증가(스코프 체인 타고)
변수의 은닉화
동작 원리 | 설명
시작: 전역 컨텍스트 활성화
전역 컨텍스트
environmentRecord
: { a, outer }
식별자 저장outerEnvironmentReference
: 아무것도 안 담김. (전역 컨텍스트는 선언 시점 無)this
: 전역 객체
1, 2번째 줄: 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당
10번째 줄: outer함수 호출
2번째 줄:
outer
실행 컨텍스트의
environmentRecord
: { inner }
식별자 저장
outerEnvironmentReference
: outer 함수 선언 시(전역 공간 에서 선언 → 전역 컨텍스트)의 LexicalEnvironment
가 담김
[ GLOBAL, { a, outer } ]
this
: 전역 객체
3번째 줄: outer
스코프에 있는 변수 inner
에 함수 할당
7번째 줄: inner
함수 호출
outer
실행 컨텍스트의 코드 → 임시중단inner
실행 컨텍스트 → 활성화 (3번째 줄로 이동)3번째 줄:
inner
실행 컨텍스트의
environmentRecord
: { a }
식별자 저장
outerEnvironmentReference
: inner함수 선언 시(outer함수 내부에서 선언 → outer함수)의 LexicalEnvironment
가 담김
[ outer, { inner } ]
this
: 전역 객체
4번째 줄: 식별자 a에 접근
environmentRecord
에서 a 검색undefined
출력5번째 줄: inner 스코프에 있는 변수 a에 3할당
6번째 줄: inner 함수 실행 종료
콜 스택에서 inner 실행 컨텍스트 제거 → 바로 아래의 outer 실행 컨텍스트 활성화 → 중단했던 7번째 줄 다음으로 이동
8번째 줄: 식별자 a에 접근
JS 엔진이 활성화된 실행 컨텍스트의
LexicalEnvironment
에 접근
첫 요소의environmentRecord
에서 a 찾고 없으면 →outerEnvironmentReference
에 있는environmentRecord
로 넘어가는 식으로 계속 검색
→ 전역 LexicalEnvironment
에 a 있음 → a에 저장된 값 (1) 반환
9번째 줄: outer 함수 실행 종료
콜 스택에서 outer 실행 컨텍스트 제거 → 바로 아래의 전역 컨텍스트 활성화 → 중단했던 10번째 줄의 다음으로 이동
11번째 줄: 식별자 a에 접근
활성화 상태인 전역 컨텍스트의 environmentRecord
에서 a 검색 → a 찾음 → 1출력 → 모든 코드 실행 완료 → 콜 스택에서 전역 컨텍스트 제거 → 종료
크롬 브라우저 환경에서 스코프 체인 중 상위 스코프 정보들(현재 실행 컨텍스트 제외)을 콘솔로 확인 가능
특징
함수 내부에서 실제로 호출할 외부 변수들의 정보만 보여줌
방법
함수 내부에서 함수 출력
var a = 1;
var outer = function () {
var b = 2;
var inner = function () {
console.log(b) // 추가 시, 결과 2
console.dir(inner); // 결과 1
};
inner();
};
outer();
debugger
로 더 제대로 된 정보 확인 가능
debugger
입력구분
전역 변수 | 지역 변수 | |
---|---|---|
~에서 선언한 변수 | 전역 공간 | 함수 내부 |
전역 변수 사용 최소화 권고 (코드의 안전성을 위해)
안전성
지역 변수 > 함수 표현식 > 함수 선언문
전역 변수 사용 최소화에 도움 주는 방법
즉시실행함수 (IFE), 네임 스페이스, 모듈 패턴, 샌드박스 패턴
AMD (모듈 관리 도구), CommonJS, ES의 모듈 등
실행 컨텍스트
실행할 코드에 제공할 환경 정보를 모아놓은 객체.
전역 컨텍스트 (전역 공간에서 자동으로 생성), eval, 함수 실행에 의한 컨텍스트 등
활성화되는 시점에 3가지 정보 수집 (VariableEnvironment
, LexicalEnvironment
, ThisBinding
)
생성 시 VariableEnvironment
와 LexicalEnvironment
가 동일한 내용으로 구성됨
구성 내용
environmentRecord
매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집
outerEnvironmentReference
바로 직전 컨텍스트의 LexicalEnvironment
정보를 참조
차이점
LexicalEnvironment
→ 함수 실행 도중에 변경되는 사항이 즉시 반영
VariableEnvironment
→ 초기 상태 유지
호이스팅
environmentRecord
의 수집 과정을 추상화한 개념
실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 ‘끌어올린다’고 해석
변수 선언과 값 할당이 동시에 이뤄진 문장
‘선언부’만을 호이스팅 & 할당 과정은 원래 자리에 남음 → 여기서 함수 선언문 / 표현식의 차이 발생
스코프
변수의 유효 범위
outerEnvironmentReference
해당 함수가 선언된 위치의
LexicalEnvironment
를 참조
코드 상에서 어떤 변수에 접근하려고 하면
현재 컨텍스트의 LexicalEnvironment
를 탐색 → 발견되면 그 값 반환. 발견하지 못할 경우 다시 outerEnvironmentReference
에 담긴 LexicalEnvironment
를 탐색하는 과정을 거침
전역 컨텍스트의 LexicalEnvironment
까지 탐색해도 해당 변수를 찾지 못하면 undefined 반환
전역/지역 변수
전역 컨텍스트의
LexicalEnvironment
에 담긴 변수
그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들
this
실행 컨텍스트를 활성화하는 당시에 지정된 this가 저장됨
함수를 호출하는 방법에 따라 그 값이 달라짐
지정되지 않은 경우에 전역 객체가 저장됨
참고