JavaScript

You Don't Know JS Yet - this & Object Prototypes CH01 요약

한땀코딩 2021. 1. 20. 12:39

Ch01 | this or That?

자바스크립트에서 this는 그 이름 때문에 많은 개발자들이 혼란을 느끼는 요소입니다. 알아보면 동작 자체가 굉장히 복잡하진 않지만, 이름에서 오는 혼동 때문에 많이들 잘못 사용하고 있다고 합니다.

Why this?

this를 활용하면 이용하여 하나의 함수가 컨텍스트를 특별히 인자로 받지 않아도 call같은 컨텍스트 변경 함수를 이용하여 원하는 결과를 줄 수 있게 됩니다.

function identify() {
    return this.name.toUpperCase();
}

var me = {
    name: "Grace"
};

var you = {
    name: "Reader"
};

identify.call( me ); // GRACE
identify.call( you ); // READER

this를 피하고자 했다면 아래처럼 고쳐볼 수 있습니다.

function identify(context) {
    return context.name.toUpperCase();
}

identify( you ); // READER

this를 활용하게 되면, 좀 더 깔끔한 API 설계와 재활용이 가능하다고 합니다. 작가의 말에 따르면, 컨텍스트를 두번째 예시처럼 직접 넘기게 되면 지저분하다고 합니다.

Confusions

이번 챕터에서는 흔히 알려진 this에 관한 오해 2가지를 소개합니다.

itself

흔한 오해 중 하나는, this가 함수 그 자체를 가리키고 있다는 착각입니다. 이런 착각은 왜 생길까요? 함수가 자신을 가리킬 일이 있을까요? 자신을 호출하거나, 함수 호출 사이에 상태를 저장하기 위해서 같은 이유가 있을 수 있지만, 이를 위해선 더 나은 패턴이 있다고 합니다. 이는 이번 3권에서 계속 언급될 예정입니다.

this가 함수 자신을 가리키지 않는다는 건 아래 예시를 통해 확인이 가능합니다.

function foo(num) {
    console.log( "foo: " + num );
    // keep track of how many times `foo` is called
    this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        foo( i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 0 -- 띠용??

foo 함수 안의 this.count가 바깥에서 작성된 foo.count와 동일할 거 같지만, 실제로는 함수가 호출될 때 this가 전역을 가리키게 되면서 전역 변수인 count를 생성하게 됩니다. 그래서 함수를 몇번이고 호출해도 foo.count는 초기값 0인 그대로인 것입니다.

만약 함수 호출이 foo.count를 실제로 증가시키고 싶었다면 아래처럼 작성할 수 있습니다.

function foo(num) {
    console.log( "foo: " + num );
  // 함수명 foo 사용
    foo.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
        foo( i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 4

이렇게 함수의 이름을 직접적으로 작성해서 컨텍스트를 고정시키는 겁니다. 그런데 컨텍스트를 넘기는 방법을 저자는 선호하지 않기 때문에, this를 살리면서 이런 결과를 내고 싶다면 call을 활용하면 됩니다.

function foo(num) {
    console.log( "foo: " + num );
    this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
    if (i > 5) {
    // call 사용
        foo.call( foo, i );
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 0 -- 띠용??

Its Scope

다른 오해는 this가 함수 스코프를 가리킨다는 착각입니다. 아주 틀린 말은 아니지만, 정확히 말하면 함수의 lexical scope를 가리키는 것이 아닙니다.

function foo() {
    var a = 2;
    this.bar();
}

function bar() {
    console.log( this.a );
}

foo(); //undefined

위 코드에서 foo안에서 bar를 호출하고 싶다면 우선 그냥 bar라고 적는 것이 자연스럽습니다. 아마 이런 코드를 작성하는 사람의 의도는, foo와 bar의 lexical scope를 연결하려는 것이었을 가능성이 높습니다. bar의 this.a 가 foo함수 내부의 a를 가리키게 하고 싶은 것이죠. 다만, 이런 연결 행위는 this로는 불가능합니다. this로는 lexical scope에서 무언가를 찾을 수 없습니다.

What's this?

this는 실제 작성된 코드의 위치가 아닌, 런타임 환경, 즉 함수가 불리는 환경과 관계가 있습니다. 함수가 호출되면, 실행 컨텍스트라는 것이 생기는데, 여기에는 콜스택 어디에서 함수가 호출됐는지, 어떻게 함수가 호출됐는지, 어떤 인자를 넘겨받았는지 등을 기억하고 있습니다. 이런 여러 정보 중 하나가 this이고, 함수가 실행되는 동안 참조될 수 있습니다.