[JS] this

서로·2024년 8월 15일
0

JS

목록 보기
8/15
post-thumbnail

➊ this

this는 통상 클래스로 생성한 인스턴스 객체를 의미한다.

그러나 다른 언어에서와 달리 자바스크립트에서 this는 (클래스 내부가 아니어도) 어디서든지 사용할 수 있으며, this를 출력하면 예상과 다르게 동작할 때가 많다.

this는 함수가 호출될 때 결정된다.

지금부터 전역 공간, 함수, 메서드, 화살표 함수, 콜백 함수, 생성자 함수 내부에서
this를 사용할 때 어떤 객체가 바인딩 되는지 알아보도록 하겠다.

① 전역 공간에서의 this

전역 공간에서 this는 전역 객체를 가리킨다.
여기서 전역 객체는 JS 런타임 환경에 따라 달라지는데,
브라우저 환경에서는 window이고 Node.js 환경에서는 global이다.

그리고 이 전역 객체는 프로퍼티를 가지는데
전역 공간에서 선언한 모든 변수는 전역 객체의 프로퍼티로 등록된다!

아래 예제를 살펴보자.

var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1

전역 변수 a에 1을 할당하였더니 a, window.a, this.a 모두 1이 출력되었다.
위의 예제를 통해 전역 변수는 global 혹은 window의 프로퍼티라는 것을 알 수 있다.

그렇다면 지역 변수는 어떨까?

자바스크립트의 모든 변수는 실행 컨텍스트의 LexicalEnvironment 객체의 프로퍼티로서 동작한다!

따라서 전역 공간에서 선언한 변수는 전역 컨텍스트의 LexicalEnvironment 객체의 프로퍼티이며,
함수 내부에서 변수를 선언하더라도 그 변수는 실행 컨텍스트의 LexicalEnvironment 객체의 프로퍼티가 된다.

🚨
하지만 전역 컨텍스트의 L.E가 전역 객체(this)를 그대로 참조할 뿐
지역 실행 컨텍스트의 L.Ethis는 별개이므로 혼동하지 않도록 주의하자!

② 메서드 안에서의 this

어떤 함수를 호출할 때는 함수로서 호출하는 경우메서드로서 호출하는 경우 2가지가 있다.

둘은 무슨 차이일까? 아래 예제를 통해 설명하도록 하겠다.

var func = function (x) {
    console.log(this, x);
};
func(1);
var obj = {
    method: func
};
obj.method(2);

위의 코드에서 func이라는 동일한 함수를 그냥 함수로서 호출하는 경우와
obj 객체의 프로퍼티로 등록 후 객체의 메서드로서 호출하는 경우를 확인할 수 있다.

func을 그냥 함수로서 호출하면 this는 전역 객체인 window가 된다.
그러나 프로퍼티 접근 연산자(.)로 연결하여 객체의 메서드로서 호출하면 thisobj가 된다.

실제로 자바스크립트 엔진은 점(.) 연산자의 유무로 메서드로서 호출했는지, 함수로서 호출했는지 판단한다.

🚨 어떤 함수를 함수로서 호출하면 실행 컨텍스트가 생성될 때 this 바인딩을 따로 하지 않는다.

이처럼 this가 따로 지정되지 않으면 전역 객체를 바라보게 된다.

이러한 점 때문에 우리는 this가 실제 어떤 객체를 바라보고 있는지 예측할 수 없다.

아래 예제를 통해 this가 어떤 객체를 참조하고 있는지 살펴보자!

var obj1 = {
    outer: function () {
        console.log(this);
        var innerFunc = function () {
            console.log(this);
        }
        innerFunc(); // window
        var obj2 = {
            innerMethod: innerFunc
        };
        obj2.innerMethod(); // obj2
    }
};
obj1.outer(); // obj1

위에서 함수 실행문은 총 3개이다.
가장 먼저 실행되는 함수는 obj1.outer();으로 프로퍼티 접근 연산자(.)을 이용하여 메서드로서 호출하였다.
따라서 이 함수 안에서 thisobj1 이다.

두 번째로 실행되는 함수는 innerFunc();으로 그저 함수로서 호출하였다.
this가 바인딩되지 않았으므로 전역 객체를 바라보게 된다.
따라서 thiswindow 이다.

세 번째로 실행되는 함수는 obj2.innerMethod();으로 프로퍼티 접근 연산자(.)을 이용하여 메서드로서 호출하였다.
따라서 이 함수 안에서 thisobj2 이다.

이처럼 this은 함수를 어떻게 호출하느냐에 따라 바라보는 객체가 달라진다.
함수를 메서드로서 실행하면 실행 컨텍스트가 생성될 때 this는 점(.) 앞의 객체를 바인딩하며
함수를 그냥 함수로서 실행하면 this 바인딩을 하지 않으므로 전역 객체를 바라보게 되는 것이다!

함수 내부에서 this가 전역 객체를 바라보는 문제를 보완하고자
ES6 부터 화살표 함수가 도입되었다!

③ 화살표 함수 안에서의 this

화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어
상위 스코프의 this를 그대로 활용하게 된다.

var obj = {
    outer: function () {
        console.log(this);
        var innerFunc = () => {
            console.log(this);
        };
        innerFunc();
    }
};
obj.outer(); // obj

위의 예제에서 obj.outer(); 함수를 메서드로서 호출하였다.
따라서 가장 먼저 obj 객체가 출력된다.

그 다음으로 화살표 함수인 innerFunc();을 함수로서 호출하는데
상위 스코프의 this을 그대로 활용하여 obj 객체가 출력된다.

④ 콜백 함수 안에서의 this

함수 A의 제어권을 다른 함수 B에게 넘겨주는 경우 함수 A를 콜백 함수라고 한다.

이때, 함수 A는 함수 B의 내부 로직에 따라 실행되며, this 역시 함수 B 내부 로직에서 정한 규칙에 따라 값이 결정된다!

아래 예제 코드를 살펴보자.

setTimeout(function () { console.log(this); }, 300);
document.body.querySelector('#a')
    .addEventListener('click', function (e) {
        console.log(this, e);
    });

0.3초 뒤에 실행되는 콜백 함수는 window가 출력되며
ida인 요소를 클릭했을 때 실행되는 콜백 함수는 해당 id = a인 요소를 출력하게 된다.

이는 addEventListener 함수가 콜백 함수에서의 this를 무엇으로 할지에 대해 미리 정의해두었기 때문이다!

⑤ 생성자 함수 안에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다.

자바에서의 class와 유사하다고 생각하면 된다.

결론부터 말하자면 생성자 함수 내부에서의 this은 곧 새로 만들 인스턴스 자신을 의미한다.

아래 예제를 살펴보자!

var Cat = function (name, age) {
    this.bark = '야옹';
    this.name = name;
    this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi); // 초코 객체와 나비 객체 출력

위 생성자 함수 안에서의 this는 앞으로 생성될 객체에 접근하기 위함이다.
따라서 this을 통해 하나의 생성자 함수로 다양한 속성을 갖는 객체를 생성할 수 있다. (다형성)

➋ 명시적으로 this 바인딩 하기

명시적으로 this을 바인딩할 수 있는 방법으로 세 가지 메서드가 존재한다.
call, apply, bind 을 차례대로 살펴보자!

① call 메서드

call은 메서드의 호출 주체인 함수를 즉시 실행하며
메서드의 첫 번째 인자를 this로 바인딩하고 두 번째 인자를 호출할 함수의 매개변수로 한다.

var func = function (a, b, c) {
    console.log(this, a, b, c);
};
func(1, 2, 3);
func.call({ x: 1 }, 4, 5, 6);

위 예제에서 func을 그냥 함수로서 호출했을 때 this은 전역 객체가 된다.
그러나 call 메서드를 이용하여 호출했을 때 첫 번째 인자로 전달된 객체인 { x: 1 }this가 된다!

② apply 메서드

apply 메서드는 call 메서드와 기능적으로 완전히 동일하다.
그러나 유일한 차이는 call 메서드는 첫 번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면,
apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다.

아래의 예제를 살펴보자.

var func = function (a, b, c) {
    console.log(this, a, b, c);
};
func.call({ x: 1 }, 4, 5, 6); // { x: 1 } 4 5 6
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

같은 함수를 call 메서드와 apply 메서드 두 방식으로 호출해보았다.
인자를 전달하는 방식만 다를 뿐 두 실행문 모두 같은 출력 결과를 가진다.

③ bind 메서드

bind 메서드는 위의 두 메서드와 다르게 즉시 호출하지 않고
전달받은 this와 인수들을 바탕으로 새로운 함수를 반환한다.

아래의 예제를 살펴보자.

var func = function (a, b, c, d) {
    console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x: 1 }, 4, 5);
bindFunc(6, 7);

bind 메서드를 이용하여 새로운 함수 bindFunc을 반환하였다.
bindFunc을 호출하면 인자로 전달된 { x: 1 } 객체가 this로서 출력되며,
인자로 전달된 45도 순서대로 파라미터 ab에 전달된다.
즉, 최초 func 함수에 4, 5, 6, 7을 인자로 넘기고 { x: 1 } 객체가 this로 바인딩된 것과 같다.

이처럼 call, apply, bind 메서드를 이용하면
this를 예측할 수 있으므로 개발자들의 혼란을 막을 수 있다.
또한 화살표 함수를 이용하여 상위 스코프의 this를 그대로 이용할 수 있다.

profile
읽기 쉬운 코드와 글을 작성해요 📝

0개의 댓글