connecting dots

자바스크립트의 실행 컨텍스트(Execution Context)와 this 키워드, ES6 모듈 특징 본문

TIL

자바스크립트의 실행 컨텍스트(Execution Context)와 this 키워드, ES6 모듈 특징

dearsuhyun 2024. 8. 21. 18:56

실행 컨텍스트 (Execution Context) - Record와 Outer를 중심으로

record/outer는 실행컨텍스트와 크게 관련 없음

lexical 은 실행 컨텍스트보다 상위개념이라고 볼 수 있음

설명이 어렵게 되어 있음 - 전반적으로 더 읽기 좋게 풀어서 설명해보기 (어렵게 썼는데 틀리면 ? 신뢰도 하락)

독자 입장에서 글의 흐름이 뭉쳐있다, 쉽게 풀고 더 흐름을 자연스럽게 고쳐보기

 

실행 컨텍스트는 실행 가능한 코드에 제공할 환경(코드 실행에 영향을 주는 조건이나 상태) 정보를 모아놓은 객체입니다.
해당 객체에는 변수 객체, 스코프 체인, this 정보가 담겨있습니다.
자동으로 전역 컨텍스트가 생성된 후 함수 호출시마다 함수 컨텍스트가 생성되고, 컨텍스트 생성이 완료된 후에 함수가 실행됩니다.
함수 실행 중에 사용 되는 변수들을 변수 객체 안에서 값을 찾고 값이 존재하지 않는다면 Lexical 환경의 outerEnvironmentReference를 통해 Scope 체인을 따라 올라가면서 탐색합니다. 함수 실행이 마무리가 되면 해당 컨텍스트는 사라지고, 페이지가 종료되면 전역 컨텍스트도 사라집니다.

 

 

1. 전역 실행 컨텍스트 (Global Execution Context):

자바스크립트 프로그램이 시작될 때 생성됩니다.

전역 컨텍스트에서 this는 전역 객체(window 객체 또는 Node.js 환경에서는 global 객체)를 참조합니다.

 

2. 함수 실행 컨텍스트 (Function Execution Context):

함수가 호출될 때마다 새로운 실행 컨텍스트가 생성됩니다.

함수 내부에서의 this는 함수가 어떻게 호출되었는지에 따라 달라집니다.

 

3가지 주요 구성 요소

1) 변수 객체(Variable Object): 현재 실행 중인 함수의 매개변수, 변수, 함수 선언 등을 포함합니다.

2) 스코프 체인(Scope Chain): 현재 컨텍스트에서 접근할 수 있는 변수들을 결정합니다.

3) this 바인딩: this가 가리키는 객체를 결정합니다.

 

var x = 'xxx';

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  
  bar();
}

foo();

 

자바스크립트 코드를 실행 --> 콜스택에 전역 실행 컨텍스트를 담음 --> 전역에서 함수 foo를 호출하면 --> foo의 실행 컨텍스트를 만들어서 콜스택에 담음 (콜스택에서는 가장 최근에 추가된 콜스택만 활성화됨) -> 함수 bar를 호출하면 --> bar의 실행 컨텍스트를 만들어서 콜 스택에 담음 --> 가장 최근에 추가된 bar 부터 실행 --> bar 콜스택에서 삭제 --> foo 실행 --> foo 콜스택에서 삭제 --> 전역 실행

출처: https://velog.io/@ggong/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-execution-context

 

1. Record로 호이스팅 이해하기

틀렸다고 볼 수 없지만 어렵게 설명되어 있음

 

환경 레코드(Environment Record)란?

환경 레코드(Environment Record)는 자바스크립트에서 변수와 함수가 어디에 저장되고 어떻게 관리되는지를 담당하는 구조입니다. 쉽게 말하면, 코드에서 사용하는 변수와 함수들을 저장하는 “메모장” 같은 역할을 한다고 생각하면 됩니다. --> 메모리

이 메모장에는 각 변수나 함수의 이름(식별자)과 그에 연결된 값이 기록됩니다.

 

환경 레코드의 역할

- 변수와 함수의 저장소:

자바스크립트에서 변수를 선언하거나 함수를 정의할 때, 그것들은 바로 환경 레코드에 저장됩니다.

예를 들어, let x = 5; 라는 코드를 실행하면, 환경 레코드에 x라는 이름과 5라는 값이 저장됩니다.

 

- 식별자와 바인딩된 값:

식별자는 변수나 함수의 이름을 의미합니다. 이 식별자에 연결된 값(바인딩된 값)이 무엇인지 환경 레코드가 기억합니다.

예를 들어, function sayHello() {} 라는 코드를 실행하면, 환경 레코드는 sayHello라는 식별자와 이 함수가 가리키는 코드를 기억합니다.

 

- 코드 실행 중 값 조회:

코드가 실행될 때, 자바스크립트 엔진은 변수나 함수의 값을 필요로 하면 환경 레코드를 참조합니다.

예를 들어, console.log(x); 라는 코드가 실행될 때, 환경 레코드는 x가 어떤 값(이 경우 5)을 가지고 있는지 알려줍니다.

 

환경 레코드와 호이스팅의 관계

환경 레코드의 생성

자바스크립트가 코드를 실행할 때, 먼저 실행 컨텍스트를 생성합니다. 그리고 이 실행 컨텍스트 내에서 환경 레코드가 만들어집니다. 이 과정은 두 단계로 이루어집니다:

1단계: 생성 단계 - 변수와 함수의 선언이 환경 레코드에 기록됩니다.

2단계: 실행 단계 - 실제 코드가 실행되고, 변수와 함수에 값이 할당됩니다.

 

호이스팅의 실제 동작

호이스팅은 생성 단계에서 일어납니다. 자바스크립트는 코드를 읽으면서 변수와 함수 선언을 발견하면, 그것들을 환경 레코드에 미리 기록해 둡니다. 그래서 실제 코드가 실행될 때, 선언 전에 변수나 함수를 참조해도 오류가 발생하지 않는 것입니다.

 

 

1) var 키워드와 변수 호이스팅

var로 선언된 변수는 코드의 최상위로 호이스팅되며, 초기화와 선언이 동시에 이루어집니다. 하지만 중요한 점은, 호이스팅될 때 변수가 undefined로 초기화된다는 것입니다. 즉, 변수를 실제로 할당하기 전에 접근해도 오류가 발생하지 않고 undefined를 반환합니다.

console.log(a); // undefined
var a = 10;
console.log(a); // 10
실행 과정
1. 생성 단계:
자바스크립트 엔진은 var a를 발견하고, 이를 환경 레코드에 undefined 값과 함께 등록합니다.

2. 실행 단계:
console.log(a);를 실행할 때, a는 이미 환경 레코드에 존재하며 undefined로 초기화되어 있습니다. 따라서 undefined가 출력됩니다.
이후 a = 10;이 실행되면, a의 값이 10으로 바뀝니다.

 

2) let과 const 키워드와 변수 호이스팅

letconst로 선언된 변수도 호이스팅됩니다. 하지만 중요한 차이점은, 이들 변수는 호이스팅되지만 초기화되지 않습니다.

호이스팅 (끌어올려지는 것 + 초기화되는것) --> 자바스크립트의 개발용어로 이해하면

let, const는 호이스팅된다고 말할 수 없음 !

 

 

이 변수들은 **“Temporal Dead Zone (TDZ)”**에 놓이게 됩니다. 이 때문에, 선언되기 전에 해당 변수에 접근하면 ReferenceError가 발생합니다.

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
console.log(b); // 20

console.log(c); // ReferenceError: Cannot access 'c' before initialization
const c = 30;
console.log(c); // 30

 

실행 과정
1. 생성 단계:
자바스크립트 엔진은 let과 const로 선언된 변수들을 환경 레코드에 등록하지만, 초기화는 하지 않습니다. 이때 이 변수들은 TDZ에 놓여 있습니다.
TDZ란 변수가 초기화되기 전까지 해당 변수를 참조할 수 없는 기간을 의미합니다.

2. 실행 단계:
console.log(b); 또는 console.log(c);가 실행될 때, 변수 b와 c는 TDZ에 있기 때문에 ReferenceError가 발생합니다.
이후 변수들이 선언되면서 TDZ가 종료되고, 변수들이 초기화됩니다. b = 20;, c = 30;이 실행되면, 이제 b와 c는 정상적으로 값을 가집니다.

 

2. Outer로 스코프체이닝 이해하기

 

1. 스코프 체이닝(Scope Chaining)

스코프 체이닝은 현재 스코프에서 변수를 찾지 못하면, 외부(상위) 스코프에서 계속해서 변수를 찾는 과정입니다. 자바스크립트는 변수를 사용할 때, 먼저 현재 스코프에서 그 변수를 찾습니다. 만약 찾지 못하면, 바로 외부 스코프(상위 스코프)로 이동해 다시 찾습니다. 이 과정을 스코프 체인이라고 합니다.

 

2. Lexical Environment (렉시컬 환경)

Lexical Environment는 자바스크립트에서 변수와 함수 선언을 기록하고 관리하는 구조를 말합니다. 쉽게 말하면, 변수와 함수들이 어디에 저장되고, 어떻게 접근되는지를 결정하는 “상자” 같은 역할을 합니다.

 

3. Outer Environment Reference

Outer Environment Reference는 현재 함수(또는 블록)가 자신을 둘러싼 외부 스코프를 참조할 수 있도록 만들어진 연결 고리입니다.

(현재 Lexical Environment에서 바깥쪽(외부)의 Lexical Environment를 참조할 수 있는 링크)

즉, 현재 코드가 바깥에서 선언된 변수나 함수에 접근할 수 있도록 해주는 “다리”라고 생각하면 됩니다.

 

4. Lexical Environment와 Outer Environment Reference의 관계

Lexical Environment는 각 실행 컨텍스트(예: 전역, 함수)에서 사용되는 변수와 함수들을 저장하고 관리합니다.

Lexical EnvironmentOuter Environment Reference를 통해 바로 바깥쪽의 Lexical Environment와 연결됩니다. 이 연결을 통해 자바스크립트는 변수를 현재 스코프에서 찾지 못하면, 바깥쪽 스코프에서 찾을 수 있게 됩니다.

 

스코프 체인

Lexical Environment들은 서로 Outer Environment Reference로 연결되어 스코프 체인을 형성합니다.

자바스크립트는 변수를 사용할 때, 먼저 현재 Lexical Environment에서 찾고, 없으면 Outer Environment Reference를 따라 바깥쪽 스코프에서 변수를 찾습니다.

 

쉽게 비유
Lexical Environment: 각 방 안에 있는 “변수와 함수들이 기록된 상자”
Outer Environment Reference: 각 방을 연결하는 “복도”
이 복도를 따라가면서 다른 방의 상자(즉, 외부 스코프에 있는 변수들)에 접근할 수 있습니다.

 

 

3. 스코프 체이닝과 Outer Environment Reference의 연결

let globalVar = "I'm a global variable";

function outerFunction() {
  let outerVar = "I'm in outerFunction";

  function innerFunction() {
    let innerVar = "I'm in innerFunction";

    console.log(globalVar); // 스코프 체인을 따라 전역 스코프에서 찾음
    console.log(outerVar);  // 스코프 체인을 따라 outerFunction 스코프에서 찾음
    console.log(innerVar);  // 현재 스코프에서 찾음
  }

  innerFunction();
}

outerFunction();

 

스코프 체이닝의 동작:

innerFunction 내부에서 globalVar, outerVar, innerVar를 출력하려고 할 때:

globalVarinnerFunction 스코프에서 찾을 수 없으므로, Outer Environment Reference를 통해 상위 스코프인 outerFunction으로 이동합니다. 거기서도 찾을 수 없으므로 다시 전역 스코프로 이동해 찾습니다.

outerVarinnerFunction 스코프에 없지만, 바로 외부 스코프인 outerFunction 스코프에 존재합니다. 그래서 여기서 변수를 찾습니다.

innerVarinnerFunction 스코프에 바로 존재하므로, 스코프 체인을 따라 올라가지 않고 현재 스코프에서 찾습니다.

 

 

this 키워드란?

this는 자바스크립트에서 현재 실행 중인 코드의 실행 컨텍스트에 따라 참조하는 객체를 의미합니다. this는 코드가 실행되는 방식에 따라 달라집니다.

 

1. 전역 컨텍스트에서의 this:

 전역 컨텍스트에서 this는 전역 객체를 참조합니다.

 브라우저 환경에서는 this === window가 참입니다.

console.log(this); // window 객체를 출력 (브라우저에서)

 

2. 일반함수에서의 this

호출 위치에서 this가 정의 !

const person = {
  name: "Alice",
  sayHello: function() {
    console.log("Hello, " + this.name);
  }
};

person.sayHello(); // "Hello, Alice"

 

** 추가 예제

// numbers1, numbers2는 같은 배열 데이터 생성 방식
const numbers1 = [5, 6, 7, 8] // 리터럴 방식
const numbers2 = new Array(5, 6, 7, 8) // 생성자 방식

Array.prototype.hello = function() {
  return this.join(' / ')
}
// [1, 2, 3].map()
// Array.prototype.map = function() {}
// join() 메서드는 배열의 모든 요소를 연결해 하나의 문자열로 만듦

// 배열 데이터에서 커스텀 메서드 사용
const res = numbers1.hello()
console.log(res) // 5 / 6 / 7 / 8

 

2) 객체의 메서드에서의 this:

객체의 메서드에서 this는 그 메서드가 속한 객체를 참조합니다. --> 알 수 없다 

밑의 예제를 보면 객체의 메서드에서 this는 그 메서드가 속한 객체를 참조합니다.

--> 참조한다는 것을 확신할 수 없음

--> 이 예제를 설명할 때는 맞지만, 일반화된 this 키워드 설명할 때에는 틀렸음

 

const obj = {
  name: 'Alice',
  greet() {
    console.log(this.name); // this는 obj를 참조
  }
};
obj.greet(); // "Alice" 출력

 

 

3) 생성자 함수에서의 this:

• 함수가 new 키워드와 함께 호출될 때(생성자 함수 호출), 새로 생성된 객체(인스턴스)를 가리킵니다.

this는 그 인스턴스를 참조합니다.

 

 

생성자 함수의 역할은 새 인스턴스를 생성하고, 이 새 객체에 속성을 추가하는 것입니다.

자바스크립트의 모든 데이터는 인스턴스 !

객체(데이터) vs 개체(물체, 모든 데이터)

Object

object

대문자 소문자 다름 ( 찾아보기 )

 

일반함수로 만들어진 생성자 함수에서 this는 새로 생성된 인스턴스를 참조할 수 있다 !!!

function Person(name) {
  this.name = name;
}
const person1 = new Person('Bob');
console.log(person1.name); // "Bob" 출력

 

** 추가 예제

// numbers1, numbers2는 같은 배열 데이터 생성 방식
const numbers1 = [5, 6, 7, 8] // 리터럴 방식
const numbers2 = new Array(5, 6, 7, 8) // 생성자 방식, numbers2는 인스턴스

Array.prototype.hello = function() {
  return this.join(' / ')
}
// [1, 2, 3].map()
// Array.prototype.map = function() {}
// join() 메서드는 배열의 모든 요소를 연결해 하나의 문자열로 만듦

// 배열 데이터에서 커스텀 메서드 사용
const res = numbers2.hello()
console.log(res) // 5 / 6 / 7 / 8

 

 

2. 명시적인 this 바인딩 (call, apply, bind)

call, apply, bind 메서드를 사용하여 함수 호출 시 this를 특정 객체로 명시적으로 설정할 수 있습니다.

 

1) call 메서드

call은 함수를 즉시 실행하면서 this를 내가 원하는 값으로 설정해주는 역할을 합니다

원래 sayHello함수는 this가 가리키고 있는 것이 무엇인지 알지 못하지만

call 메서드를 사용함으로써 이 this가 person을 가리키고 있다고 설정해 주는 것 !

function sayHello(greeting) {
  console.log(greeting + ", my name is " + this.name);
}

const person = { name: "Alice" };

sayHello.call(person, "Hi");
// "Hi, my name is Alice"

 

2) apply 메서드

call과 거의 일치하지만, 함수에 전달하는 인수들을 배열로 전달해줍니다.

 

applycall처럼 thisperson으로 설정해주지만, 다른 점은 apply에서는 인수들을 배열로 줘야 합니다.

--> sayHello 함수에서 greetingpunctuation을 배열로 넘겨주면 되는 것 !

function sayHello(greeting, punctuation) {
  console.log(greeting + ", my name is " + this.name + punctuation);
}

const person = { name: "Bob" };

sayHello.apply(person, ["Hello", "!"]);
// "Hello, my name is Bob!"

 

3) bind 메서드

bind는 call이나 apply와 달리 즉시 함수를 호출하지 않고, this가 고정된 새로운 함수를 반환합니다. 이후 이 새로운 함수를 호출할 때마다 this가 항상 고정된 객체를 참조합니다.

 

bind는 “나중에 쓸 때 이 함수에서 this는 무조건 person이야!” 라고 말해주는 역할을 합니다.

그러면 boundSayHello라는 새로운 함수가 만들어지고, 이 함수는 this가 항상 person이 되는 것 !

--> 언제 boundSayHello를 호출하든, thisperson이고, name"Charlie"가 된다고 해석합니다.

function sayHello(greeting) {
  console.log(greeting + ", my name is " + this.name);
}

const person = { name: "Charlie" };

const boundSayHello = sayHello.bind(person);

boundSayHello("Hi");
// "Hi, my name is Charlie"

 

 

3. 화살표함수에서의 this

선언 위치에서 this가 정의 !

화살표 함수에서는 this항상 함수를 감싸고 있는 외부 환경에 의해 결정됩니다.

즉, 화살표 함수는 자기만의 this를 가지지 않고, 자신이 속한 바깥 함수나 전역 컨텍스트this를 그대로 사용 !

function abc() {
  this.a = 10;
  const obj2 = {
    a: 1,
    b: 2,
    getB() => {
      console.log(this.a); // 10
    }
  }
}

const obj3 = {
  a: 1,
  b: 2,
  getC() => {
    console.log(this.a); // undefined

 

const person = {
  name: "Charlie",
  sayHello: function() {
    const innerFunction = () => {
      console.log("Hello from innerFunction, " + this.name);
    };
    innerFunction();
  }
};

person.sayHello(); // "Hello from innerFunction, Charlie"

 

• call: 함수를 호출할 때 this를 특정 객체로 설정하며, 추가적인 인수들을 개별적으로 전달합니다.
함수.call(특정객체, 인수1, 인수2, ...)
즉시 호출됨.

• apply: call과 거의 동일하지만, 인수들을 배열 형태로 전달합니다.
함수.apply(특정객체, [인수1, 인수2, ...])
즉시 호출됨.

• bind: this가 고정된 새로운 함수를 반환합니다. 이 함수는 이후 호출할 때마다 this가 고정된 객체를 참조합니다.
const 새로운함수 = 함수.bind(특정객체)
즉시 호출되지 않고, 반환된 함수를 나중에 호출.

 

 

cf. 엄격모드와 비엄격모드

자바스크립트에서 모듈 구조모듈 구조가 아닌 경우(즉, 전통적인 스크립트 방식) 사이에는 중요한 차이점이 있습니다. 특히 엄격 모드의 적용 여부와 스코프 관리 방식에서 차이가 두드러집니다. 아래에서 두 가지를 비교해보겠습니다.

 

1. 모듈 구조가 아닌 경우 (전통적인 스크립트 방식)

// traditional.js - 전통적인 스크립트 방식

x = 10; // 전역 변수로 암묵적 선언 (비엄격 모드에서는 허용됨)
console.log(x); // 10

function nonStrictFunction() {
    y = 20; // 암묵적 전역 변수 선언 (비엄격 모드에서는 허용됨)
    console.log(y); // 20
}

nonStrictFunction();

console.log(this); // 전역 객체 (window, Node.js에서는 global)

 

특징:

엄격 모드가 기본적으로 비활성화: 전통적인 스크립트에서는 엄격 모드가 기본적으로 비활성화되어 있습니다. 따라서 "use strict";를 명시적으로 선언해야 엄격 모드가 적용됩니다.

암묵적 전역 변수 생성 허용: x = 10;과 같은 암묵적 전역 변수 선언이 허용됩니다. 선언되지 않은 변수를 할당할 경우 자동으로 전역 변수가 됩니다.

전역 스코프 사용: 모든 코드는 전역 스코프에서 실행되며, this는 전역 객체(window 또는 global)를 가리킵니다.

 

2. 모듈 구조 (ES6 모듈)

// module.js - ES6 모듈 구조

let x = 10; // 모듈 스코프 내에서 변수 선언
console.log(x); // 10

function strictFunction() {
    y = 20; // ReferenceError: y is not defined (엄격 모드에서는 오류)
    console.log(y);
}

strictFunction();

console.log(this); // undefined (모듈에서는 this가 전역 객체를 가리키지 않음)

 

특징:

엄격 모드가 자동 적용: ES6 모듈에서는 "use strict";를 명시하지 않아도 엄격 모드가 자동으로 적용됩니다. 이로 인해 더 안전한 코드 작성이 가능합니다.

암묵적 전역 변수 생성 금지: 엄격 모드에서는 암묵적 전역 변수 생성이 금지됩니다. 위 예제에서 y = 20;은 선언되지 않은 변수에 값을 할당하려고 하므로 오류가 발생합니다.

모듈 스코프 사용: ES6 모듈의 모든 코드는 고유한 모듈 스코프 내에서 실행됩니다. 이 때문에 모듈 간의 전역 변수 충돌이 발생하지 않습니다. 또한, 모듈 내에서 thisundefined가 됩니다.

 

 

결론

전통적인 스크립트 방식에서는 엄격 모드가 자동으로 적용되지 않으므로, 안전한 코드를 작성하기 위해서는 "use strict";를 명시적으로 선언해야 합니다. 또한, 전역 스코프에서 모든 코드가 실행되며, 암묵적인 전역 변수 생성이 허용됩니다.

ES6 모듈 방식에서는 엄격 모드가 자동으로 적용되어, 더 안전한 코드 작성을 보장합니다. 모듈 스코프 내에서 코드가 실행되며, this는 전역 객체를 가리키지 않고 undefined가 됩니다.


 

https://youtu.be/EWfujNzSUmw?si=NR1VHFLZJprG3Sbt

https://velog.io/@kados22/FE-%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94

 

[FE 기술 면접] 실행 컨텍스트가 무엇인가요?

1️⃣ 실행 컨텍스트(Execution Context)에 대해서 설명해주세요.실행 컨텍스트는 실행 가능한 코드에 제공할 환경 정보를 모아놓은 객체입니다. 해당 객체에는 변수 객체, 스코프 체인, this 정보가

velog.io

 

반응형