[JavaScript]this

ungnam·2023년 1월 27일

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

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

함수를 호출하는 방식에는 다음과 같은 4가지가 있다.

  1. 일반 함수 호출
  2. 메서드 호출
  3. 생성자 함수 호출
  4. Function.prototype.call/apply/bind 메서드에 의한 간접 호출

일반 함수 호출

일반 함수의 this는 기본적으로 window다.

(function foo() {
	console.log(this);
})();

위 코드를 브라우저에서 실행했을 시 결과는 window이다.

다만, 일반 함수의 경우 객체를 생성하지 않기 때문에 일반적으로 this는 여기서 의미가 없다. 따라서 strict mode가 적용된 일반 함수에서의 thisundefined가 된다.
(참고로 node.js 환경에서 위 코드를 똑같이 실행했을 때 결과는 window가 아닌 global이 나오게 된다.)

메서드 내에서 정의한 중첩 함수, 그리고 콜백 함수 또한 일반 함수로 호출될 경우 마찬가지로 내부의 this는 전역 객체가 바인딩된다. 그러나 두 함수의 경우 일반적으로 외부 함수를 돕는 헬퍼 함수의 역할을 하기 때문에 외부 함수의 this와 서로 일치하지 않는다는 점은 헬퍼 함수로 동작하기 어렵게 만든다.

따라서 외부 함수와 this를 일치시키기 위해서는

  1. 외부 함수의 this를 변수에 저장하여 헬퍼 함수에게 전달
  2. Function.prototype.call/apply/bind 메서드로 this를 명시적으로 바인딩
  3. 화살표 함수를 사용해서 외부 함수와 this 바인딩 일치

방식을 이용해야 한다.

메서드 호출

메서드 호출 시 this는 메서드를 호출한 객체가 바인딩된다.

const person = {
	name: 'bear',
    age: 24,
    sayName() {
    	return this.name;
    }
}

console.log(person.sayName());	// bear

sayName 메서드의 경우 프로퍼티에 바인딩된 함수이므로 다른 객체의 프로퍼티에 할당하여 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수 있다.

const anotherPerson = {
  name: "Kim"
};
anotherPerson.sayName = person.sayName;
// 이 때 sayName 메서드를 호출한 객체는 anotherPerson이다.
console.log(anotherPerson.sayName());

const sayName = person.sayName;
// 일반 함수로 호출하면 this는 window
// window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티로 기본값은 ''이다.
console.log(sayName());	// ''

이를 통해 메서드 내부의 this는 메서드를 가리키고 있는 객체와는 관계가 없고 메서드를 호출한 객체에 바인딩된다는 사실을 다시 한번 확인할 수 있다.

생성자 함수 호출

생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩된다.

function Human(name) {
	this.name = name;
}

const human = new Human('bear');
console.log(human.name);	// bear

new 연산자와 함께 호출하지 않으면 생성자 함수가 아니라 일반 함수로 동작한다는 점을 주의하자.

프로토타입 메서드 내부에서 사용된 this도 일반 메서드와 마찬가지로 해당 메서드를 호출한 객체에 바인딩된다.

function Human(name) {
	this.name = name;
}
Human.prototype.sayName = function() {
  return this.name;
};
const me = new Human("Lee");
Human.prototype.name = "Kim";
console.log(me.sayName()); // ①
console.log(Human.prototype.sayName()); // ②

①의 경우 getName 메서드를 호출한 객체는 me다. 따라서 thisme이므로 this.nameLee다.
②의 경우 getName 메서드를 호출한 객체는 Human.prototype이다. 여기에 name: 'Kim'을 할당하였기 때문에 this.nameKim이다.

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

function sayName() {
	console.log(arguments);
    return this.name;
}
sayName();

앞에서 설명했다시피, 일반적인 함수의 thiswindow이다.
이 때 Function.prototype.call/apply/bind 메서드를 통해 함수의 this를 변경할 수 있다.

Function.prototype.call

sayName.call({ name: 'bear' }, 1, 2, 3)

this{ name: 'bear' }로 변경하고, 인수 리스트를 사용해 함수를 호출한다.

Function.prototype.apply

sayName.apply({ name: 'bear' }, [1, 2, 3])

this{ name: 'bear' }로 변경하고, 인수 배열을 사용해 함수를 호출한다.

Function.prototype.bind

sayName.bind({ name: 'bear' })()

this{ name: 'bear' }로 변경하고, apply/call 메서드와 달리 함수를 호출하지 않고 인수 없이 this로 사용할 객체만 전달한다.

요약

this는 무조건 호출 시에 결정된다.
obj.method() / new 키워드 / call/apply/bind 키워드를 제외한 나머지 일반 함수의 경우 this는 기본적으로 window(node의 경우 global)

다음 포스트에서 작성할 내용

  • 화살표 함수에서의 this
  • this를 분석할 수 없는 경우
profile
꾸준함을 잃지 말자.

0개의 댓글