heyday2024 님의 블로그
[JS 기초 문법 3주차(2)] 실행 컨텍스트 (스코프, 변수, 객체, 호이스팅) 본문
실행 컨텍스트(Execution Context)
: 자바스크립트의 실행 컨텍스트는 실행할 코드에 제공할 환경정보들을 모아놓은 객체
자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 크게 3가지의 일을 함.
- 선언된 변수를 위로 끌어올림: 호이스팅(Hoisting)
- 외부 환경 정보 구성(lexical environment)
- this 값 설정
--> 이런 특징들이 JS가 다른 언어와 다르다는 것을 보여줌.
- 호이스팅: 자바스크립트만의 독특한 개념으로, 다른 언어에서는 일반적으로 지원되지 X
- 외부 환경 정보 구성: 자바스크립트와 비슷한 방식으로 파이썬, Java(람다식), 일부 스크립트 언어들도 클로저 및 렉시컬 스코프를 지원함
- this 설정: 자바스크립트는 매우 유연하게 this를 설정하는 반면, 다른 객체지향 언어들(Java, C++, Python)은 보다 고정적이고 명확한 규칙을 따름
Stack(Last in First out: 마지막으로 들어온게 제일 먼저 나감)
vs queue(First in First out: 먼저들어온게 먼저 나감)
- 자바스크립트는 싱글 스레드로 동작하기 때문에 한 번에 하나의 실행 컨텍스트만 활성화될 수 있습니다. 이는 실행 스택(콜 스택)에서 관리되며, 현재 실행 중인 함수의 실행 컨텍스트가 스택의 최상단에 있음.
콜 스택(call stack) : 자바스크립트 코드가 실행되며 생성되는 실행 컨텍스트(Execution Context)를 저장하는 자료구조
즉, 동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 스택의 한 종류인 콜스택에 이를 쌓아올림. 스택이기 때문에 코드의 순서를 보장할 수 있음.
- 컨텍스트의 구성
- 구성방법
- 전역공간
- eval()함수
- 함수(우리가 흔히 실행컨텍스트를 구성하는 방법)
- 구성방법
// ---- 1번
var a = 1;
function outer() {
function inner() {
console.log(a); //undefined
var a = 3;
}
inner(); // ---- 2번
console.log(a);
}
outer(); // ---- 3번
console.log(a);
코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료
특정 실행 컨텍스트가 생성되는(또는 활성화되는) 시점 = 콜 스택의 맨 위에 쌓이는(노출되는) 순간
곧, 현재 실행할 코드에 해당 실행 컨텍스트가 관여하게 되는 시점
<실행 컨텍스트에 담기는 정보>
- VariableEnvironment(VE)
- 현재 컨텍스트 내의 식별자 정보(=record)를 갖고있어요.
- var a = 3
- 위의 경우, var a를 의미
- 외부 환경 정보(=outer)를 갖고있어요.
- 선언 시점 LexicalEnvironment의 snapshot
- 현재 컨텍스트 내의 식별자 정보(=record)를 갖고있어요.
- LexicalEnvironment(LE)
- VariableEnvironment와 동일하지만, 변경사항을 실시간으로 반영해요.
- ThisBinding
- this 식별자가 바라봐야할 객체
- VE vs LE : 차이점은 오로지 Snapshot 여부!!!!!!!!!:
- VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다르다.
- VE : 스냅샷을 유지해요.
- LE : 스냅샷을 유지하지 않아요. 즉, 실시간으로 변경사항을 계속해서 반영합니다.
- VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다르다.
- 이 두가지는 담기는 항목은 완벽하게 동일해요. 그러나, 스냅샷 유지여부는 다음과 같이 달라요.
- 구성 요소(VE, LE 서로 같아요!)
- VE, LE모두 동일하며, ‘environmentRecord’와 ‘outerEnvironmentReference’로 구성
- environmentRecord(=record)
- 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장돼요.
- 함수에 지정된 매개변수 식별자, 함수자체, var로 선언된 변수 식별자 등
- outerEnvironmentReference(=outer)
Lexical Environment(VE와 동일하나 실시간으로 변경사항이 반영 되는...)
1. EnvironmentRecord(=record)
- 현재 컨텍스트와 관련된 코드의 식별자 정보가 수집됨.
- 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집함.
- 수집만하고 실행은 하지 않음!!!
Record의 수집과정: 호이스팅(Hoisting: 무언가를 끌어올리다)
- 변수 정보 수집 과정을 이해하기 쉽게 설명한 가상개념
- 변수 정보 수집을 모두 마쳤어도 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태임.
- JS엔진은 호이스팅으로 코드 실행 전 이미 모든 변수정보를 알고있음
<호이스팅 규칙(과정)>
- 매개변수 및 변수는 선언부를 호이스팅함.
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
function a (x) {
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
매개변수 호이스팅 적용 전...: 기존 생각대로 위에서부터 코드를 읽어나가면 예상 결과값은 1, undefined, 2이다.
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
매개변수 호이스팅 적용 후: a(1)의 1이 결국 함수 a에 가장 상단에 선언됨.
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var x;
var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
console.log(x);
}
a(1);
변수 호이스팅 적용: 호이스팅 적용을 생각하고 결과값을 출력하면 1, 1, 2이다.
즉, 예상한 결과값과 호이스팅이 실제로 적용된 후의 결과값은 다르다.
2. 함수 선언은 전체를 호이스팅함(즉, 함수 자체를 다 한번에 호이스팅함: 함수 선언식일때만!!)
//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
console.log(b);
var b = 'bbb';
console.log(b);
function b() { }
console.log(b);
}
a();
호이스팅 적용 전: 예상 결과값은 undfined, bbb, b 함수 이다.
//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var b; // 변수 선언부 호이스팅
function b() { } // 함수 선언은 전체를 호이스팅
console.log(b);
b = 'bbb'; // 변수의 할당부는 원래 자리에
console.log(b);
console.log(b);
}
a();
호이스팅 적용 후: 함수 선언문은 함수 전체가 다 호이스팅 됨으로, 실제 결과값은 함수b, bbb, bbb가 된다.
확실히 var를 쓰면 호이스팅에 의해 많은 혼란과 여러 문제점들을 초래할 수 도 있을 것 같다. 그래서 많은 개발자들이 다양한 경험 끝에 var 사용을 지양하고 대신 let과 const 사용을 추천하는 것이다.
그리고 협업을 많이 하고, 복잡한 코드 일수록, 전역 공간에서 이루어지는 코드 협업에서는 되도록 함수 표현식을 활용해야한다!!!
왜일까??
이것도 호이스팅과 관련되어있다....
// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
function a () { /* ... */ }
a(); // 실행 ok
// 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
// (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
var b = function () { /* ... */ }
b(); // 실행 ok
// (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
// d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
var c = function d () { /* ... */ }
c(); // 실행 ok
d(); // 에러!
자바스크립트에서는 3가지 함수 정의 방식이 있음. 선언, 표현, 기명함수...
여기서 함수 표현식 사용을 적극 권장하는 이유는
함수가 할당된 변수는 하나의 변수로 취급하기 때문에 함수 선언문과 달리 함수 자체가 호이스팅 되는 것이 아니라
그 할당된 변수의 식별자만 우선 호이스팅 된다.
즉, 다시 말하자면:: 변수는 호이스팅되지만, 함수의 할당 자체는 호이스팅되지 않기 때문에
위에서 봤던 함수 선언문에서의 함수 전체가 호이스팅 되는 것을
피할 수 있다. 고로 호이스팅 관련 문제를 초래하지 않는다!!!!!
왜 이부분이 중요한가? 예를 들어
...
console.log(sum(3, 4));
// 함수 선언문으로 짠 코드
// 100번째 줄 : 시니어 개발자 코드(활용하는 곳 -> 200군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
return x + y;
}
...
...
var a = sum(1, 2);
...
// 함수 선언문으로 짠 코드
// 5000번째 줄 : 신입이 개발자 코드(활용하는 곳 -> 10군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
return x + ' + ' + y + ' = ' + (x + y);
}
...
var c = sum(1, 2);
console.log(c);
어떤 개발자가 sum이라는 함수를 이미 선언했고, 나중에 다른 개발자가 위의 sum 함수를 인지하지 못하고 이어서 코드를 짜면서 같은 이름의 함수를 선언한다고 가정하자... 그러면, 나중에 작성한 sum 함수 자체가 위로 끌어올려지는 호이스팅에 의해 기존에 sum 함수를 이용했던 모든 요소의 값이 변경될 수 있다.
하지만, 나중에 코딩을 짠 개발자가 이를 인지하고, 함수 표현식을 썼더라면 위의 재난은 막을 수 있었을 것이다.
...
console.log(sum(3, 4));
// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
return x + y;
}
...
...
var a = sum(1, 2);
...
// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
return x + ' + ' + y + ' = ' + (x + y);
}
...
var c = sum(1, 2);
console.log(c);
2. OuterEnvironmentReference(=outer) - 스코프, 스코프 체인
- "스코프": 식별자에 대한 유효 범위를 의미
- OuterEnvironmentReference(=Outer): 스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)
- 외부 환경 참조 정보: 이는 함수가 자신의 실행 컨텍스트 내에서 필요한 변수를 찾을 때 상위 스코프(외부 스코프)로 접근할 수 있도록 연결된 구조를 뜻함(즉, 스코프 체인이 가능케하는 것). 쉽게 말해, 함수가 정의된 위치에 따라 외부 스코프를 참조할 수 있는 메커니즘을 제공함
- 실행 컨텍스트 관점에서의 스코프:
- "스코프 체인": 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색
- outer는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조
- 예를 들어, A함수 내부에 B함수 선언 → B함수 내부에 C함수 선언(Linked List)한 경우
- "스코프 체인": 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색
- 항상 outer는 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능
- 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능
예시: CALL STACK에 들어오는 실행 컨택스트와 OUTER 영향
최종정리:
각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.
'프론트엔드 부트캠프' 카테고리의 다른 글
[Github 특강] Github 이용해서 협업 과정 + 팁(dev) (2) | 2024.10.17 |
---|---|
[JS 강의 2회차] 객체&배열 의 데이터 저장, 수정, 삭제, 복사 정리 (2) | 2024.10.17 |
[알고리즘 특강(4)] (1) | 2024.10.15 |
[3주차 JS 기초 문법] 데이터 타입, 메모리, 변수 선언과 데이터 할당, 얕은 복사와 깊은 복사, null/ undefined (1) | 2024.10.15 |
[JS 1강] <var, let, const 의 차이> <&&, || 연산자> <if, else 와 switch 문으로 조건문> <for문으로 반복문> (0) | 2024.10.14 |