면접에서 this 물어보면 이렇게 답하겠다
우리 리더가 경력 면접에 들어가면 this를 꼭 물어본다고 한다.
나한테 물어보면 어떻게 답할까 고민하다 보니, 글로 정리하게 됐다.
결론부터 말하면 this는 함수가 객체이기 때문에 존재한다.
함수는 객체다
JS에서 함수는 일급 객체(First-class Object)다.
프로퍼티를 가질 수 있고, 변수에 담기고, 인자로 넘겨지고, 어디서든 호출된다.
Java나 C++에서 메서드는 클래스에 귀속된다.
this가 항상 인스턴스를 가리키는 게 당연하다.
JS는 다르다.
function greet() {
console.log(this.name);
}greet는 어디에도 소속되지 않은 독립적인 Function 객체다.
함수가 객체라서 어디든 돌아다닐 수 있다면, 실행될 때 자기가 누구 소속인지를 어떻게 알 수 있을까?
이게 this가 존재하는 이유다.
소유자는 호출 시점에 결정된다
함수는 정의 시점에 소유자가 고정되지 않는다.
const kim = { name: "Kim", greet };
const lee = { name: "Lee", greet };
kim.greet(); // this → kim → 'Kim'
lee.greet(); // this → lee → 'Lee'greet라는 함수 객체는 하나인데, kim도 lee도 자기 프로퍼티로 참조하고 있다.
함수가 객체이기 때문에 여러 곳에서 공유될 수 있고, "누가 나를 호출했는가"를 런타임에 동적으로 결정해야 한다.
여기서 흔한 실수가 나온다.
const fn = kim.greet;
fn(); // undefined함수를 꺼내는 순간 호출 주체가 사라진다.
변수에 할당하면 함수 객체에 대한 참조만 복사되고, kim과의 연결은 끊긴다.
call, apply, bind — 함수가 객체라서 가능하다
함수가 객체라서 메서드를 가질 수 있다.
Function.prototype에 있는 call, apply, bind가 바로 그거다.
greet.call(kim); // this를 kim으로 명시
greet.apply(lee); // this를 lee로 명시
const bound = greet.bind(kim);
bound(); // this가 kim으로 고정함수가 단순한 코드 블록이 아니라 객체이기 때문에 가능한 거다.
call과 apply는 즉시 실행, bind는 this가 고정된 새 함수를 반환한다.
new 바인딩
function Person(name) {
this.name = name;
}
const p = new Person("Choi");
console.log(p.name); // 'Choi'new가 하는 일:
- 빈 객체를 생성한다
- 그 객체의
[[Prototype]]을Person.prototype에 연결한다 Person함수를 호출하면서this를 새 객체에 바인딩한다- 함수가 객체를 반환하지 않으면 새 객체를 반환한다
핵심은 3번이다.
함수는 그냥 함수 객체일 뿐인데, new가 this를 새 객체로 바인딩해서 생성자처럼 동작하게 만든다.
this 바인딩 규칙 정리
| 호출 방식 | this | 이유 |
|---|---|---|
fn() | globalThis (strict: undefined) | 소유자 없이 호출 → 기본값 |
obj.fn() | obj | . 왼쪽이 소유자 |
fn.call(ctx) / apply | ctx | 함수 객체의 메서드로 명시 지정 |
fn.bind(ctx)() | ctx | 새 함수 객체를 만들어 고정 |
new Fn() | 새로 생성된 인스턴스 | 생성자 패턴 |
() => {} | 선언 시점의 외부 this | 자체 this 바인딩 없음 (lexical) |
프로토타입과 this
JS는 프로토타입 기반 언어다.
클래스가 아니라, 객체가 다른 객체를 직접 상속한다.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + " makes a sound");
};
const dog = new Animal("Dog");
dog.speak(); // 'Dog makes a sound'speak은 dog에 없다.
프로토타입 체인을 타고 Animal.prototype에서 찾는다.
그런데 this는 Animal.prototype이 아니라 dog을 가리킨다.
메서드가 어디에 정의되어 있는지가 아니라, 누가 호출했는지가 this를 결정한다.
이게 프로토타입 상속이 작동하는 핵심이다.
메서드는 프로토타입에 한 벌만 두고, this로 인스턴스를 구분한다.
만약 this가 정적으로 바인딩됐다면 — 함수가 정의된 곳의 객체를 가리켰다면 — this는 항상 Animal.prototype을 가리킬 거다.
프로토타입 기반 상속이 성립하지 않는다.
this가 동적으로 바인딩되는 건 프로토타입 언어의 필수 조건이다.
화살표 함수는 왜 다른가
화살표 함수는 "함수가 객체라서 돌아다닌다 → this가 동적이다"라는 문제를 아예 회피한 설계다.
자체 this를 갖지 않고, 선언된 렉시컬 스코프의 this를 그대로 캡처한다.
const team = {
name: "Frontend",
members: ["Dawn", "Alex"],
printMembers() {
this.members.forEach((m) => {
console.log(`${m} belongs to ${this.name}`);
// 화살표 함수라서 this === team (외부 스코프의 this)
});
},
};ES6 이전에는 const self = this나 .bind(this)로 우회했다.
화살표 함수는 "나는 독립 객체가 아니라, 상위 스코프에 종속된 표현식이다"라고 선언하는 거다.
함수의 두 가지 성격 — 독립 객체 vs 스코프 종속 표현식 — 을 문법으로 구분한 셈이다.
면접에서 답한다면
한 문장으로 요약하면 이거다.
JS에서 함수는 객체이기 때문에 어디든 전달·공유될 수 있고, 그래서 실행 문맥을 런타임에 결정하는 메커니즘이 필요한데, 그게 this다.
this가 뭐예요?
이렇게 답하겠다.
JS에서 함수는 독립적인 객체입니다. 어떤 객체에 소속되지 않기 때문에, 함수를 실행할 때 호출 주체를 알려주는 장치가 필요합니다. 그게 this입니다.
this는 함수가 호출되는 시점에 동적으로 결정되고, 기본 바인딩 · 암시적 바인딩 · 명시적 바인딩 · new 바인딩 4가지 규칙이 있습니다.
동적 바인딩은 프로토타입 기반 상속의 필수 조건이기도 합니다. 메서드가 프로토타입 체인 어딘가에 정의되어 있더라도, this가 호출한 인스턴스를 가리켜야 상속이 동작합니다.
arrow function은 이 규칙의 예외입니다. 자체 this가 없고 선언 시점의 외부 스코프 this를 캡처합니다. 콜백에서 this 컨텍스트를 유지하기 위한 설계입니다.
규칙만 외우면 금방 까먹는다.
왜 존재하는지 이해하면 규칙은 자연스럽게 따라온다.
댓글
불러오는 중...