카테고리 없음

this, 클로저 사실 나도 잘 몰라...ㅡJavaScript 완전 정복

taek2-0310 2026. 5. 22. 16:53

this란? 🔍

this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다. 가장 일반적으로는 객체의 메서드 안에서 사용되며 이를 통해 동일한 메서드를 서로 다른 객체에서 재사용할 수 있다.

일반 변수는 선언된 위치(렉시컬 스코프)에 의해 값이 결정되지만 this는 다르다. 함수가 어떤 방식으로 호출(바인딩)되었는지에 따라 가리키는 값이 동적으로 결정된다.

쉽게 비유하자면 "나"라는 단어와 같다. "나는 밥을 먹었다"에서 '나'가 누구인지는 그 말을 누가 했느냐에 따라 달라지는 것처럼 this도 누가(어떤 컨텍스트가) 함수를 호출했느냐에 따라 달라진다.

 

this가 결정되는 4가지 규칙 🚨

1. 기본 바인딩

아무런 컨텍스트 없이 함수를 단독 호출하면 this는 전역 객체를 가리킨다. 그리고 이 this는 브라우저에서 window 객체가 된다.

function greet() {
  console.log(this); // window (브라우저)
}

greet();

 

2. 암시적 바인딩

객체의 메서드로 호출되면 this는 그 객체를 가리킨다.

const user = {
  name: '철수',
  greet() {
    console.log(this.name); // '철수'
  }
};

user.greet(); // this === user

 

3. 명시적 바인딩

함수가 호출될 때 apply, call 또는 bind가 사용되었다면 첫번째 인자로 전달하는 값에 this 를 바인딩 한다.

bind는 새로운 함수를 반환하며 this를 영구 고정한다는 점에서 call/apply와 다르다.

jsfunction greet() {
  console.log(this.name);
}

const user = { name: '철수' };

greet.call(user);        // '철수'
greet.apply(user);       // '철수'
const bound = greet.bind(user);
bound();                 // '철수'

 

4. new 바인딩

new 키워드로 생성자 함수를 호출하면, this는 새로 생성되는 인스턴스 객체를 가리킨다

function Person(name) {
  this.name = name; // 새 인스턴스에 바인딩
}

const p = new Person('민준');
console.log(p.name); // '민준'

 

❗️우선순위: new > 명시적(bind) > 암시적(메서드) > 기본❗️

화살표 함수의 this — 완전히 다른 규칙❓

화살표 함수는 this를 아예 갖지 않는다. 대신 자신이 선언된 위치의 외부 스코프 this를 그대로 캡처한다.(클로저)

const person = {
	name : 'Seo',
    sayName : function() {
    	innerFun = function() {
        	return `안녕하세요 ${this.name}님`
         }
       console.log(innerFun())  /// 안녕하세요 ''님
    }
 }
 
 person.sayName()

innerFun 함수 앞에 마침표(.) 을 붙여서 호출하지도 않았고, bind, call, apply 를 사용하지도 않았다. 일반 함수 호출 되었기 때문에 여기서 this 는 window가 된다.

const person = {
	name : 'Seo',
    sayName : function() {
    	innerFun =() => {
        	return `안녕하세요 ${this.name}님` /// 안녕하세요 Seo님
         }
       console.log(innerFun())
    }
 }
 
 person.sayName()

여기서도 innerFun 은 일반함수로 호출 되었으나 innerFun 이 화살표 함수로 선언이 되어 있다. 화살표 함수에서의 this 는 자신의 상위 스코프를 따르기 때문에 여기서 this 는 person 객체 안에 선언된 name 이 된다.

클로저 - 기억하는 함수 📸

클로저의 정의

MDN은 클로저를 이렇게 정의한다.

"A closure is the combination of a function and the lexical environment within which that function was declared." 클로저는 함수와 그 함수가 선언된 렉시컬 환경의 조합이다.

풀어 말하면 외부 함수가 종료된 후에도 내부 함수가 외부 함수의 변수를 참조할 수 있는 현상이다.

 

이게 왜 가능하냐면 함수 객체는 생성될 때 [[Environment]]라는 내부 슬롯에 자신이 정의된 위치의 상위 스코프 참조를 저장한다.(함수가 살아있는 한 유지)

const x = 1;

function outer() {
  const x = 10;
  const inner = function() { console.log(x); }; // [[Environment]] = outer의 렉시컬 환경
  return inner;
}

const innerFunc = outer(); // outer 실행 컨텍스트는 스택에서 제거
innerFunc(); // 10 — 그러나 x는 여전히 참조 가능

outer의 실행 컨텍스트가 콜 스택에서 제거되어도 inner의 [[Environment]]가 outer의 렉시컬 환경을 참조하고 있기 때문에 가비지 컬렉션의 대상이 되지 않는다.

클로저라고 부르는 기준

 

  • 상위 스코프의 식별자를 참조한다
  • 외부 함수보다 더 오래 유지된다 (외부로 반환된다)
function foo() {
  const x = 1;

  // 클로저 — 상위 스코프 참조 + 외부로 반환
  function bar() {
    console.log(x);
  }
  return bar;
}

 

클로저 왜 쓰지?

클로저의 핵심 활용: 상태 은닉

클로저를 실전에서 가장 많이 쓰는 이유는 외부에서 접근할 수 없는 private 상태를 만들기 위해서다.

// 전역 변수 방식 — 누구나 변경 가능한 취약한 구조
let num = 0;
const increase = function() { return ++num; };
// 클로저 방식 — num은 외부에서 직접 접근 불가
const increase = (function() {
  let num = 0;
  return function() {
    return ++num;
  };
}());

increase(); // 1
increase(); // 2
increase(); // 3

즉시 실행 함수(IIFE)로 num을 감싸면 외부에서 직접 접근이 불가능해진다. 반환된 함수만이 num을 변경할 수 있다.

증가와 감소 모두 필요하다면 메서드를 가진 객체를 반환하면 된다:

const counter = (function() {
  let num = 0;

  return {
    increase() { return ++num; },
    decrease() { return num > 0 ? --num : 0; }
  };
}());

counter.increase(); // 1
counter.increase(); // 2
counter.decrease(); // 1

장점은 

  • 전역변수 사용의 최소화
  • 데이터 보존 가능
  • 모듈화를 통한 코드 재사용에 편리
  • 정보의 접근 제한 (캡슐화)
  this 클로저
결정 시점 함수 호출 시점 함수 정의 시점
핵심 질문 누가 이 함수를 호출했나? 어디서 이 함수가 정의됐나?