본문 바로가기
Study/JavaScript

You don't know JS yet(3-2~4) 클로저,this,prototype

by dailycoding777 2025. 2. 13.

클로저 전에 알아야 할 내용

🔥 콜 스택(Call Stack)이 뭐냐?

  • 자바스크립트 엔진이 함수 실행을 관리하는 공간
  • LIFO(Last In, First Out) 구조 → 마지막에 들어온 함수가 먼저 실행 완료됨
  • 함수가 호출될 때 스택에 쌓이고, 실행이 끝나면 스택에서 제거됨

⚙️ 콜 스택 동작 방식

  1. 함수 호출 → 콜 스택에 push(쌓임)
  2. 함수 실행 완료 → 콜 스택에서 pop(제거)
  3. 비동기 함수 (setTimeout, fetch 등) → 콜 스택에서 빠지고 Web APIs → Task Queue → Event Loop 통해 실행

Heap 영역이 뭐냐?

  • 런타임 중에 동적으로 메모리 할당하는 공간
  • 크기가 가변적이라 필요할 때 메모리를 할당하고, 필요 없으면 해제됨
  • JS에서 객체(Object)나 함수(Function) 같은 데이터가 저장됨
  • 스택(Stack)보다 크지만 속도는 상대적으로 느림

⚙️ Heap & Garbage Collection (GC)

  • JS는 자동 메모리 관리 (Garbage Collector) 가 해줌
  • 가비지 컬렉션(GC) → 필요 없는 메모리 정리하는 기능
  • Mark & Sweep 알고리즘 사용
    1. Reachable(도달 가능한) 객체 찾음 (ex. 글로벌 변수, 함수 실행 중인 객체)
    2. 도달 불가능한 객체는 제거해서 메모리 해제

📝 콜 스택(Call Stack) vs 힙(Heap) vs 가비지 컬렉션(GC) 비교 표

콜 스택(Call Stack) 힙(Heap) 가비지 컬렉션(GC)

역할 함수 실행 순서 관리 동적 메모리 저장소 (객체, 배열, 함수 등) 필요 없는 메모리 자동 정리
구조 LIFO (Last In, First Out) 비정형 구조 (메모리 주소 기반) Mark & Sweep 알고리즘 사용
저장 데이터 원시값 (숫자, 문자열), 함수 호출 정보 객체, 배열, 함수 등 동적 데이터 도달 불가능한(사용되지 않는) 데이터
속도 빠름 (고정 크기, 단순한 구조) 상대적으로 느림 (가변 크기, 탐색 필요) 자동 실행되지만 성능 부담 가능
메모리 해제 함수 실행 종료 시 자동 해제 GC가 직접 관리 주기적으로 실행되며, 메모리 누수 방지
오류 가능성 Stack Overflow (무한 재귀 등) 메모리 누수 가능 (참조 남아 있으면 GC 해제 X) 너무 자주 실행되면 성능 저하 가능
예제 js function a() { b(); } function b() { c(); } function c() {} a(); js let obj = { name: "나" }; js let obj = { name: "나" }; obj = null; // GC가 정리!

🎯 한 줄 요약

  • 콜 스택함수 실행을 관리 (LIFO, 빠름, 스택 오버플로우 주의)
  • 동적 메모리 저장 공간 (객체, 배열 저장, 느림, GC 필요)
  • 가비지 컬렉션(GC)필요 없는 메모리 자동 정리 (Mark & Sweep 방식)

3.2 클로저(Closure)

함수가 생성될 때 , 외부 변수(환경)을 기억하고 계속 접근할 수 있는 기능

거의 모든 JS 개발자는 클로저를 이용하고 있음

사실 클로저는 JS에만 있는 기능이 아니라 주요 프로그래밍 언어에서 사용함

📌 즉, 함수가 생성된 시점의 변수 값을 나중에도 기억하고 사용할 수 있음!

📌 JavaScript에서 함수는 "어디에서 선언되었는가"를 기억함

이 spirit 을 참조하는 함수가 있어서 spirit은 삭제되지 않고 Heap으로 옮겨짐.

Heap에서는 가비지 컬렉터가 참조되지 않는 데이터를 수거하는데 ,

coco 변수에 저장된 이 함수가 프로그램이 돌아가는 동안은 사라지지 않는다.

**function outerFunction() {
  let outerVariable = "나는 외부 변수!";

  function innerFunction() {
    console.log(outerVariable); // 바깥 함수의 변수를 기억하고 있음!
  }

  return innerFunction; // 내부 함수 반환
}

const closureFunc = outerFunction(); // outerFunction 실행 후 innerFunction 반환
closureFunc(); // "나는 외부 변수!"**
  • outerFunction() 실행이 끝났지만, interFunction() 이 outerVariable 을 기억하고 있음
  • 함수가 생성될 당시의 환경을 저장하는 것

3️⃣ 클로저의 핵심 특징

특징 설명

1. 내부 함수가 외부 함수의 변수를 기억함 innerFunction()이 outerVariable을 계속 사용할 수 있음
2. 외부 함수 실행이 끝나도 변수를 유지 outerFunction()이 종료된 후에도 outerVariable이 살아있음
3. 데이터를 은닉하고 보호 가능 클로저를 사용하면 특정 데이터에 대한 접근을 제한 가능

4️⃣ 클로저의 활용 예제

🔹 1. 데이터 은닉 (Private 변수)

function createCounter() {
  let count = 0; // 외부에서는 직접 접근 불가

  return {
    increment: function () {
      count++;
      console.log(count);
    },
    decrement: function () {
      count--;
      console.log(count);
    },
    getCount: function () {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined (직접 접근 불가!)

✔ count 변수는 직접 접근 불가능하지만, 내부 함수로 조작 가능

데이터 은닉(캡슐화) 효과를 가짐


🔹 2. 특정 값 유지 (클로저로 상태 저장)

function createAdder(value) {
  return function (num) {
    return num + value; // 클로저를 이용해 `value`를 기억
  };
}

const addFive = createAdder(5);
console.log(addFive(10)); // 15
console.log(addFive(20)); // 25

value 값이 클로저 내부에 저장되어 유지됨!

새로운 createAdder(10)을 만들면 다른 값도 유지 가능


🔹 3. 이벤트 핸들러에서 클로저 사용

function attachEventListeners() {
  let count = 0;

  document.querySelector("#button").addEventListener("click", function () {
    count++;
    console.log(`버튼 클릭 횟수: ${count}`);
  });
}

attachEventListeners();

클로저를 이용해 count 값이 이벤트 발생할 때마다 증가하도록 유지됨


🔹 4. setTimeout에서 클로저 활용

function delayedMessage(msg, delay) {
  setTimeout(function () {
    console.log(msg);
  }, delay);
}

delayedMessage("Hello, 클로저!", 2000); // 2초 후에 "Hello, 클로저!" 출력

클로저를 사용하면 setTimeout() 내부에서 msg 값을 계속 유지 가능

5️⃣ 클로저의 메모리 관리 이슈

클로저는 외부 함수의 변수를 계속 참조하기 때문에 메모리가 해제되지 않을 수 있음!

→ 너무 많은 클로저를 남발하면 메모리 누수(Memory Leak)가 발생할 수도 있음

🔹 클로저의 메모리 문제 해결법

✔ 클로저가 더 이상 필요 없을 때 변수를 null로 설정

✔ 너무 많은 클로저를 사용하지 않도록 설계 주의

function createCounter() {
  let count = 0;
  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter = null; // 클로저 참조 제거 (메모리 해제 가능)

6️⃣ 클로저 vs 일반 함수 비교

구분 일반 함수 클로저

변수 접근 함수 내부 변수만 접근 가능 외부 함수의 변수도 기억함
데이터 유지 함수 실행이 끝나면 변수 사라짐 변수를 계속 유지할 수 있음
활용 예시 일반적인 연산, DOM 조작 데이터 은닉, 상태 저장, 이벤트 핸들러, 비동기 처리

7️⃣ 클로저 정리

클로저는 "함수가 선언될 때의 환경을 기억하고, 그 환경에 접근할 수 있는 함수"

외부 함수의 변수를 내부 함수가 계속 유지할 수 있음

데이터 은닉, 상태 저장, 이벤트 핸들러, 비동기 처리 등에 활용 가능

메모리 누수 문제를 주의해야 함

클로저는 이런 언어에서 사용 가능하다.

  • 함수가 일급객체이고 중첩 함수 허용
  • lexical scoping 지원

this 알기전에 실행컨텍스트, 콜스택 차이 먼저 볼게요.

 

🏗️ 1. 실행 컨텍스트 (Execution Context)

➡️ "함수가 실행될 때 생성되는 환경(메모리 공간)"

➡️ 함수가 실행되면 변수, 함수 선언, this 값, 스코프 정보를 저장하는 공간이 생성됨.

실행 컨텍스트가 포함하는 것

  1. 변수 객체(Variable Object, VO) → 함수 내 변수, 함수 선언 저장
  2. 스코프 체인(Scope Chain) → 함수 내부에서 외부 변수에 접근할 수 있도록 관리
  3. this 바인딩 → this 값을 결정

제일 위에 함수가 실행 → 종료되면 제일 위에 실행 컨텍스트가 사라짐

(LIFO 구조)

📌 실행 컨텍스트 예제

function outer() {
  let a = 1;

  function inner() {
    let b = 2;
    console.log(a + b);
  }

  inner();
}
outer();

실행 과정

  1. 전역 실행 컨텍스트 생성 (outer 함수 저장)
  2. outer() 실행 → 실행 컨텍스트 생성
  3. inner() 실행 → 실행 컨텍스트 생성 (외부 outer 스코프 포함)
  4. inner() 실행 종료 → 실행 컨텍스트 삭제
  5. outer() 실행 종료 → 실행 컨텍스트 삭제
  6. 전역 실행 컨텍스트 남음

📚 2. 콜 스택(Call Stack) (LIFO Last In , First Out)

➡️ "실행 컨텍스트를 저장하는 Stack 자료구조"

➡️ **"어떤 함수가 실행 중인지, 어떤 함수가 끝났는지"**를 관리

📌 콜 스택 동작 예제

function first() {
  second();
  console.log("첫 번째 함수 실행");
}
function second() {
  third();
  console.log("두 번째 함수 실행");
}
function third() {
  console.log("세 번째 함수 실행");
}

first();

콜 스택 변화 과정

스택 순서 실행 중인 함수

1️⃣ first() 호출 → 스택에 추가
2️⃣ second() 호출 → 스택에 추가
3️⃣ third() 호출 → 스택에 추가
4️⃣ third() 실행 완료 → 스택에서 제거
5️⃣ second() 실행 완료 → 스택에서 제거
6️⃣ first() 실행 완료 → 스택에서 제거
모든 함수 실행 종료 → 스택이 비어 있음

🎯 🚀 실행 컨텍스트 vs 콜 스택 차이 정리

실행 컨텍스트(Execution Context) 콜 스택(Call Stack)

역할 함수가 실행될 때 변수, this, 스코프 정보를 저장 실행 중인 함수를 관리
형태 메모리 공간 (환경) Stack 자료구조
관계 하나의 실행 컨텍스트가 하나의 함수 실행 환경을 담당 실행 컨텍스트를 스택에 push/pop
종료 시점 함수 실행이 끝나면 삭제됨 스택에서 함수가 pop될 때 함께 사라짐
예제 변수, 함수 선언, this 값 저장 함수 실행 순서 관리

결론

  • 실행 컨텍스트 → 함수 실행을 위한 환경(메모리 공간)
  • 콜 스택 → 실행 컨텍스트를 관리하는 Stack 자료구조
  • 함수 실행 시 → 콜 스택에 실행 컨텍스트가 push
  • 함수 종료 시 → 콜 스택에서 pop되며 실행 컨텍스트 제거

3.3 this 키워드

this는 어떤 객체를 가르키는 키워드다.

근데 그 “어떤”이 뭔지 정의를 내릴 수 없다.

“어떤 객체”는 상황에 따라 다르기 때문이다.

왜 this가 호출 방식에 따라 달라질까?

자바스크립트는 "함수가 어디서 선언되었냐"가 아니라, "어디서 호출되었냐" 를 기준으로 this 값을 결정함.

→ 즉, "실행 컨텍스트" 가 this를 결정하는 핵심 요소임!


📌 실행 컨텍스트(Execution Context)란? ⇒ 함수가 실행되는 환경이다.


  • 함수 호출 방식에 따라 this 값이 달라짐
  • 객체 메서드 → 해당 객체가 this
  • 화살표 함수 → 상위 스코프의 this를 가져옴
  • call, apply, bind로 this 강제 설정 가능

function이라고 정의하면 이 함수는 window 객체에 등록이 된다.


1. this는 어디에 저장되길래 호출 방식마다 다를까?

➡️ 실행 컨텍스트가 생성될 때 this 값이 저장(바인딩)됨.

➡️ "함수가 호출될 때" 실행 컨텍스트가 만들어지고, 그 순간 this가 결정됨.

➡️ this는 실행 컨텍스트의 내부 환경(VO, Lexical Environment) 안에 저장됨.

📌 실행 컨텍스트 구조를 보면 이렇게 생겼음:

실행 컨텍스트 = {
  VariableEnvironment: { ... },
  LexicalEnvironment: { this: ??? },  <-- 여기에 this가 저장됨
  ThisBinding: ???   <-- this를 참조하는 공간
}
  • 실행 컨텍스트가 생성될 때 Lexical Environment 안의 this 값이 정해짐.
  • 함수가 실행될 때, 실행 컨텍스트의 this 값을 참조함.

📌 즉, this는 함수가 실행될 때 실행 컨텍스트 내부에서 저장되기 때문에 호출 방식마다 달라지는 것!


2. "실행 컨텍스트가 생성될 때 자동으로 this가 바인딩됨"

✔️ "자동 바인딩"이란 실행 컨텍스트가 만들어질 때 this 값이 결정(등록)된다는 의미.

✔️ 즉, 어떤 객체가 this가 될지 실행 컨텍스트가 자동으로 결정해줌.

✔️ 이 결정 과정에서 "어떻게 함수가 호출되었는지"가 중요한 역할을 함.


3. "함수가 어떻게 호출되었는지"에 따라 this가 달라지는 이유?

➡️ 함수 실행 시 실행 컨텍스트가 만들어지고, "함수를 호출한 방식"에 따라 this를 어디로 바인딩할지 결정함.

➡️ 즉, "누가 이 함수를 호출했느냐"가 this를 결정함!

📌 🚀 this가 결정되는 원리 (호출 방식마다 다른 이유)

호출 방식 this가 가리키는 값 이유

전역 실행 (window) window (브라우저) 전역 컨텍스트에서 실행되면 this = window
일반 함수 호출 window (엄격 모드 undefined) 실행 컨텍스트 생성 시 기본적으로 전역 객체 바인딩
객체 메서드 호출 해당 객체 . 앞에 있는 객체가 this로 설정됨
생성자 함수 호출 (new 사용) 새로 생성된 객체 new 키워드를 사용하면 새로운 객체가 this가 됨
화살표 함수 상위 스코프의 this 실행 컨텍스트 생성 시 this를 새로 바인딩하지 않음 (Lexical this)
call / apply / bind 사용 지정한 객체 this를 직접 바꿀 수 있음
이벤트 핸들러 이벤트가 발생한 요소 이벤트가 실행된 대상이 this가 됨

💡 🚀 this가 결정되는 진짜 핵심 원리

  • 자바스크립트는 함수를 실행할 때 실행 컨텍스트를 생성하면서 this를 바인딩함.
  • 실행 컨텍스트가 만들어질 때 어떤 방식으로 호출되었는지를 확인하고, 그에 맞는 this를 저장함.
  • 즉, "함수 호출 방식"이 곧 실행 컨텍스트의 this 바인딩 방식을 결정하는 원인!
  • 화살표 함수는 실행 컨텍스트 생성 시 this를 새로 바인딩하지 않음 → 상위 스코프 this를 그대로 사용!
// 대충이름.tsx
// this => window로
// this => 화살표 함수  
// function 선언
// 함수 표현식 => const 함수선언 = function(){}
// 함수표현식 (설탕친게 화살표) const 함수설탕 = () => { }
// this를 사용할 때는 함수 표현식은 화살표함수랑 달라요.
// 화살표 =>this 대충이름 , 함수표현식 => window
export function 대충이름(){
	const arrow = () => {
			this.dddd
		return 어쩌구
		
	}
}

**그래서 화살표 함수는 this를 의도적으로 컨트롤하기에 최적이다.

(함수표현식은 안됨)**

➡️ 화살표 함수는 실행 컨텍스트에서 this를 새로 바인딩하지 않음

➡️ 즉, 화살표 함수 내부에서 this를 사용하면 상위 스코프의 this를 그대로 참조함.

➡️ this가 바뀌는 걸 방지할 때 유용함!


🔥 왜 화살표 함수가 this를 다루기 좋을까?

  • 일반 함수는 호출 방식에 따라 this가 달라져서 실수할 가능성이 높음.
  • 화살표 함수는 this를 상위 컨텍스트에서 가져오기 때문에 예측 가능하고 안전함!
  • 특히 콜백 함수나 이벤트 핸들러에서 this가 변하는 걸 막을 때 좋음.

📌 일반 함수 vs 화살표 함수의 this 차이

const obj = {
  name: "JavaScript",
  normalFunc: function () {
    console.log("일반 함수 this:", this); // obj
  },
  arrowFunc: () => {
    console.log("화살표 함수 this:", this); // window (obj가 아님!)
  }
};

obj.normalFunc(); // this = obj
obj.arrowFunc();  // this = window (객체가 아닌 상위 스코프 참조!)

📌 일반 함수는 obj를 가리키지만, 화살표 함수는 window를 가리킴!

📌 화살표 함수는 this를 새로 바인딩하지 않기 때문에 obj를 가리키지 않음.


🎯 화살표 함수가 유용한 경우

1️⃣ setTimeout에서 this 유지

const obj = {
  name: "JavaScript",
  sayLater: function () {
    setTimeout(() => {
      console.log("화살표 함수 this:", this.name); // this가 obj를 유지!
    }, 1000);
  }
};
obj.sayLater();

화살표 함수 사용 → this가 obj를 유지!

❌ 일반 함수였다면 this가 window가 됨.


2️⃣ 이벤트 핸들러에서 this 고정

const obj = {
  name: "JavaScript",
  handleEvent: function () {
    document.getElementById("btn").addEventListener("click", () => {
      console.log("화살표 함수 this:", this.name); // this가 obj를 유지!
    });
  }
};
obj.handleEvent();

화살표 함수 사용 → this가 obj를 유지!

❌ 일반 함수였다면 this가 클릭한 버튼 요소가 됨.


🚀 결론

✔️ 화살표 함수는 this를 새로 바인딩하지 않음 → 상위 스코프의 this를 유지

✔️ setTimeout, 이벤트 핸들러, 콜백 함수에서 this가 바뀌는 문제를 방지할 때 유용

✔️ 일반 함수는 this가 호출 방식에 따라 달라지지만, 화살표 함수는 예측 가능함

this를 의도적으로 유지하고 싶다면 화살표 함수가 최고다.


3.4 프로토 타입

🔥 자바스크립트 프로토타입(Prototype) 개념 정리

➡️ 자바스크립트는 프로토타입 기반(prototype-based) 언어

➡️ 클래스가 아니라 프로토타입을 통해 객체가 상속을 받음

➡️ 프로토타입 체인(prototype chain)을 이용해 속성과 메서드를 찾음


1️⃣ 프로토타입(Prototype)이란?

  • 부모자식지간 객체다. 이말임
  • 모든 객체는 prototype이라는 숨겨진 링크(참조)를 가짐
  • 이 prototype이 다른 객체(부모 객체)로부터 속성과 메서드를 상속받음
  • 자바스크립트의 상속 구조는 프로토타입을 통해 구현됨

📌 프로토타입 예제

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  console.log(`Hello, my name is ${this.name}`);
};

const user1 = new Person("Minho");
user1.sayHello(); // "Hello, my name is Minho"

Person 생성자의 prototype에 sayHello를 추가하면 모든 Person 객체가 이를 상속받음.


2️⃣ 프로토타입 체인(Prototype Chain)이란?

➡️ 객체에서 프로퍼티나 메서드를 찾을 때 해당 객체에 없으면 프로토타입을 따라 올라가면서 찾음

➡️ 최종적으로 Object.prototype까지 올라가고, 그래도 없으면 undefined 반환

📌 프로토타입 체인 예제

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log(`${this.name}가 소리를 냅니다.`);
};

const dog = new Animal("멍멍이");
dog.speak(); // "멍멍이가 소리를 냅니다."

console.log(dog.toString()); // `toString`은 Object.prototype에 정의됨

✅ dog.speak()를 호출할 때 dog 객체 → Animal.prototype → Object.prototype 순서로 찾아감.

✅ dog.toString()을 호출할 때도 자신에게 없으면 프로토타입 체인을 따라 Object.prototype에서 찾음


3️⃣ Object.create()란? (프로토타입을 직접 설정하는 방법)

➡️ Object.create(proto)를 사용하면 직접 프로토타입을 설정하여 객체를 생성할 수 있음

📌 Object.create() 예제

const parent = {
  greet() {
    console.log("안녕하세요!");
  }
};

const child = Object.create(parent); // parent를 프로토타입으로 하는 객체 생성
child.greet(); // "안녕하세요!"  (부모 객체에서 상속받음)

✅ Object.create(parent)를 사용하면 child 객체가 parent를 프로토타입으로 가짐

✅ 즉, child에는 greet()이 없지만 프로토타입 체인(parent)을 통해 찾음


🎯 🚀 최종 정리

✔️ JS는 클래스 기반 X → 프로토타입 기반 O

✔️ 프로토타입 → 객체가 상속받는 부모 객체

✔️ 프로토타입 체인 → 객체에서 프로퍼티를 찾을 때 상위 프로토타입을 따라가며 검색

✔️ Object.create() → 프로토타입을 직접 설정하여 객체를 생성하는 방법

자바스크립트는 클래스를 사용하지 않고도 프로토타입을 통해 상속을 구현할 수 있음!

'Study > JavaScript' 카테고리의 다른 글

You don't know JS yet(2장 2.1~2.2)  (1) 2025.02.27
You don't know JS yet(1장 4)  (1) 2025.02.20
You don't know JS yet(3-1) 이터레이션  (0) 2025.02.13
You don't know JS yet(2)  (0) 2025.02.06
You don't know JS yet(1.5~1.8)  (0) 2025.01.23