[JavaScript] 스코프와 클로저

wha1e·2025년 1월 6일
1

TIL

목록 보기
4/8

지난 글에서 React의 useState는 왜 const로 사용하는지 알아보며, 변수에 대해 정리해보았다.

이 과정에서 스코프(Scope)에 대해 알아보게 되었는데, 제대로 알고 넘어가지 않았던 개념에 대해 상세히 알아보고 보다 친숙하게 용어를 익히기 위해 포스팅을 작성한다.


📍스코프란?

스코프(Scope)변수나 함수가 유효한 범위를 의미한다.
즉, 특정 코드에서 변수에 접근하거나 사용할 수 있는 규칙과 범위라고 볼 수 있다.

이 Scope를 눈으로 확인할 수 있는데, 개발자 도구의 Source에서 JS 코드를 확인하게 되면 쉽게 Scope의 구조를 확인할 수 있다.

위와 같이 Scope 안에는 Local, Closure, Global, CallStack과 같은 요소가 포함되어있음을 볼 수 있다.

이번에 알아볼 개념은 Local, Global, Closure와 같은 Scope의 개념이지만, JavaScript의 동작원리에 포함되는 Call StackExecution Context의 개념도 함께 이해해야 심도 깊은 이해를 가져갈 수 있다.


📍Call Stack (호출 스택)

call stack is a mechanism for an interpreter (like the JavaScript interpreter in a web browser) to keep track of its place in a script that calls multiple functions — what function is currently being run and what functions are called from within that function, etc.

호출 스택은 여러 함수들을 호출하는 스크립트에서 해당 위치를 추적하는 인터프리터 (웹 브라우저의 JavaScript 인터프리터같은)를 위한 메커니즘입니다. 현재 어떤 함수가 실행중인지, 그 함수 내에서 어떤 함수가 호출되어야 하는지, 등을 제어합니다.

Call Stack(호출 스택)은 Stack의 자료 구조를 가지며, LIFO(Last In, First Out) 방식으로 작동된다. 실행되는 우선순위와 실행되고 있는 함수 등을 제어하는 기능을 한다.

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

위 코드가 실행될 때, 호출 스택의 동작 단계는 아래와 같다.

Call Stack은 코드가 실행되면서 생성되는 Execution Context를 저장하는 자료구조다. 엔진이 처음 script를 실행할 때, Global Execution Context를 생성하고 이를 Call Stack에 push한다.

그 후 엔진이 함수를 호출할 때 마다 함수를 위한 Execution Context를 생성하고 이를 Call Stack에 push 한다.


📍Execution Context (실행 컨텍스트)

Execution Context(실행 컨텍스트)JavaScript 코드의 일부가 실행되는 환경을 의미한다. 실행 컨텍스트는 변수, 함수, 객체가 어떻게 관리되고 실행되는지 정의하고 있다.

(1) Execution Context의 종류

  1. Global Execution Context(전역 실행 컨텍스트)
    • 코드가 실행되면 가장 먼저 생성.
    • 전역 스코프와 전역 객체(window 또는 global)를 포함.
  2. Function Execution Context(함수 실행 컨텍스트)
    • 함수가 호출될 때마다 생성.
    • 각 함수의 매개변수, 지역 변수, arguments 객체, this 값을 포함.
  3. Eval Execution Context
    • eval() 함수로 코드를 실행할 때 생성.

(2) Execution Context의 구성 요소

  1. Variable Environment (변수 환경)
    • 변수를 저장하고 스코프를 관리.
    • let, const, var로 선언된 변수를 포함.
  2. Lexical Environment (렉시컬 환경)
    • 코드가 작성된 스코프 체인외부 렉시컬 환경 참조를 관리.
    • 함수가 선언될 당시의 환경을 기억.
  3. This Binding (this 바인딩)
    • 실행 컨텍스트에서 this 키워드가 무엇을 참조하는지 정의.

📍스코프의 종류?

(1) 전역 스코프 (Global Scope)

  • 정의: 전역 스코프에 선언된 변수는 프로그램 전체에서 접근 가능.
  • 특징:
    • 함수, 블록 안이 아닌 코드의 최상위 레벨에서 선언된 변수는 전역 변수.
    • 전역 변수는 앱 전체에서 사용 가능하지만, 잘못 사용하면 코드의 복잡성을 높이고 의도치 않은 버그를 유발할 수 있다.
noVarTagVar = "I am global"; //변수의 종류 선언 없이 이름만 선언하게 되면 전역 변수로 저장
let globalVar = "I am global"; // 전역 변수

function printGlobalVar() {
  console.log(globalVar); // 접근 가능
}
printGlobalVar(); // "I am global"

(2) 지역 스코프 (Local Scope)

  • 정의: 특정 코드 블록({ }) 또는 함수 내부에서 선언된 변수는 해당 블록이나 함수 내부에서만 유효.
  • 특징:
    • 외부에서 접근할 수 없으며, 같은 이름의 변수를 다른 블록에서 독립적으로 사용할 수 있다.
    • 주로 함수와 블록을 이용해 변수를 지역화한다.

함수 스코프

  • var로 선언된 변수는 함수 내부에서만 유효.
function localScopeExample() {
  var localVar = "I am local";
  console.log(localVar); // 접근 가능
}
// console.log(localVar); // ReferenceError: localVar is not defined

블록 스코프

  • letconst는 블록 스코프를 따르며, { } 안에서만 유효.
if (true) {
  let blockScopedVar = "I am block-scoped";
  console.log(blockScopedVar); // 접근 가능
}
// console.log(blockScopedVar); // ReferenceError: blockScopedVar is not defined

📍스코프 체인(Scope Chain)

  • 정의: 변수를 찾을 때 현재 스코프에서 찾고, 없으면 상위 스코프를 순차적으로 탐색하는 방식.
  • 작동 원리:
    1. 변수 참조 시, 가장 가까운 스코프에서 해당 변수를 찾음.
    2. 없으면 상위 스코프로 이동하여 검색.
    3. 최상위 스코프(전역)까지 검색한 후, 없으면 ReferenceError 발생.
let a = "global";

function outer() {
  let b = "outer";

  function inner() {
    let c = "inner";
    console.log(a); // "global" (전역 스코프에서 찾음)
    console.log(b); // "outer" (상위 스코프에서 찾음)
    console.log(c); // "inner" (현재 스코프에서 찾음)
  }

  inner();
}

outer();

📍렉시컬 스코프 (Lexical Scope)

  • 정의: JavaScript는 렉시컬 스코프를 따르며, 변수를 선언할 때의 코드 구조에 따라 스코프가 결정된다. Static Scope(정적 스코프)라고 불리기도 한다.
  • 특징: 함수가 호출되는 위치가 아니라 함수가 선언된 위치가 스코프를 결정함.
let globalVar = "global";

function outer() {
  let outerVar = "outer";

  function inner() {
    console.log(globalVar); // "global"
    console.log(outerVar);  // "outer"
  }

  return inner;
}

const innerFunction = outer();
innerFunction(); // "global", "outer" (선언된 위치 기준으로 스코프 결정)

Dynamic Scope(동적 스코프)라는 개념도 존재하는데, 만약 동적 스코프처럼 동작했다면 아래와 같이 동작했을 것이다.

let globalVar = "I'm global";

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

  function inner() {
    console.log(globalVar); // 호출된 위치 기준으로 결정
    console.log(outerVar);  // 호출된 위치 기준으로 결정
  }

  return inner;
}

function anotherFunction() {
  let globalVar = "I'm dynamic";
  let outerVar = "I'm dynamic outer";
  const dynamicFunc = outer();
  dynamicFunc(); // inner 함수 호출
}

anotherFunction();

실행 결과

I'm dynamic
I'm dynamic outer

JavaScript는 렉시컬 스코프를 사용해 코드 작성 시점에 변수의 선언 위치에 따라 변수가 유효한 범위를 고정한다. 이를 통해 코드를 더 직관적이고, 예측 가능하게 만든다.


📍Closure(클로저)

closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope. In JavaScript, closures are created every time a function is created, at function creation time.

클로저는 주변 상태(어휘적 환경)에 대한 참조와 함께 묶인(포함된) 함수의 조합입니다. 즉, 클로저는 내부 함수에서 외부 함수의 범위에 대한 접근을 제공합니다. JavaScript에서 클로저는 함수 생성 시 함수가 생성될 때마다 생성됩니다.

function outer() {
  let count = 0; // 외부 변수

  return function inner() { // 내부 함수
    count++; // 외부 변수를 사용
    console.log(count); // 외부 변수를 출력
  };
}

const closureFunction = outer(); // outer 실행 -> inner 반환
closureFunction(); // 1
closureFunction(); // 2
closureFunction(); // 3

위와 같이 구성하게 되면, outer라는 함수에 대한 참조를 기억하며 기존 값을 유지하며 증가시킬 수 있다.


📍Closure(클로저)가 왜 중요할까?

  1. 전역변수 사용의 최소화
    • 전역변수가 많으면 의도치 않게 어디에서든 접근하는 상황이 발생할 수 있다. 클로저를 이용하여 전역변수를 최소한으로 사용함으로써 이러한 실수나 예외적인 상황을 방지할 수 있다.
  2. 데이터 보존 가능
    • 클로저 함수는 외부 함수의 실행이 끝나더라도 외부 함수 내 변수를 사용할 수 있다. 따라서 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있게하는 폐쇄성을 갖는다.
  3. 모듈화를 통한 코드 재사용에 편리
    • 클로저 함수를 각각의 변수에 할당하면 각자 독립적으로 값을 사용하고 보존 가능하다. 이와 같이 함수의 재사용성을 극대화하고 함수 하나를 독립적인 부품의 형태로 분리하는 것을 모듈화라고 한다. 클로저를 통해 데이터와 메소드를 묶어다닐 수 있기에 클로저는 모듈화에 유리하다.
  4. 정보의 접근 제한 (캡슐화)
    • 클로저 모듈 패턴을 사용해 객체에 담아 여러 개의 함수를 리턴하도록 만들어 정보의 접근을 제한할 수 있는데, 이를 캡슐화라고 한다.

참고 자료

https://velog.io/@graphicnovel/JS-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

https://yozm.wishket.com/magazine/detail/2886/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

https://velog.io/@jiyaho/JS-Closure%ED%81%B4%EB%A1%9C%EC%A0%80%EC%9D%98-%EA%B0%9C%EB%85%90-%ED%8A%B9%EC%A7%95-%EC%9E%A5%EC%A0%90-%EC%98%88%EC%8B%9C#-closure%EC%9D%98-%EA%B0%9C%EB%85%90

profile
상상을 현실로 만드는 FE

0개의 댓글

관련 채용 정보