[JS] this 바인딩에 대해 알아보자

일반 함수와 화살표 함수에서의 this 바인딩을 구분해 보자

this란?

일반적으로 대부분의 언어에서 this 키워드는 자기 자신의 인스턴스를 가리키는 용어이다.(python에서는 self 키워드)

하지만, javascript의 this 키워드는 다른 언어와는 다른 방식으로 작동한다.

* chrome 브라우저 환경에서 테스트한 결과입니다. node의 경우 window 전역 객체 대신 global 전역 객체를 사용하는데, global이 제대로 반환되지 않아서 브라우저 환경에서 진행하였습니다.

Java에서의 this

class Test {
    public int number;

    public void setNumber(int number){
        this.number = number
    }
}

class Main {
    public static void main(String[] args){
        Test t1 = new Test();
        Test t2 = new Test();

        t1.setNumber(1);
        t2.setNumber(2);

        System.out.println(t1.number); // 1
        System.out.println(t2.number); // 2
    }
}

당연하게도 Java에서의 this는 Test 인스턴스를 가리킨다는 것을 알 수 있다. 그렇다면 js에서는 어떻게 작동할까?

javascript에서의 this

this 바인딩 규칙

우선 설명 이전에, 바인딩 규칙부터 알아보자.

  1. 일반 함수(function 키워드 사용)에서 사용된 this는 해당 함수를 호출하는 객체를 가리킨다.
  2. 화살표 함수에서 사용된 this는 block을 타고 올라갔을 때, 맨 처음 만나는 일반 함수의 this를 가리킨다.
  3. new 키워드를 사용할 경우 생성자 함수가 객체 그 자체가 된다. 생성자 함수는 일반 함수여야한다. 생성자 함수 호출 시 this는 무조건 자기 자신이다.

첫 번째 규칙 - 일반 함수의 this

일반 함수(function 키워드 사용)에서 사용된 this는 해당 함수를 호출하는 객체를 가리킨다.

const test = {
    setNumber: function(number) {
        this.number = number
        return this
    }
}

const result = test.setNumber(1)

console.log(test.number)        // 1
console.log(result === test)    // true

해당 방식은 다른 언어와 유사하게 작동하는 것을 알 수 있다.

규칙에 맞게 따져보자.

  • setNumber 함수가 일반 함수로 선언되어 있음.
  • setNumber 함수를 test 객체가 호출함.
    • ex. test.setNumber(1)
  • -> setNumber 에서 this = test

그렇다면 setNumber 함수를 다른 객체에서 사용하면 어떻게 될까?

const test = {
    setNumber: function(number) {
        this.number = number
        return this
    }
}

const other = {}
other.setNumber = test.setNumber

const result = other.setNumber(1)

console.log(test.number)        // undefined
console.log(other.number)       // 1
console.log(result === test)    // false
console.log(result === other)   // true
  • testsetNumberothersetNumber 로 옮김.
  • other.setNubmer(1) 을 실행.
  • -> this = other 임을 알 수 있음.

즉, 규칙에 따라, this는 일반함수를 호출하는 객체임을 알 수 있다.

두 번째 규칙 - 화살표 함수의 this

화살표 함수에서 사용된 this 는 block 계층을 타고 올라갔을 때, 맨 처음 만나는 일반 함수의 this 를 가리킨다.

const test = {
    setNumber: (number) => {
        this.number = number
        return this
    }
}

const result = test.setNumber(1)
console.log(test.number)        // undefined
console.log(result === test)    // false
console.log(result === window)  // true

해당 코드를 보면, setNumber 내부의 this 가 window 객체임을 알 수 있다. 왜 이런 현상이 발생한 것 일까?

규칙에 맞게 한번 따져보자,

  • setNumber 는 화살표 함수임.
  • setNumber 의 상위 블록은 전역 객체인 window 임.
  • 맨 처음 만나는 일반 함수가 없었으므로, window = this 임을 알 수 있음.

이번엔 일반함수가 있는 경우를 알아보자

const test = {
    setNumber: function (number) {
        const run = (number) => {
            this.number = number
            return this
        }
        return run(number)
    }
}

const result = test.setNumber(1)
console.log(test.number)        // 1 
console.log(result === test)    // true
console.log(result === window)  // false

이번 코드의 결과는 위의 결과와 완전히 정반대라는 것을 알 수 있다.

다시한번 규칙에 맞게 따져보자.

  • setNumber 는 일반 함수임.
  • setNumber 내부에 run 이라는 화살표 함수가 존재함.
  • setNumberrun 을 실행한 후 결과를 반환함.
  • run 의 상위 계층 블록은 setNumber이고, setNumber 의 상위 계층 블록은 window 임.
  • run 에게는 setNumber 가 상위 계층 블록 중 처음 만나는 일반 함수이므로 setNumberthis 가 바로 runthis 임.
  • setNumber 를 호출한 객체는 test 객체임
  • 그러므로 this = test 임을 알 수 있음.

세 번째 규칙 - new 키워드

new 키워드를 사용할 경우 생성자 함수가 객체 그 자체가 된다. 생성자 함수는 일반 함수여야한다. 생성자 함수 호출 시 this는 무조건 자기 자신이다.

class Test {
    setNumber(number) {
        this.number = number
        return this
    }
}

const test = new Test()
const result = test.setNumber(1)

console.log(test.number)        // 1
console.log(result === test)    // true

해당 코드는 setNumber가 일반함수이므로 당연히 thistest 임을 알 수 있다.

그런데 만약 아래와 같은 코드가 있다면 어떨까?

class Test {
    setNumber = (number) => {
        this.number = number
        return this
    }
}

const test = new Test()
const result = test.setNumber(1)

console.log(test.number)        // 1
console.log(result === test)    // true

해당 코드는 setNumber가 화살표 함수이므로, 앞서 말했던 것 처럼 thiswindow가 될 것 같지 않은가?

그렇지 않다. 위 코드는 아래와 같은 코드이다.

const Test = function () {
    this.setNumber = (number) => {
        this.number = number
        return this
    }
}

const test = new Test()
const result = test.setNumber(1)

console.log(test.number)        // 1
console.log(result === test)    // true

js에서는 함수에 new를 붙여버리면, 그 함수가 생성자 함수가 되어버린다. 그리고 new는 일반 함수에만 붙일 수 있다.

그렇다면 setNumber의 맨 처음 만나는 상위 블록의 일반 함수가 생성자이고, 생성자의 호출시 this는 자기자신이므로 setNumberthistest가 된다.

정리

해당 내용은 mdn web docs - this을 참고해서 작성한 글이다. 더 자세한 정보를 얻고 싶다면 문서에서 확인해보자.

  • this 바인딩 정리
    1. 일반 함수(function 키워드 사용)에서 사용된 this는 해당 함수를 호출하는 객체를 가리킨다.
    2. 화살표 함수에서 사용된 this는 block을 타고 올라갔을 때, 맨 처음 만나는 일반 함수의 this를 가리킨다.
    3. new 키워드를 사용할 경우 생성자 함수가 객체 그 자체가 된다. 생성자 함수는 일반 함수여야한다. 생성자 함수 호출 시 this는 무조건 자기 자신이다.
  • 최상위 thiswindow 이다.(node.js에서는 global)