자바스크립트에서 실행 컨텍스트(execution context)와 스코프(scope)는 모두 코드 실행과 변수 접근 방식에 관련된 개념이지만 서로 다른 역할을 수행한다. 이 둘의 차이를 이해하려면 각각의 정의와 역할을 살펴보는 것이 중요하다.

 

실행컨텍스트 (execution context)

정의 

실행 컨텍스트는 자바스크립트 코드가 실행되는 환경으로 코드가 어떻게 실행되고 변수와 함수가 어떻게 메모리에 저장되고 참조되는지를 정의한다. 즉, 자바스크립트가 코드를 실행하기 위해 필요한 모든 정보를 담고 있는 객체라고 볼 수 있다. 

 

구성요소 

1. 변수 환경 

- 선언된 변수 및 함수 선언과 관련된 정보가 저장된다. 

2. 렉시컬 환경 

- 현재 실행 중인 코드가 참조할 수 있는 스코프와 관련된 정보가 저장된다. 

- 외부 렉시컬 환경에 대한 참조를 포함해 상위 스코프를 참조한다. 

3. this 바인딩 

- this 키워드가 가리키는 객체를 정의한다. 

 

실행컨텍스트의 종류 

1. 전역 컨텍스트 (Global Context)

- 스크립트가 실행될 때 가장 먼저 생성되며, 브라우저 환경에서는 window객체가 전역 컨텍스트가 된다. 

2. 함수컨텍스트 (Function Context)

- 함수가 호출될 때 생성되며, 함수 내부의 변수와 this를 관리한다. 

3. Eval컨텍스트(Eval Context)

- eval( )함수가 실행될 때 생성된다. 

 

실행컨텍스트의 동작 

실행컨텍스트는 스택 구조로 관리되며 LIFO(Last In First Out)방식으로 처리된다. 

function outer() {
  console.log("Outer Start");
  inner(); // inner 실행
  console.log("Outer End");
}

function inner() {
  console.log("Inner Function");
}

outer(); // outer 실행

 

실행과정 

1. 전역 컨텍스트가 생성되고 스택에 추가된다. 

2. outer함수 호출 시 outer함수의 실행 컨텍스트가 생성되어 스택에 추가된다. 

3. inner함수 호출 시 inner함수의 실행 컨텍스트가 생성되어 스택에 추가된다. 

4. 함수 실행이 완료되면 해당 컨텍스트는 스택에서 제거된다. 

스코프(Scope)

정의 

스코프는 변수와 함수가 접근할 수 있는 유효 범위를 의미한다. 스코프는 실행컨텍스트와 다르게 코드 구조를 기준으로 동작하며 변수의 가시성을 결정한다. 

 

스코프의 종류 

1. 전역 스코프

- 어디서든 접근 가능한 범위이다. 전역스코프에 정의된 변수는 모든 코드에서 사용할 수 있다. 

const globalVar = "I am global";

function printVar() {
  console.log(globalVar); // 접근 가능
}
printVar();

2. 지역스코프

- 함수 또는 블록 내부에서만 접근 가능한 범위이다. 

function myFunction() {
  const localVar = "I am local";
  console.log(localVar); // 접근 가능
}
myFunction();
// console.log(localVar); // 오류! 지역 변수는 함수 밖에서 접근 불가

3. 블록스코프 

- {}로 감싸진 블록 내부에서만 유효한 스코프이다. let과 const는 블록 스코프를 따른다. 

if (true) {
  let blockScoped = "I am block scoped";
  console.log(blockScoped); // 접근 가능
}
// console.log(blockScoped); // 오류! 블록 스코프 밖에서 접근 불가

 

렉시컬 스코프 

렉스컬 스코프는 코드가 작성된 위치를 기준으로 스코프가 결정되는 방식을 말한다. 자바스크립트는 렉시컬 스코프 규칙을 따르기 때문에, 함수를 어디서 호출되었는지가 아니라 함수를 어디서 선언했는지에 따라 스코프가 결정된다. 

function outer() {
  const outerVar = "I am outer";
  
  function inner() {
    console.log(outerVar); // outerVar를 참조 가능
  }

  inner();
}
outer();

'면접 준비 > javascript' 카테고리의 다른 글

[JS] 클로저란 무엇일까요?  (0) 2025.01.21
[JS]자바스크립트의 데이터 타입  (0) 2025.01.17
[JS]var, let, const의 차이 + TDZ  (0) 2025.01.17

클로저(closure)는 자바스크립트에서 함수와 그 함수가 선언된 렉시컬 환경의 조합을 의미합니다. 

즉, 클로저는 함수가 자신이 선언된 환경밖에서 호출될때에도 그 환경에 접근할 수 있도록 유지되는 특징을 말합니다. 

 

클로저는 어떻게 동작하나요? 

자바스크립트는 함수가 생성될 때 해당 함수가 선언된 위치의 렉시컬 스코프를 기억합니다. 이로 인해 함수 내부에서 사용하는 변수나 상수는 함수가 실행될 때의 스코프가 아니라 함수가 선언된 시점의 스코프를 기준으로 참조됩니다. 

 

클로저를 만드는 조건 

1. 함수 내부에 선언된 함수가 필요합니다. 

2. 내부 함수가 외부 함수의 변수에 접근해야합니다. 

 

클로저의 예제 

1. 기본적인 클로저 예제

function outerFunction() {
  let count = 0; // 외부 함수의 지역 변수

  return function innerFunction() {
    count++; // 외부 변수에 접근
    console.log(`현재 count 값: ${count}`);
  };
}

const increment = outerFunction(); // outerFunction 실행, innerFunction을 반환
increment(); // 현재 count 값: 1
increment(); // 현재 count 값: 2
increment(); // 현재 count 값: 3

여기서 중요한 점은 innerFunction이 count에 접근할 수 있다는 점입니다. 

outerFunction이 종료되었지만, 클로저 덕분에 innerfunction은 count를 기억하고 계속 접근할 수 있습니다. 

 

2. 클로저를 사용한 데이터 캡슐화 

클로저는 데이터를 외부에서 직접 수정하지 못하도록 캡슐화하는데 유용합니다. 

function createCounter() {
  let count = 0;

  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount());  // 1

여기서 count는 외부에서 직접 접근할 수 없으며, 오직 반환된 객체의 메소드를 통해서만 접근할 수 있습니다. 

 

클로저의 주요 활용 사례 

1. 정보 은닉 및 데이터 캡슐화 

- 클로저를 이용하여 외부에서 접근이 불가능한 프라이빗 변수처럼 사용할 수 있습니다. 

2. 부분 적용함수 

- 함수의 일부 인자를 미리 고정하여 새 함수를 생성할 때 사용합니다. 

function multiply(multiplier) {
  return function (number) {
    return number * multiplier;
  };
}

const double = multiply(2); // multiplier = 2 고정
console.log(double(5)); // 10
console.log(double(10)); // 20

3. 이벤트 리스너 및 비동기 작업에서의 상태 유지 

- 클로저를 활용하여 이벤트 핸들러나 비동기 작업에서 상태를 유지할 수 있습니다. 

function setupButton() {
  let clickCount = 0;

  document.getElementById('myButton').addEventListener('click', function () {
    clickCount++;
    console.log(`버튼 클릭 횟수: ${clickCount}`);
  });
}

setupButton();

4. 메모리 효율적인 반복 작업 

- 클로저는 반복문에서 특정 변수 상태를 고정하여 효율적으로 작업을 수행할 수 있습니다. 

 

클로저 사용시 주의점 

1. 메모리 누수

클로저는 함수의 렉시컬 스코프를 계속 참조하므로 불필요한 클로저가 남앙 있으면 메모리 누수가 발생할 수 있습니다. 따라서 클로저가 더 이상 필요하지 않을 경우 참조를 해제해야합니다. 

2. 과도한 사용으로 코드 가독성 저하 

클로저가 많아지면 스코프 체인이 복잡해지고, 코드 가독성이 떨어질 수 있습니다. 적절한 설계를 통해 이를 방지해야합니다 .

 

자바스크립트이 데이터 타입은 크게 원시타입과 참조타입으로 나눌 수 있습니다. 각 타입은다루는 방식이 다르고 값의 저장방식이나 값을 비교할 때의 특성에 차이가 있습니다. 

원시타입 (primitive types)

원시타입은 불변성을(immutable) 가지고 있으며 변수에 직접 값이 저장됩니다. 원시 타입은 6가지로 정의됩니다.

 

 

string : 텍스트 데이터를 나타내는 타입입니다.

예시: "Hello, World!", 'JavaScript'

number: 정수와 부동소수점 숫자를 포함한 숫자 타입입니다.

예시: 42, 3.14, -1

bigint : BigInt는 정밀도가 큰 정수를 다룰 수 있는 타입입니다. number로 표현할 수 없는 매우 큰 숫자들을 표현할 수 있습니다.

예시: 1234567890123456789012345678901234567890n

boolean : 참(True) 또는 거짓(False) 값을 나타내는 데이터 타입입니다.

예시: true, false

undefined : 변수는 선언되었지만, 값이 할당되지 않은 상태일 때 자동으로 가지는 값입니다.

예시: let x; console.log(x); // undefined

null : "없음" 또는 "빈 값"을 나타내는 특별한 값입니다. 객체가 없는 상태를 나타냅니다.

예시: let y = null;

symbol : 유일하고 변경 불가능한 고유의 값을 생성하는 타입입니다. 주로 객체의 고유한 프로퍼티 키로 사용됩니다.

예시: let sym = Symbol('description');

 

undefined와 null의 차이

undefined: 값이 할당되지 않은 변수의 상태를 나타냅니다.

null: 값이 없다는 의도적인 표현입니다.

 

참조타입(Reference Types)

참조타입은 객체와 같은 복합적인 값을 나타냅니다. 원시타입과는 달리 변수에는 값 자체가 아니라 값이 저장된 메모리 주소가 저장됩니다. 참조 타입에는 다음이 포함됩니다. 

 

객체

여러 값을 키-값 쌍으로 저장하는 자료형입니다. javascript에서 객체는 다양한 속성과 매서드를 가질 수 있는 복합 데이터 타입입니다. 

let person = { name: "Alice", age: 25 };

 

배열

객체의 특별한 형태로 순서가 있는 데이터를 저장하는 자료형입니다. 배열의 요소들은 인덱스를 통해 접근합니다. 

let arr = [1, 2, 3, 4, 5];

 

함수 

함수도 객체의  일종으로 특정 작업을 수행하는 코드 블록을 나타냅니다. 

function greet(name) {
  return `Hello, ${name}!`;
}

프론트엔드에서는 변수를 다루를 때 var, let, const를 사용해서 선언합니다. 각각이 어떤 차이를 가지고 있는지에 대해서 한번 다루어 보고자 합니다. 

Var 

스코프 

var는 함수 스코프를 가집니다. 그렇기 때문에 변수가 선언된 함수 내에서만 유효합니다. 블록 스코프를 지원하지 않기 때문에 if, for등의 블록안에서 선언해도 블록 외부에서 접근이 가능합니다. 

호이스팅

var로 선언된 변수는 호이스팅되어 변수 선언이 코드의 최상단으로 끌어 올려집니다. 하지만 초기화는 호이스팅되지 않으므로 선언 전에 접근하면 undefined를 반환합니다. 

function testVar() {
  if (true) {
    var x = 10; // 블록 내부에서 선언
  }
  console.log(x); // 블록 외부에서도 접근 가능
}
testVar(); // 출력: 10

Let 

스코프

let은 블록스코프를 가집니다. 블록내에서만 유효하며 블록 외부에서는 접근할 수 없습니다. 

 

호이스팅

let으로 선언된 변수도 호이스팅 되지만 TDZ(Temporal Dead Zone)때문에 초기화 전에 접근하려 하면 ReferenceError가 발생합니다.

function testLet() {
  if (true) {
    let y = 20; // 블록 내부에서 선언
    console.log(y); // 출력: 20
  }
  console.log(y); // ReferenceError: y is not defined
}
testLet();

 

const 

스코프 

const도 let과 마찬가지로 블록 스코프를 가집니다. 

 

상수 

const로 선언된 변수는 재할당이 불가능합니다. 하지만 참조형 데이터(예: 배열, 객체)의 경우, 내부 값은 변경할 수 있습니다. 

 

호이스팅 

const도 호이스팅되지만 let과  마찬가지로 TDZ때문에 초기화 이전에 접근하면 referenceError가 발생합니다.

const z = 30;
z = 40; // TypeError: Assignment to constant variable.

const obj = { name: "Alice" };
obj.name = "Bob"; // 객체 내부의 속성은 변경 가능
console.log(obj.name); // 출력: Bob

 

결론 

const를 기본으로 사용하는 것이 좋습니다. 변수에 재할당이 필요하지 않다면 const로 선언해 안정성을 높이는 것이 좋습니다. 

let은 재할당이 필요한 경우에만 사용하는 것이 좋습니다. 

var는 사용하지 않는 것이 권장됩니다. 함수 스코프와 호이스팅으로 인해 예기치 않은 동작이 발생할 수 있습니다.

 


++ TDZ에 대해서 

TDZ(temporal Dead Zone)은 자바스크립트에서 let과 const로 선언된 변수를 초기화 전에 접근하려고 할 때 발생하는 특별한 구역을 의미합니다. TDZ는 변수 선언이 호이스팅되지만, 초기화가 되지 않은 상태에서 접근을 시도하면 에러를 발생시키는 구역입니다. 

function testTDZ() {
  console.log(a); // ReferenceError: Cannot access 'a' before initialization
  let a = 10; // 변수 'a'의 초기화
}

testTDZ();

let a=10;으로 a변수를 선언했지만 선언만 호이스팅되고 초기화는 나중에 이루어지기 때문에, console.log(a)에서 초기화 전인 TDZ구간에서 a에 접근하려 하면 ReferenceError가 발생합니다. 

 

var의 경우에는 TDZ의 영향을 받지 않습니다. var는 함수 스코프로 동작하며, 선언은 호이스팅되지만 TDZ가 적용되지 않기 때문에 초기화 전에 접근해도 undefined로 값이 반환됩니다. 

function testVar() {
  console.log(c); // 출력: undefined
  var c = 30; // 변수 c의 초기화
}

testVar();

 

그렇다면 TDZ는 왜 필요한걸까요? 

TDZ는 예기치 않은 오류를 방지하고 코드의 안정성을 높이는 데 중요한 역할을 합니다. let과 const를 사용함으로써 변수가 초기화 되기 전에 접근하는 실수를 줄이기 위해Javascript엔진이 이를 강제로 막아줍니다. 

 

+ Recent posts