JavaScript의 핵심 원리 탐구: 실행 컨텍스트관점으로 보는 클로저, this, 그리고 호이스팅

Sean L.·2024년 3월 29일
24

JavaScript

목록 보기
1/1

여러 면접을 거쳐오면서 기술면접 질문들로 자주 받는 내용들인 실행 컨텍스트,클로저,this,호이스팅을 한번에 다뤄볼려고 한다. 위 내용들을 학습해오면서 늘 궁금했던 점이 있었는데 4가지를 연관지어서 이해하고 학습하면 기술 면접을 보러 들어가기전에 한번더 확인할필요 없이 원리와 구조를 이해하기 쉬운데 대부분의 블로그들은 각각의 내용을 다루거나 기술면접 질문들을 나열해두고 하나씩 따로 설명하는 글이 대부분이였어서 무척 아쉬웠다.

그래서 이번에는 각각 내용의 간략한 개념을 다룬후 연관지어 한번 생각해보면 좋을 것 같다.

자바스크립트의 심층적인 이해는 개발자로서의 성장에 필수적인 요소입니다. 특히, 실행 컨텍스트, 클로저, this 바인딩, 그리고 호이스팅 같은 개념은 자바스크립트 엔진의 내부 동작 방식을 깊이 이해하는 데 중요한 열쇠입니다. 하지만, 이 중요한 개념들이 서로 어떻게 연결되어 있는지를 명확하게 다루는 자료를 찾기란 생각보다 어려울 수 있습니다. 많은 학습 자료와 기술 블로그가 이 개념들을 각각 별도로 설명하며, 이로 인해 독자들은 이들이 어떻게 상호 작용하는지에 대한 통합적인 이해를 얻기 어렵습니다.

실제로, 기술 면접 과정에서 이러한 주제들에 대한 질문을 받는 것은 매우 흔한 일입니다. 면접관은 단순히 개념을 암기한 수준을 넘어, 이러한 개념들이 실제 코드 실행에 어떠한 영향을 미치는지, 그리고 이들이 상호 어떻게 연결되어 있는지에 대한 깊이 있는 이해를 기대합니다. 따라서, 이러한 주제들을 분리되지 않고 통합적으로 이해하는 것이 중요합니다.

이 글에서는, 자바스크립트의 핵심 개념인 실행 컨텍스트, 클로저, this 바인딩, 그리고 호이스팅을 서로 연관 지어 설명하려 합니다. 목표는 이러한 개념들이 서로 어떻게 의존하며, 실제 자바스크립트 엔진이 코드를 어떻게 해석하고 실행하는지에 대한 명확한 그림을 제공하는 것입니다. 이를 통해, 독자 여러분은 기술 면접뿐만 아니라, 실제 프로젝트에서도 보다 견고하고 효율적인 코드를 작성할 수 있는 근본적인 이해를 얻을 수 있을 것입니다.

실행 컨텍스트는 함수 호출, 변수 범위 결정, this 값의 결정 등 자바스크립트 코드 실행의 기반이 되는 환경입니다. 클로저는 이러한 실행 컨텍스트를 이용하여 함수가 자신이 생성될 때의 환경을 "기억"하게 해주는 메커니즘입니다. this 바인딩은 실행 컨텍스트가 생성될 때 결정되며, 함수 호출 방식에 따라 그 값이 달라집니다. 마지막으로, 호이스팅은 변수와 함수 선언이 해당 스코프의 최상단으로 끌어올려지는 것처럼 동작하는 자바스크립트의 독특한 특성입니다.

이 글을 통해, 이러한 개념들이 단순한 이론적 지식을 넘어서 어떻게 실제 코드의 실행에 영향을 미치는지, 그리고 개발자가 보다 나은 코드를 작성하기 위해 이러한 개념들을 어떻게 활용할 수 있는지에 대해 깊이 있게 탐구해 보겠습니다.

자바스크립트의 실행 컨텍스트란 무엇일까?

자바스크립트의 실행 컨텍스트(Execution Context)는 코드가 실행되기 위한 환경을 의미합니다. 이 환경에는 변수, 함수 선언, this 값 등이 포함되어 있으며, 코드가 실행될 때 자바스크립트 엔진이 관리하는 내부적인 데이터 구조입니다. 실행 컨텍스트는 코드의 실행 순서를 관리하고, 실행되는 코드에 필요한 모든 정보를 담고 있어서 코드가 올바르게 작동할 수 있도록 합니다.

자바스크립트에서 실행 컨텍스트는 크게 두 가지 유형으로 나뉩니다:

  1. 글로벌 실행 컨텍스트(Global Execution Context): 자바스크립트 코드가 처음 실행될 때 생성되는 실행 컨텍스트입니다. 이는 코드의 최상위 레벨, 즉 전역 환경에서 실행되는 모든 코드를 포함합니다. 글로벌 실행 컨텍스트는 하나만 존재하며, 전역 객체(브라우저에서는 window, Node.js에서는 global)와 전역 변수를 관리합니다.
  2. 함수 실행 컨텍스트(Function Execution Context): 함수가 호출될 때마다 생성되는 실행 컨텍스트입니다. 각 함수 호출마다 고유한 실행 컨텍스트가 생성되어 해당 함수의 지역 변수, 매개변수, this 값 등을 관리합니다. 함수 실행 컨텍스트는 호출 스택(Call Stack)이라는 구조를 통해 관리되며, 함수의 호출 순서에 따라 스택에 쌓이고 실행이 끝나면 스택에서 제거됩니다.

실행 컨텍스트의 생명주기는 크게 세 단계로 나눌 수 있습니다:

  1. 생성 단계(Creation Phase): 실행 컨텍스트가 생성되고, 변수 객체(Variable Object)가 초기화됩니다. 변수 객체는 해당 컨텍스트 내의 변수, 함수 선언, 매개변수 등을 저장합니다. 이 단계에서는 함수 선언은 메모리에 저장되지만, 변수는 선언만 되고 초기화는 되지 않습니다(호이스팅 현상).
  2. 실행 단계(Execution Phase): 변수 값이 할당되고, 코드가 실제로 실행됩니다. 이 단계에서는 코드 내부에서 선언된 변수와 함수가 실제 값을 가지며, 코드의 실행 결과가 결정됩니다.
  3. 종료 단계(Termination Phase): 실행 컨텍스트의 코드 실행이 완료되고, 해당 컨텍스트는 호출 스택에서 제거됩니다.

this binding이란 무엇일까?

this 바인딩은 자바스크립트에서 함수나 메서드가 호출되는 방식에 따라 this 키워드가 가리키는 값, 즉 함수나 메서드의 실행 컨텍스트가 어떤 객체를 참조하는지 결정하는 과정을 의미합니다. this의 값은 함수를 어떻게 호출하느냐에 따라 동적으로 결정됩니다.

this 바인딩의 규칙

  1. 전역 컨텍스트 또는 함수 호출: 일반적인 함수 호출에서 this는 전역 객체를 참조합니다. 브라우저 환경에서는 window가 되며, Node.js 환경에서는 global이 됩니다. 엄격 모드('use strict')에서는 thisundefined로 설정됩니다.

    function globalFunction() {
    console.log(this); // window,nodeJS 또는 strict mode에서는 undefined
    }
    globalFunction();
  2. 메서드 호출: 객체의 메서드로 함수가 호출될 때, this는 메서드를 호출한 객체에 바인딩됩니다. 이는 해당 메서드가 속한 객체의 컨텍스트에서 실행되기 때문입니다.

    const myObject = {
      method: function() {
        console.log(this); // 이 함수를 호출한 객체(myObject)를 참조
      }
    };
    myObject.method();
    
  3. 생성자 함수 호출: new 키워드를 사용하여 함수(생성자)를 호출할 때, this는 새로 생성되는 객체에 바인딩됩니다. 생성자 함수는 보통 대문자로 시작하는 이름을 가지며, 새 객체를 생성하고 초기화하는 역할을 합니다.

    function MyConstructor() {
      this.property = 'value';
      console.log(this); // 이 함수로 생성된 새로운 객체를 참조
    }
    const newInstance = new MyConstructor();
    
  4. apply, call, bind 메서드에 의한 호출: 이 메서드들을 사용하면 함수의 this 값을 명시적으로 설정할 수 있습니다. applycall은 함수를 즉시 호출하면서 첫 번째 인자로 this 값을 전달하고, bindthis 값이 설정된 새 함수를 반환하지만 즉시 호출하지는 않습니다.

    function customFunction(a, b) {
      console.log(this); // 명시적으로 바인딩된 객체를 참조
      console.log(a + b);
    }
    
    const customObject = { name: 'custom' };
    
    // apply 사용
    customFunction.apply(customObject, [1, 2]);
    
    // call 사용
    customFunction.call(customObject, 3, 4);
    
    // bind 사용
    const boundFunction = customFunction.bind(customObject);
    boundFunction(5, 6);
    

this 바인딩은 코드의 실행 컨텍스트에 따라 동적으로 결정되기 때문에, 특히 콜백 함수나 이벤트 핸들러 같은 경우에 예상치 못한 this의 값으로 인해 버그가 발생할 수 있습니다. 따라서, this의 바인딩을 의도한 대로 제어하기 위해 화살표 함수(ES6 이상)를 사용하거나, bind 메서드를 활용하는 것이 좋습니다. 화살표 함수는 자신을 둘러싼 외부 스코프의 this 값을 그대로 상속받아, this 바인딩을 보다 예측 가능하게 만듭니다.

호이스팅이란 무엇일까?

호이스팅(Hoisting)은 자바스크립트에서 변수나 함수의 선언을 그 범위의 최상단으로 끌어올리는 행위를 말합니다. 실제로 코드가 재배치되지는 않지만, 자바스크립트 엔진이 코드를 실행하기 전에 변수와 함수 선언을 메모리에 저장하므로, 선언 전에 해당 변수나 함수를 참조할 수 있게 됩니다.

함수 호이스팅

함수 선언은 호이스팅되어 코드의 상단으로 끌어올려집니다. 이는 함수를 선언하기 전에도 호출할 수 있음을 의미합니다.

console.log(myFunction()); // "Hello, world!"

function myFunction() {
  return "Hello, world!";
}

변수 호이스팅

변수 선언은 호이스팅되어 해당 스코프의 최상단으로 끌어올려지지만, 초기화는 호이스팅되지 않습니다. var 키워드로 선언된 변수의 경우, 호이스팅되어 undefined로 초기화됩니다. 반면, letconst로 선언된 변수는 호이스팅은 되지만, 초기화되지 않은 상태(temporal dead zone)에서 접근하려고 하면 참조 에러(ReferenceError)가 발생합니다.

console.log(x); // undefined
var x = 5;

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

클로저란 무엇일까?

클로저(Closure)는 함수와 그 함수가 선언된 어휘적 환경(Lexical Environment)과의 조합을 말합니다. 자바스크립트에서 클로저는 내부 함수가 외부 함수의 스코프에 있는 변수에 접근할 수 있게 하는 기능을 말하며, 외부 함수가 실행을 마친 후에도 외부 함수의 변수에 접근할 수 있게 합니다. 이러한 특성 덕분에 프로그래밍에서 데이터 은닉, 캡슐화, 메모리 효율성 증대 등 다양한 용도로 활용됩니다.

클로저의 동작 원리

  1. 외부 함수가 호출되어 내부 함수가 생성될 때, 내부 함수는 외부 함수의 변수를 참조하는 스코프 체인을 포함합니다.
  2. 외부 함수의 실행이 완료되어도, 외부 함수의 변수가 내부 함수에 의해 참조되고 있기 때문에, 해당 변수는 가비지 컬렉션의 대상이 되지 않습니다.
  3. 내부 함수가 실행될 때, 여전히 외부 함수의 변수에 접근할 수 있습니다. 이는 내부 함수가 선언될 당시의 어휘적 환경을 기억하기 때문입니다.
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

여기서 makeAdder는 외부 함수이며, 반환되는 익명 함수는 내부 함수입니다. 각각의 내부 함수는 makeAdder 함수 호출 시 전달된 x의 값을 기억하는 클로저를 형성합니다. 따라서, add5add10은 각각 x가 5와 10으로 설정된 상태의 클로저를 참조합니다.

클로저는 자바스크립트에서 강력한 프로그래밍 패턴을 가능하게 하며, 함수형 프로그래밍에서 핵심적인 역할을 합니다. 클로저를 통해 상태를 안전하게 은닉하고, 특정 함수에 지속적인 상태를 유지할 수 있게 하며, 고차 함수에서 널리 사용됩니다.

실행컨텍스트 관점에서의 호이스팅,클로저,this

자바스크립트의 호이스팅, 클로저, this 바인딩은 모두 실행 컨텍스트와 깊은 연관이 있습니다. 이들 개념을 통합적으로 이해하기 위해, 각각이 어떻게 실행 컨텍스트와 연결되는지를 중심으로 설명하겠습니다. 그럼 예제 코드를 통해 이해를 돕겠습니다.

// 예제 코드
function outer() {
  console.log(this); // 1. `this` 바인딩 (전역 객체 또는 외부 함수가 속한 객체)
  console.log(inner); // 2. 호이스팅 (undefined, 함수 표현식은 호이스팅되지 않음)
  var captured = 'captured variable';

  function inner() {
    console.log(captured); // 3. 클로저 ('captured variable', outer의 변수를 참조)
    console.log(this); // 4. `this` 바인딩 (전역 객체, 내부 함수에서 별도의 바인딩 설정이 없으면 전역)
  }

  var inner = function() {
    console.log(captured); // 5. 클로저 + 호이스팅 ('captured variable', 호이스팅으로 인해 여기서도 접근 가능)
    console.log(this); // 6. `this` 바인딩 (전역 객체, 함수 표현식의 `this`도 전역 객체를 가리킴)
  };

  return inner;
}

var innerFunc = outer(); // outer 함수 호출
innerFunc(); // 반환된 inner 함수 호출

실행 컨텍스트와의 연관성

  1. this 바인딩: this의 값은 실행 컨텍스트의 생성 단계에서 결정됩니다. 전역 실행 컨텍스트에서는 this가 전역 객체를 가리킵니다. 함수 실행 컨텍스트에서 this의 값은 함수가 어떻게 호출되었는지에 따라 다릅니다. 예제에서 outer 함수의 this는 전역 객체를 가리키며, 내부 함수에서도 동일하게 전역 객체를 가리킵니다(명시적으로 바인딩을 변경하지 않는 한).
  2. 호이스팅: 변수와 함수 선언은 실행 컨텍스트의 생성 단계에서 메모리에 저장됩니다. 이로 인해, 선언 전에도 해당 변수나 함수에 접근할 수 있게 됩니다. 그러나 변수의 초기화는 실행 단계에서 이루어지므로, inner 함수 표현식은 undefined로 로그됩니다. 이는 호이스팅이 변수 선언은 처리하지만 초기화는 처리하지 않기 때문입니다.
  3. 클로저: 함수가 선언된 어휘적 환경을 기억하여 선언된 환경 밖에서도 해당 환경에 접근할 수 있는 기능입니다. outer 함수의 실행 컨텍스트가 종료된 후에도, 반환된 inner 함수는 outer 함수의 변수 captured에 접근할 수 있습니다. 이는 inner 함수가 생성될 때의 스코프 체인이 클로저를 형성하기 때문입니다.

결론

실행 컨텍스트는 자바스크립트 코드의 실행 환경을 구성하며, this 바인딩의 결정, 호이스팅 현상의 기반, 그리고 클로저의 작동 원리를 이해하는 데 중요한 역할을 합니다. 각 함수의 호출은 새로운 실행 컨텍스트를 생성하고, 이 컨텍스트 내에서 변수의 생명주기, this의 값, 그리고 클로저

이 관리됩니다. 이러한 개념들은 자바스크립트의 동적인 특성과 유연성을 가능하게 하는 핵심 요소입니다.

자바스크립트의 핵심 개념인 실행 컨텍스트, 클로저, this 바인딩, 그리고 호이스팅을 깊이 있게 이해하는 것은 개발자가 견고하고 효율적인 코드를 작성하는 데 있어 매우 중요합니다. 이 글을 통해, 이러한 개념들이 단순히 독립적인 이론이 아니라, 서로 밀접하게 연결되어 있으며 자바스크립트 엔진이 코드를 어떻게 실행하는지에 대한 근본적인 이해를 제공하는 것을 목표로 했습니다.

실행 컨텍스트는 자바스크립트의 코드 실행 환경의 기초를 형성하며, 클로저는 이 환경을 통해 함수가 자신이 생성된 스코프의 변수에 접근할 수 있도록 합니다. this 바인딩은 실행 컨텍스트의 중요한 부분으로, 함수가 어떻게 호출되는지에 따라 그 값이 결정되며, 호이스팅은 변수와 함수 선언이 실행 전에 처리되는 방식을 설명합니다. 이러한 개념들의 상호 작용은 자바스크립트에서 매우 흔한 패턴과 문제들을 이해하고 해결하는 데 필수적입니다.

기술 면접을 준비하는 과정에서, 또는 실제 프로젝트를 진행하면서 이러한 개념들을 명확히 이해하고 있으면, 보다 복잡한 문제를 해결할 수 있는 능력을 키울 수 있습니다. 또한, 이러한 깊이 있는 이해는 코드의 동작 원리를 예측 가능하게 만들고, 디버깅을 용이하게 하며, 다른 개발자들과의 의사소통을 개선하는 데에도 크게 기여할 것입니다.

이 글이 자바스크립트의 깊은 이해를 추구하는 모든 분들에게 유용한 글이 되기를 바랍니다.

profile
3년차 프론트엔드 개발자입니다

1개의 댓글

comment-user-thumbnail
2024년 3월 29일

우와 유익한 글 감사합니다-!

답글 달기