싯벨트 2025. 3. 15. 09:12
728x90

this 키워드

객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적 단위로 묶은 복합적인 자료구조입니다. 동작을 나타내는 메서드는 프로퍼티를 참조하고 변경할 수 있어야 하며, 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 합니다. 이때, this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 식별자, 즉 자기 참조 변수(self-referencing variable)입니다.

this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조할 수 있습니다. 단, this가 가리키는 값(this 바인딩)은 함수 호출 방식에 의해 동적으로 결정됩니다.

this 바인딩

바인딩은 식별자와 값을 연결하는 과정을 의미합니다. 변수 선언은 식별자와 확보된 메모리 공간의 주소를 바인딩하는 것이고, this 바인딩은 this와 this가 가리킬 객체를 바인딩하는 것입니다.

함수 호출 방식과 this 바인딩

this 바인딩은 함수 호출 방식, 즉 함수가 어떻게 호출되었는지에 따라 동적으로 결정됩니다. 함수 호출 방식에는 다음 4가지가 있으며, 하나씩 살펴보겠습니다.

함수 호출 방식

  1. 일반 함수 호출 - 전역
  2. 메서드 호출 - 호출한 객체
  3. 생성자 함수 호출 - 생성할 인스턴스
  4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출 - 인수에 의해 결정
const foo = function () {
    console.log(this);
};
// 1. 일반 함수 호출
foo(); // global - 전역 객체

// 2. 메서드 호출
const obj = { foo };
obj.foo(); // obj

// 3. 생성자 함수 호출
new foo(); // foo {}

// 4. Function.prototype 메서드에 의한 간접 호출
const bar = { name: "bar" };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar

일반 함수 호출

일반 함수로 호출되면 기본적으로 this에는 전역 객체가 바인딩됩니다. 메서드 내부 콜백 함수에서도 this는 기본적으로 전역 객체지만, 아래 3가지 방법을 통해서 this 바인딩을 일치시킬 수 있습니다.

 

1. this 바인딩을 변수에 할당하고, 그것을 활용

아래 코드에서는 that 이라는 변수에 this를 할당하고, 콜백 내부에서 사용했습니다.

let value = 1;
const obj = {
    value: 2,
    foo() {
        const that = this;
        setTimeout(function () {
            console.log(that.value); // 2
        }, 100);
    },
};
obj.foo();

 

2. Function.prototype.bind 메서드를 사용하여 명시적으로 바인딩

let value = 1;
const obj = {
    value: 2,
    foo() {
        setTimeout(
            function () {
                console.log(this.value); // 2
            }.bind(this),
            100
        );
    },
};
obj.foo();

 

3. 화살표 함수 사용

화살표 함수는 자신만의 this를 가지지 않습니다. 그렇기 때문에 화살표 함수 내부의 this는 상위 스코프의 this를 상속 받으며, 이를 Lexical this 라고 부릅니다.

let value = 1;
const obj = {
    value: 2,
    foo() {
        setTimeout(() => console.log(this.value), 100); // 2
    },
};
obj.foo();

메서드 호출

메서드 내부의 this는 메서드를 호출할 때 메서드 이름 앞의 마침표(.)연산자 앞에 기술한 객체가 바인딩됩니다. 메서드를 소유한 객체가 아니라 메서드를 호출한 객체에 바인딩됩니다. 이것이 가능한 이유는 메서드는 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체이기 때문입니다. 단지 해당 프로퍼티가 함수 객체를 가리키고 있을 뿐이죠. 그래서 다른 객체의 메서드가 될 수도 있고, 일반 함수로 호출될 수도 있습니다.

const person = {
    name: "Lee",
    greet() {
        return `Hi, ${this.name}`;
    },
};
console.log(person.greet()); // Hi, Lee

const anotherPerson = {
    name: "Kim",
};
anotherPerson.greet = person.greet;
console.log(anotherPerson.greet()); // Hi, Kim

const greetFn = person.greet;
console.log(greetFn()); // Hi, undefined

생성자 함수 호출

생성자 함수 내부의 this는 생성할 인스턴스에 바인딩됩니다.

Function.prototype.apply/call/bind 메서드에 의한 간접 호출

apply()와 call() 메서드는 인수를 전달하는 방식이 다르고, bind() 메서드는 함수를 호출하지는 않으므로, 반환값을 얻으려면 호출을 해줘야 합니다.

function getThisBinding() {
    return this;
}
// this로 사용할 객체
const thisArg1 = { a: 1 };
console.log(getThisBinding.apply(thisArg1, [1, 2, 3])); // {a: 1}
console.log(getThisBinding.call(thisArg1, 1, 2, 3)); // {a: 1}
console.log(getThisBinding.bind(thisArg)); // [Function: bound getThisBinding]
console.log(getThisBinding.bind(thisArg)()); // {a: 1}

참고자료

  • 모던 자바스크립트 Deep Dive