실행 컨텍스트 #3
Replies: 6 comments 3 replies
-
Activation Record함수 호출의 특정 인스턴스에 대한 중요한 상태 정보를 포함하는 데이터 구조(Data Structure)이다. 전적으로 메모리에 존재하거나 부분적으로 레지스터에 저장될 수 있다. 일반적으로 Activation Record는 다음이 포함된다.
대부분의 프로그래밍 언어에서 Activation Record는 엄격한 LIFO(LastInFirstOut) 규율을 유지 하므로 스택에 저장된다. 이러한 이유에서 Activation Record는 일반적으로 Stack Frame이라고도 한다. 일반적으로 C에서는 이러한 목적으로 스택을 항상 사용하므로 Stack Frame이 정확한 용어인데 다른 언어에서는 스택을 사용하지 않기도 하기 때문에 Activation Record란 말이 생겼다. 다음과 같은 LIFO 위반 경우가 없으면 잘 작동한다.
호출자의 Activation Record에 대한 포인터(DynamicChain)
Function PrologStack Frame을 사용하는 경우 함수는 항상 Frame Pointer를 셋업하는 코드 구문으로 시작하게 된다. 일반적인 구문은 아래와 같다. PUSH EBP ; caller의 Stack Frame Pointer를 백업한다.
MOV EBP, ESP ; ESP 값을 EBP에 복사함으로써 Stack Frame Pointer를 셋업한다.
SUB ESP, n ; 할당된 지역변수를 위한 공간을 ESP의 위치를 옮기면서 확보한다.
Function EpliogStack Frame을 사용하는 경우 함수의 끝부분에는 Frame Pointer를 원래대로 복원하는 코드가 오게된다.
Call StackCall Stack은 임시 메모리를 차지하는 Stack Frame을 만든다. 즉, Call Stack의 각 단계가 Stack Frame이다. Call Stack이 단일이기 떄문에 함수 실행이 위에서 아래로 한번에 하나씩 수행된다. 즉, 호출 스택은 동기적이다. 한번 더 적어본다. Call Stack은 무엇인가? 가장 기본적인 수준에서 호출 스택은 LIFO 원칙을 사용하여 함수 호출을 임시로 저장하고 관리하는 데이터 구조이다. 다음 코드를 보면 이해하기 쉬울 것이다.
Manage function invocation (call)
StackOverflow를 일으켜보자. 중간 Summary
그래서 this에 대한 정보가 Activation Record에 기록된다는데 어떻게 기록되는데?Stack Frame 구현을 위한 함수로 Stack Frame에 어떤게 들어가는지 세부 정보가 안나와있다. 위에서 알아본 Activation Record의 개념들로 미뤄 봤을때 다음과 같이 동작할 것이다. function foo() { var a = 1; }
function bar() { var b = 2; foo(); }
bar();
start of the stack
---------------------------
bar local frame
* a couple of OS pointers
* local variable b (2)
* place for returned value
* pointer to next frame
---------------------------
foo local frame
* a couple of OS pointers
* local variable a (1)
* place for returned value 찾았다.. 각각의 stack frame은 다음과 같은 정보를 갖고있다.
두번째 요소는 this의 생성자 필드의 함수 이름이 저장된다. V8에서 모든 생성자들은 생성자 함수에 이 값을 넣는다. 그래서 객체 생성뒤에 이 값이 변할 수 있고, 자신을 생성하게한 함수의 이름을 갖는다. 왠만하면 이 값은 유지된다. 객체가 [[Class]] 속성일 때는 사용 불가능하다. 이제 this의 동작이 이해가 된다. 함수 내부에 this가 있을때 object 내부에 있을때는 object를 가르키는데 일반 함수 안에서는 전역객체를 가르키는 이유가 애초에 object 생성의 유무로 재할당을 하기 때문이었다. 결론우리는 지금까지 Call Stack과 Activation Record(Stack Frame)에 대해 알아보았다. History
|
Beta Was this translation helpful? Give feedback.
-
LexicalEnvironment실행 컨텍스트는 블럭내 코드를 실행하기 위해 필요한 환경 정보를 모아놓은 객체이다. 실행 컨텍스트는 3가지로 구성되어있는데, 그 중 컨텍스트의 식별자 정보와 현재 블럭 외부의 환경 정보를 담당하고 있는
EnvironmentRecord
실행컨텍스트가 생성되는 시점 즉, 함수가 호출되어 코드가 실행되기 이전에 EnvironmentRecord를 정의하기 위해서 코드를 순차적으로 스캔하며 필요한 식별자 정보를 수집한다. 이 과정으로 인해 호이스팅이 발생하여 변수를 할당하기 이전에 참조가 가능하게 된다. Hoisting
JS는 코드를 실행하기 위해서 실행 컨텍스트를 우선적으로 생성한다. 실행 컨텍스트를 구성하는 EnvironmentRecord를 정의하는 과정에서 필요한 식별자 정보를 수집하게되고, 이로 인해 JS 엔진은 실제 코드가 실행되기 이전에 이미 필요한 식별자의 정보를 모두 알고 있게 된다. 선언보다 참조를 우선하였음에도 동작하는 것은 이러한 이유이다. 좀 더 자세히 그 과정을 쪼개서 살펴보자. 변수를 참조하기 이전까지 필요한 과정을 3단계로 나눌 수 있다.
식별자의 정보를 수집할 때, function a() {
console.log(x); //... 1
var x;
console.log(x); //... 2
var x = 2;
console.log(x); //... 3
}
a();
하지만 function a() {
var b;
console.log(b); // ...1
function b() {};
console.log(b); // ...2
b = 'bbb';
console.log(b); // ...3
}
정리하자면, 실행 컨텍스트가 생성될 때, 식별자 정보를 수집하기 때문에 호이스팅이 발생한다. 코드가 실행되기 이전에 함수 선언식과 표현식
두 코드 모두 함수를 정의한다는 것에는 동일하다. 하지만 앞에서 정리하였듯, 호이스팅을 통해 함수가 선언이전에 호출될 수 있느냐에 대한 차이이다. 함수 표현식을 사용하여 호이스팅을 통해 발생할 수 있는 문제를 회피하라는 데에는 이유가 있다. 예제로 살펴보면, console.log(sum(3,4)); // ... 1
function sum(a, b) {
return a + b;
}
// ... 백만줄
function sum(a, b) {
return `${a} + ${b} = ${a + b} 입니다.`;
} 맨 위의 1번 코드의 결과는 상위 게다가 ES6를 통해 도입된 3가지 방법으로 정의된 변수에 대해서는 식별자 정보를 수집하는 과정에서 변수들의 선언 과정만 수행된다. 그래서 값이 재할당되기 이전까지는 해당 변수를 참조할 수 없다. 그래서 선언 - 재할당 까지의 영역을 **TDZ(temporal dead zone)**라고 한다. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
스코프, 스코프 체인, outerEnvironmentReference스코프란 식별자에 대한 유효범위 입니다.
A라는 영역에서 선언한 변수는 A뿐만 아니라 A의 내부에서도 접근이 가능하지만 A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근이 가능합니다. 식별자의 유효범위인 스코프를 안에서부터 바깥으로 차례로 검색해나가고, 이를 스코프 체인
스코프 체인
A 함수 내부에 B 함수를 선언하고 다시 B 함수 내부에 C 함수를 선언한 경우
이렇게 outerEnvironmentReference는 연결리스트의 형태를 가지고 있습니다. 이런 구조적 특성 덕분에 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능합니다. 예제를 통해 스코프 체인을 살펴봅시다. var a = 1; // 1번째 줄
var outer = function () { // 2번째 줄
var inner = function () { // 3번째 줄
console.log(a); // 4번째 줄
var a = 3; // 5번째 줄
}; // 6번째 줄
inner(); // 7번째 줄
console.log(a); // 8번째 줄
}; // 9번째 줄
outer(); // 10번째 줄
console.log(a); // 11번째 줄
전역변수와 지역변수전역 변수 : 전역 공간에서 선언한 변수 지역 변수 : 함수 내부에서 선언한 변수 전역 공간은 덮어쓰여질 가능성이 항상 존재하므로 코드의 안정성을 위해 가급적 전역변수 사용을 최소화하는 것이 좋습니다. 렉시컬 스코프에 대해서는 추후에 5장 클로저에서 다룰 예정이므로 패스 |
Beta Was this translation helpful? Give feedback.
-
공유하고싶은 자료 링크 |
Beta Was this translation helpful? Give feedback.
-
2-3-1 environmentRecord와 호이스팅environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언 함수 자체, var로 선언된 변수의 식별자 등의 식별자 정보들이 저장되며, 컨텍스트 내부 전체를 line by line 훑어나가며 순서대로 수집한다. 변수가 수집되었더라도, 실행 컨텍스트가 관여할 코드들은 실행되지 전의 상태일 때, 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명(식별자)들을 모두 알고 있는 상태가 되게 된다. 이런 경우 ‘자바스크립트 엔진은 식별자들을 최상단으로 끌어올린 후 실제 코드를 실행한다.’라고 생각해도 코드를 해석하는데 문제가 없게 되며, 여기서 끌어올린다라는 뜻을 가진 호이스팅(hoisting)이라는 개념이 등장하게 된다. 실제로 끌어올리는 것은 아니지만, 편의상 끌어올린 것으로 간주할 수 있다는 것이다. 호이스팅 규칙 environmentRecord에는 매개변수의 이름, 함수 선언, 변수명 등이 담긴다. function 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) 1, (2) undefined, (3) 2로 출력되리라 예상하겠지만, 실제로는 (1) 1, (2) 1, (3) 2 로 출력되게 된다. 호이스팅때문에 실제로 코드는 아래와 같이 변수 x가 끌어올려진 것 처럼 동작하기 때문이다. function a () {
var x; // 수집 대상 1의 변수 선언 부분
var x; // 수집 대상 2의 변수 선언 부분
var x; // 수집 대상 3의 변수 선언 부분
x = 1; // 수집 대상 1의 할당 부분
console.log(x); // (1)
console.log(x); // (2)
x = 2; // 수집 대상 3의 할당 부분
console.log(x); // (3)
}
a(1); 함수 선언문과 함수 표현식 호이스팅을 다루면서 같이 알아두면 좋은 내용으로, 함수 선언문(function declaration)과 함수 표현식(function expression)이 있다. 둘 모두 함수를 새롭게 정의할 때 사용하는데, 그 중 함수 선언문은 function 정의부만 존재하고 별도의 할당 명령이 없는 것을 의미하고, 반대로 함수 표현식은 정의한 function을 별도의 변수에 할당하는 것을 말한다. 함수 표현식은 또, 함수명 정의 여부에 따라 함수명이 정의된 함수 표현식을 ‘기명 함수 표현식’ 그렇지 않는 함수 표현식을 ‘익명 함수 표현식’이라고 한다. 용도에 있어서도 차이가 있지만, 이 둘은 호이스팅 방식에서 차이가 있다. console.log(sum(1, 2)); // 3
console.log(multiply(3, 4)); // error: multiply is not a function
function sum (a, b) { // 함수 선언문 sum
return a + b;
}
var multiply = function (a, b) { // 함수 표현식 multiply
return a * b;
} 결론적으로 함수 선언문인 sum은 전체가 호이스팅되고, 함수 표현식 multiply는 변수 이므로 선언부만 호이스팅되게 된다. 따라서 위와 같은 코드에서 sum은 정상 동작하게 되지만, multiply은 선언 이전에 호출할 경우 에러가 발생하게 된다. |
Beta Was this translation helpful? Give feedback.
-
3주차 주제는 실행 컨텍스트입니다. 아래 키워드 중 학습을 원하는 키워드를 선정하여 작성해주세요.
Beta Was this translation helpful? Give feedback.
All reactions