[JS스터디] 22. this

2023. 5. 14. 19:26JavaScript

this 키워드

this : 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수

객체 리터럴을 식별자에 할당하여 객체를 생성하는 경우, 할당되기 직전에 객체 리터럴이 평가되어 객체가 생성되므로, 객체 리터럴 안에서 자신이 속한 객체의 프로퍼티나 메소드를 참조하기 위해 식별자를 재귀적으로 참조할 수 있다.

하지만 생성자 함수로 객체를 생성할 경우, 생성자 함수 내에서 자신이 생성할 인스턴스 객체를 가리킬 방법은 없다. 아직 객체를 생성하기 이전이기 때문에 참조할 식별자가 없기 때문이다. 이 때 사용하는 것이 this다.

this가 가리키는 값, 즉 this 바인딩함수 호출 방식에 의해 동적으로 결정된다. (바인딩: 식별자와 확보된 메모리 공간의 주소를 연결하는 것)

객체 리터럴의 메소드 내부에서 사용되는 this는 메소드를 호출한 객체를 가리킨다. (여기서 메소드를 호출한 객체는 메소드가 속한 객체와 같지 않다.)

반면 생성자 함수 내부에서 사용되는 this는 생성자 함수가 생성할 인스턴스 객체를 가리킨다.

(자바나 C++의 경우 this는 언제나 클래스가 생성하는 인스턴스를 가리킨다)

this는 코드 어디에서든 참조 가능하다. 전역에서 참조하면 전역 객체를 가리키게 된다. 다만 this는 객체의 프로퍼티나 메소드를 참조하기 위한 자기 참조 변수이기 때문에 일반적으로는 객체나 생성자 함수 내부에서만 의미가 있다. 따라서 strict mode에서는 생성자 함수가 아닌 일반 함수에서 this 사용 시 undefined가 바인딩된다.

함수 호출 방식과 this 바인딩

앞서 언급했듯 this가 가리키는 값, 즉 this 바인딩함수 호출 방식에 의해 동적으로 결정된다.


함수 호출 방식

  1. 일반 함수 호출 → this: 전역 객체
  2. 메소드 호출 → this: 메소드를 호출한 객체
  3. 생성자 함수 호출 → this: 생성자 함수가 생성할 인스턴스
  4. Function.protoytpe.apply/call/bind 메소드에 의한 간접 호출 → this: 첫 번째 인수로 전달한 객체

일반 함수 호출

일반 함수로 호출할 경우 함수 내부의 this는 전역 객체가 바인딩된다.

function foo() {
    console.log("foo's this: ", this); // window
    function bar() {
        console.log("bar's this: ", this); // window
    }
    bar();
}
foo();

하지만 this는 자기 참조변수이기 때문에 이렇게 사용하는 것은 의미가 없기 때문에 strict mode에서는 undefined가 바인딩된다.

function foo() {
    'use strict';

    console.log("foo's this: ", this); // undefined
    function bar() {
        console.log("bar's this: ", this); // undefined
    }
    bar();
}
foo();

메소드 내에 정의된 중첩함수의 경우도 일반함수로 호출되는 경우 전역 객체를 가리킨다. 단, 이 경우 메소드 내의 중첩되지 않은 외부 함수는 해당 메소드가 속한 객체를 가리킨다. (함수 호출 방식이 메소드 호출)

콜백 함수가 일반 함수로 호출돼도 this는 전역 객체가 바인딩된다. 어떤 함수든 일반함수로 호출되면 전역 객체가 바인딩된다.

var value = 1;
const obj = {
    value: 100,
    foo() {
        console.log("foo's this: ", this); // {value: 100, foo: f}

        // 콜백 함수가 일반함수로서 호출되는 경우
        setTimeout(function() {
            console.log("callback's this: ", this); // window
            console.log("callback's this.value: ", this.value); // 1 (전역 변수 참조)
        }, 100);
    }
};
obj.foo();

이럴 경우 콜백 함수는 외부 함수를 돕는 헬퍼 함수의 역할을 위해 존재하는 것인데, 외부 함수가 아닌 전역 객체를 가리키기 때문에 콜백함수가 헬퍼함수로서의 역할을 하기 어렵게 만든다. 따라서 아래처럼 수정이 필요하다.

var value = 1;
const obj = {
    value: 100,
    foo() {
        // this(obj)를 새로운 변수에 할당해놓는다.
        const that = this;

        // 콜백 함수에서는 this(window) 대신 that(obj)를 참조한다.
        setTimeout(function() {
            console.log("callback's that.value: ", that.value); // 1 (전역 변수 참조)
        }, 100);
    }
};
obj.foo();

이 외에도 Function.prototype.call, Function.prototype.bind, Function.prototype.apply 메소드를 제공한다.

메소드 호출

메소드 내부에서의 this는 메소드를 호출한 객체가 바인딩된다.

메소드 소유한 객체 (X)

메소드 호출한 객체 (O)

메소드는 객체에 속해있는 것이 아니라, 객체의 프로퍼티에 바인딩되어있는 독립적인 함수이다. 따라서 메소드는 다른 객체의 메소드가 될 수도 있고, 일반 함수로 호출될 수도 있기 때문에 메소드가 객체에 속해있다는 표현은 바람직하지 않다.

const person = {
    name: 'Lee',
    getName() {
        return this.name;
    }
};
const anotherPerson = {
    name: 'Kim',
};

// 다른 객체의 메소드로 할당할 수 있다
// person의 프로퍼티가 가리키는 메소드 getName함수를 anotherPerson의 메소드로 할당
anotherPerson.getName = person.getName;

// 일반함수로 호출도 할 수 있다
const getName = person.getName;
console.log(getName()); // window.name

프로토타입 메소드 내부에서 사용되는 this역시 메소드를 호출한 객체에 바인딩된다.

function Person(name) {
    this.name = name;
}
Person.prototype.getName = function() {
    return this.name;
}
const me = new Person('Lee');
console.log(me.getName()); // Lee

Person.prototype.name = 'Kim';
console.log(Person.prototype.getName()); // Kim

생성자 함수 호출

생성자 함수 내부의 this는 생성자 함수가 생성 인스턴스가 바인딩된다.

new 연산자와 함께 객체를 생성해야 생성자 함수로 동작하고, this가 그 객체를 가리키게 된다. new 없이 생성하면 일반 함수로 호출되어 전역 객체를 가리키게 된다.

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

Function.prototype의 메소드로 모든 함수 객체가 apply, call, bind메소들을 상속받아 사용할 수 있다.

이들을 사용하면 this로 사용할 객체를 인수로 전달하여 직접 지정할 수 있다.

applycall의 본질적인 기능은 함수를 호출하는 것이다. 호출을 하되 인수로 특정 객체를 전달하여 호출한 함수의 this에 바인딩한다. 둘은 두 번째 인수 (함수에 전달한 인수 배열)를 어떻게 전달하느냐만 다르고 본질은 같다.

대표적으로 arguments 객체와 같이 유사 배열 객체에 배열 메소드를 사용하는 경우에 사용한다. arguments는 배열이 아니라 유사 배열이기 때문에 Array.prototype.slice 등의 메소드를 사용할 수 없는데, apply, call메소드를 사용하면 가능하다.

function convertArgsToArray() {
    console.log(arguments);
    const arr = Array.prototype.slice.call(arguments);
    console.log(arr);
    return arr;
}
convertArgsToArray(1, 2, 3);
// 27. 배열 공부 후 다시보기

bind는 apply와 call과 달리 함수를 호출하지 않고, 첫번째 인수로 전달된 함수로 this바인딩을 새로 교체한 함수를 생성해 반환한다.

메소드의 this와 메소드 내부 중첩함수 또는 콜백함수의 this가 일치하지 않는 문제를 해결할 때 유용하다.

const person = {
	name: 'Lee',
	foo(callback) {
		setTimeout(callback, 100); // this: window
	}
};

person.foo(function() {
	// this.name은 window.name을 가리킨다.
	console.log(`Hi! my name is ${this.name}.`); // Hi! my name is .
});
const person = {
	name: 'Lee',
	foo(callback) {
		// this(person)을 콜백함수의 this에 바인딩
		setTimeout(callback.bind(this), 100); // this: person
	}
};

person.foo(function() {
	// this.name은 person.name을 가리킨다.
	console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Lee.
});

스터디 교재

22장 this

'JavaScript' 카테고리의 다른 글

[JS스터디] 24. 클로저  (0) 2023.06.05
[JS스터디] 21. 빌트인 객체  (0) 2023.05.14
[JS스터디] 20. strict mode  (0) 2023.05.14
[JS스터디] 19. 프로토타입  (0) 2023.05.06
[JS스터디] 18. 함수와 일급객체  (0) 2023.05.06