easthxxn

면접에서 this 물어보면 이렇게 답하겠다

·
#javascript#this#interview#prototype
JS 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라는 함수 객체는 하나인데, kimlee도 자기 프로퍼티로 참조하고 있다.

함수가 객체이기 때문에 여러 곳에서 공유될 수 있고, "누가 나를 호출했는가"를 런타임에 동적으로 결정해야 한다.

여기서 흔한 실수가 나온다.

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으로 고정

함수가 단순한 코드 블록이 아니라 객체이기 때문에 가능한 거다.

callapply는 즉시 실행, bind는 this가 고정된 새 함수를 반환한다.

new 바인딩

function Person(name) {
  this.name = name;
}
 
const p = new Person("Choi");
console.log(p.name); // 'Choi'

new가 하는 일:

  1. 빈 객체를 생성한다
  2. 그 객체의 [[Prototype]]Person.prototype에 연결한다
  3. Person 함수를 호출하면서 this를 새 객체에 바인딩한다
  4. 함수가 객체를 반환하지 않으면 새 객체를 반환한다

핵심은 3번이다.

함수는 그냥 함수 객체일 뿐인데, new가 this를 새 객체로 바인딩해서 생성자처럼 동작하게 만든다.

this 바인딩 규칙 정리

호출 방식this이유
fn()globalThis (strict: undefined)소유자 없이 호출 → 기본값
obj.fn()obj. 왼쪽이 소유자
fn.call(ctx) / applyctx함수 객체의 메서드로 명시 지정
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'

speakdog에 없다.

프로토타입 체인을 타고 Animal.prototype에서 찾는다.

그런데 thisAnimal.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 컨텍스트를 유지하기 위한 설계입니다.

규칙만 외우면 금방 까먹는다.

왜 존재하는지 이해하면 규칙은 자연스럽게 따라온다.

댓글

0/100

불러오는 중...