context

남윤하·2024년 2월 10일

JS

목록 보기
9/9
post-thumbnail

실행컨텍스트

(참고: https://velog.io/@yunn75151/Execution-context)

  • 자바스크립트의 실행컨텍스트는 실행할 코드에 제공할 환경정보들을 모아놓은 객체이다.
  • 자바스크립트는 실행 컨텍스트가 활성화되는 시점에 다음의 일을 한다.
  1. 선언된 변수를 위로 끌어올린다 => 호이스팅
  2. 외부 환경 정보를 구성한다.
  3. this 값을 설정한다

🎈 콜 스택 (call stack)

  • 자바스크립트가 함수 실행을 다루는 방법 중 하나이다
  • 자바스크립트가 실행해야 하는 함수를 보면 stack위에 올린다.
  • call stack도 그렇게 쌓은 stack인 것이다.
  • 자바스크립트는 함수를 스택 위에 올리고, 함수를 다 실행하면 제거한다.
  • 스택을 다 처리하면, 실행시킬 것이 없어진다.

function three(){
    console.log("i love js");
}
function two(){
    three();
}
function one(){
    two();
}
function zero(){
    one();
}
zero(); // i love js
  • 사용자도구를 통해서 알아보자
  • 처음에 어떤 것도 실행이 되지않은 상태라면 anonymous라는 함수가 실행되어 Call Stack안에 들어가 있다.

✨zero 실행

✨one 실행

✨two 실행

✨three실행

  • 이것들이 전부 자바스크립트가 실행해야 하는 함수인 것이다.
    ✨three 마무리

  • 다 실행 되고 나면 가장 마지막으로 실행되었던 three부터 사라진다.
    (함수는 return되거나 실행이 마무리 되면 끝난다.)

  • 이렇게 전부 마무리되면 Call Stack에 쌓여있던 것들이 사라지게 된다.

  • 이로써 Call Stack에 쌓여있던 것들이 전부 사라지면 콜스택에서 불러올 것이 없다.

  • 결국은 특정 실행 컨텍스트가 생성되는(또는 활성화되는) 시점이 콜 스택의 맨 위에 쌓이는(노출되는) 순간을 의미하며. 곧, 현재 실행할 코드에 해당 실행 컨텍스트가 관여하게 되는 시점을 의미한다고 받아들이면 된다!

  • Call Stack을 더 이해하기 위해 Error를 추가해보자

  • zero함수안에서 one이 실행되고 나면 error가 발생하도록 했다.

    이것이 바로 콜스텍이다. 이런 순서대로 함수가 진행이된다.

  • 에러 전에 있던 콜스택 또한 확인이 가능하다

  • three 함수 내부에 에러메시지를 넣었을 때, 여기서 에러가 발생되면 그전 콜스택들이 확인 가능해진다.

  • 이런 식으로 에러전에 있던 모든 콜스택을 확인할 수 있다.
    (함수3에서 에러발생 > 그것은 two에서 실행 > 그것은 one,zero 에서 실행 > zero는 index.js에 14번째 line에서 실행)

✨ 정리

  • 리스트(콜스택)가 존재하며, 함수가 실행될 때, 리스트에 추가가 된다. 실행이 완료되면 함수는 리스트(콜스택)에서 제거된다.
  • 자바스크립트의 to do list인 것이다.

🎈 VariableEnvironment, LexicalEnvironment

  1. VariableEnvironment
    • 현재 컨텍스트 내의 식별자 정보(=record)를 가지고 있다
      - ex) var a = 3 ( var a를 의미 )
    • 외부 환경 정보(=outer)를 갖고있다.
    • 선언 시점 LexicalEnvironment의 snapshot
  2. LexicalEnvironment
    • VariableEnvironment와 동일하지만, 변경사항을 실시간으로 반영한다.
    • 실행 컨택스트가 최초에 생길 때에는 VE와 LE가 완전히 같지만,
      시간이 갈수록 LE는 실시간으로 반영하면서 업데이트가 되고, VE는 생길 때 모습을 그대로 간직한다. (이것을 스냅샷이라고 한다.)
    • 따라서 실시간으로 반영되는 LE를 써야 한다.
  3. ThisBinding
    • this 식별자가 바라봐야할 객체
    • this가 function안에서 어떤 기능을 할 지 정해주는 것이 This Binding이다.

🤖 VE vs LE

이 두가지는 완벽하게 동일하지만, 스냅샷 유지여부는 다름.

  • VE는 유지하지만, LE는 실시간으로 변경사항을 계속해서 반영한다.
  • 결국, 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용한다.

😁 구성 요소(VE, LE 서로 같다)

  1. VE, LE모두 동일하며, ‘environmentRecord’와 ‘outerEnvironmentReference’로 구성
  2. environmentRecord(=record)
    1. 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
    2. 함수에 지정된 매개변수 식별자, 함수자체, var로 선언된 변수 식별자 등
  3. outerEnvironmentReference(=outer)
  • 일반적으로 프로그래밍 언어나 실행 환경에서 발생하는 개념 중 하나이다. 이 용어는 주로 클로저(Closure)나 스코프(Scope)와 관련이 있다.
  • 클로저는 함수가 자신이 정의된 스코프 외부의 변수에 접근할 수 있는 기능을 나타낸다.
  • 이때 "outerEnvironmentReference(=outer)"는 해당 함수가 참조하는 외부 환경(스코프)를 나타낸다. (이것이 클로저의 동작을 이해하는 데 중요하다.)
function outer() {
  let outerVariable = 10;

  function inner() {
    // inner 함수 내에서 outer 함수의 변수에 접근
    console.log(outerVariable);
  }

  // outer 함수의 내부에서 inner 함수를 호출
  inner();
}

// outer 함수 호출
outer();
  • 위의 코드에서 inner 함수는 outer 함수 내에 정의되어 있다. 그리고 outer 함수는 inner 함수를 반환하고 있다.
  • 이때 inner 함수는 outer 함수의 스코프에 있는 outerVariable에 접근할 수 있다.
  • 이때 "outerEnvironmentReference(=outer)"는 inner 함수가 자신을 둘러싼 외부 환경, 즉 outer 함수의 스코프를 가리킨다.

🎈 LexicalEnvironment(1) - environmentRocord(=record)와 호이스팅

  • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장(수집)된다. = 기록된다
  • 수집 대상 정보: 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수 식별자 등.
  • 컨택스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집한다.
    (수집이되지만 실행이 되는 것은 아니다)

🎞 호이스팅

  • 식별자 정보를 수집할 때, 호이스팅을 하면 식별자 정보만 위로 다 끌어올린다.
    -> 이것이 레코드를 수집하는 과정이다.

🧨 호이스팅 법칙 1

  • 매개변수 및 변수는 선언부를 호이스팅 한다.

🎭 호이스팅 적용 전

function a () {
	var x = 1;
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);

1, undefined, 2로 예상

🎭 호이스팅 적용 후

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

  • 함수 선언은 전체를 호이스팅한다.
    🎭 호이스팅 적용 전
function a () {
	console.log(b);
	var b = 'bbb';
	console.log(b);
	function b() { }
	console.log(b);
}
a();

에러(또는 undefined), ‘bbb’, b함수로 출력예상이지만,

🎭 호이스팅 적용 후

function a () {
	var b; // 변수 선언부 호이스팅
	function b() { } // 함수 선언은 전체를 호이스팅

	console.log(b);
	b = 'bbb'; // 변수의 할당부는 원래 자리에

	console.log(b);
	console.log(b);
}
a();

실제 결과값은 b함수, ‘bbb’, ‘bbb’ 이다

개념 정리

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
그 객체 안에는 3가지가 존재한다.
( VariableEnvironment, LexicalEnvironment, ThisBindings )

  • VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다르다.
    LE는 다음 2가지 정보를 가지고 있다.
    ✓ record(=environmentRecord) ← 이 record의 수집과정이 hoisting
    ✓ outer(=outerEnvironmentReference): 함수를 둘러쌓고 있는 외부환경

🧨 함수 선언문과 표현식

🎭 호이스팅 적용 전

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;
}

LE는 record와 outer를 수집한다. 그 중, record를 수집하는 과정에서 hoisting이 일어나고, 우리가 익히 알고있는대로 위로 쭉 끌어올려본 결과를 다시 써보면 아래와 같다.

🎭 호이스팅 적용 후

// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

// 변수는 선언부만 hoisting

var multiply; 

console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) { // 변수의 할당부는 원래 자리
	return a + b;
};
  • 함수 선언문과 함수 표현식은 hoisting 과정에서 극명한 차이를 보인다.
  • 하지만 함수선언문을 사용할 때에는 많은 위험이 있고, 이는 예시를 통해서 확인해보겠다.

🤿 함수선언문

...

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);
  • 함수선언문을 이용했을 때, 더 윗줄(먼저 쓴 선언문)의 코드가 호이스팅이 되고, 최신의 함수 선언문을 작성했을 때에, 윗줄에 있는 선언문으로 작용하던 코드들이 호이스팅을 통해서 최신의 함수 선언문의 영향을 받게된다.

🤿 함수표현식

...

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);
  • 함수표현식을 작성한다면, 선언부만 호이스팅이 되며, 할당 값들은 그자리에 있기 때문에 서로의 작용을 주던 코드들에게 영향을 미치지 않는다.

협업을 많이 하고, 복잡한 코드일 수록. 전역 공간에서 이루어지는 코드 협업일 수록 함수 표현식을 활용하는 습관을 들이도록 하는것이 좋다

🎈 LexicalEnvironment(2) - 스코프, 스코프 체인, outerEnvironmentReference(=outer)

🧣 outerEnvironmentReference(이하 outer)

  • 스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)이며, 외부 환경의 참조정보.

🧣 스코프 체인

  1. outer는 현재 호출된 함수가 선언될 당시(이 말이 중요해요!)의 LexicalEnvironment를 참조.
    참조한다는 말이 어려우면, 그 당시의 환경 정보를 저장한다. 정보로 이해하면 된다.
  2. 예를 들어, A함수 내부에 B함수 선언 → B함수 내부에 C함수 선언(Linked List)한 경우.
  3. 결국 타고, 타고 올라가다 보면 전역 컨텍스트의 LexicalEnvironment를 참조하게 된다.
  4. 항상 outer는 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능
  5. 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능
// 아래 코드를 여러분이 직접 call stack을 그려가며 scope 관점에서 변수에 접근해보세요!
// 어려우신 분들은 강의를 한번 더 돌려보시기를 권장드려요 :)
var a = 1;
var outer = function() {
	var inner = function() {
		console.log(a); // 이 값은 뭐가 나올지 예상해보세요! 이유는 뭐죠? scope 관점에서!
		var a = 3;
	};
	inner();
	console.log(a); // 이 값은 또 뭐가 나올까요? 이유는요? scope 관점에서!
};
outer();
console.log(a); // 이 값은 뭐가 나올까요? 마찬가지로 이유도!

각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다

profile
개발 일지 블로그

0개의 댓글