
자바스크립트를 사용하면서 만나는 this는 뭘까?
책에서는 this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수라 설명하고, this를 통해서 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다고 합니다.
const test = {
name: 'spider man',
getName() {
console.log(this) // { name: 'spider man', getName: [Function: getName] }
return this.name;
},
};
console.log(test.getName()); // spider man
위 코드에서 test 객체 안에는 name 프로퍼티와 getName 메서드가 존재하며 내부에서 this를 출력하면, test 객체 자체를 가리켜서 객체 안에 있는 모든 것들이 확인이됩니다. 이처럼 this는 자신이 속해있는 객체 자체를 지칭하는 참조 변수입니다.
쉽게 비유하자면, this는 옷장 속에 있는 모든 물건을 가리키는 포인터와 같다고 할 수 있습니다. 옷장이 test 객체라면, 그 속에 있는 모자, 셔츠, 자켓이 객체의 프로퍼티들인 셈입니다.
this가 가리키는 객체는 함수가 어떻게 호출되었는지에 따라 달라집니다. 이를 this 바인딩이라고 부르며, 함수 호출 방식에 따라 this가 특정 객체에 바인딩됩니다.
this는 자바스크립트의 모든 실행 컨텍스트에서 참조할 수 있으며, 따라서 어떤 컨텍스트에서 함수가 호출되느냐에 따라 그 값이 달라지므로 주의가 필요합니다.
console.log(this) // 브라우저 환경에서는 window 객체, Node.js에서는 global 객체
function square(number) {
console.log(this) // 브라우저 환경에서는 window 객체, Node.js에서는 global 객체
return number * number
}
square(2);
const test = {
name: 'spider man',
getName() {
console.log(this) // { name: 'spider man', getName: [Function: getName] }
return this.name;
},
};
console.log(test.getName()); // spider man
function Person(name) {
this.name = name;
console.log(this) // Person { name: 'choi' }
}
const me = new Person('choi')
위 예시에서 this는 상황에 따라 window 객체/global객체, test 객체, 그리고 Person 생성자에서 생성된 인스턴스 객체로 바인딩됩니다. 이처럼 this는 호출 방식에 따라 달라집니다.
그럼 호출 방식별로 어떤 차이가 있을까요?
일반 함수에서의 this는 기본적으로 전역 객체에 바인딩됩니다. 브라우저 환경에서는 window 객체가, Node.js 환경에서는 global 객체가 바인딩됩니다.
function first() {
console.log('first-this: ', this); // window
function second() {
console.log('second-this', this); // window
}
second()
}
first()
위 코드에서는 first 함수와 second 함수는 독립적으로 호출되었습니다. 이 경우 this는 전역 객체(window)를 참조합니다.
콜백 함수가 일반 함수로 호출될 때는 그 함수가 속한 객체와 관계없이 전역에서 호출되기 때문에 this는 전역 객체를 가리키게 됩니다.
var value = 100
const obj = {
value: 1,
test() {
console.log('test-this: ', this) // value: 1, test: [Function: test] }
setTimeout(function() {
console.log('callback-this: ', this) // window
console.log('callback-this.value: ', this.value) // 100
}, 100)
}
}
obj.test()
setTimeout의 콜백 함수는 obj 객체 안에서 정의됐지만, 콜백 함수 자체는 일반 함수로 호출됩니다. 따라서 콜백 함수의 this는 전역 객체(window)를 가리킵니다.
이 상황을 해결하기 위해, 화살표 함수를 사용하거나, bind, call, apply 메서드를 사용하여 명시적으로 this를 지정할 수 있습니다.
// bind 사용
const obj = {
value: 1,
test() {
setTimeout(
function () {
console.log(this); // obj 객체를 가리킴
console.log(this.value); // 1
}.bind(this),
100
);
},
};
obj.test();
// 화살표 함수 사용
const obj = {
value: 1,
test() {
setTimeout(() => {
console.log(this); // obj 객체를 가리킴
console.log(this.value); // 1
}, 100);
},
};
obj.test();
위처럼 bind나 화살표 함수를 사사용하면 this는 obj객체를 가리키게 되고,this.value는 1이 됩니다.
메서드는 객체의 속성으로 정의된 함수입니다. 이때, 메서드 내부에서의 this는 메서드를 호출한 객체를 참조하게 됩니다. 즉, 메서드를 호출할 때 마침표(.) 연산자 앞의 객체가 this로 바인딩됩니다.
const test = {
name: 'spider man',
getName() {
return this.name;
},
};
console.log(test.getName()); // spider man
test.getName()에서 this는 test 객체를 참조합니다. 따라서 this.name은 test 객체의 name 속성인 'spider man'을 반환합니다.
생성자 함수는 주로 대문자로 시작하는 함수 이름을 가지고 있으며, new 키워드와 함께 호출됩니다. 생성자 함수는 인스턴스를 생성하기 위한 목적으로 사용되며, 이 경우 this는 생성자 함수가 미래에 새로 생성할 인스턴스를 가리킵니다.
function Hero(name) {
this.name = name;
}
const pool = new Hero('DeadPool');
console.log(pool.name); // DeadPool
new 키워드로 Hero 함수를 호출하면 새로운 객체가 생성되며, 그 객체가 this에 바인딩됩니다.
따라서 this.name 새로 생성된 pool 인스턴스의 name 속성에 접근하게 됩니다.
Function.prototype의 메서드 apply, call, bind 메서드는 this를 수동으로 바인딩할 때 사용됩니다. 이 메서드 사용하면 함수의 this를 명시적으로 지정할 수 있게 됩니다.
apply는 함수에 전달할 인수를 배열로 받아서 this를 지정하여 호출합니다. call은 인수들을 개별적으로 받아서 this를 지정하여 호출합니다. bind는 this를 바인딩한 새로운 함수를 반환하며 호출은 나중에 가능합니다.
function sayHello(greeting) {
return `${greeting}, my name is ${this.name}`;
}
const person = { name: 'John' };
console.log(sayHello.call(person, 'Hello')); // Hello, my name is John
console.log(sayHello.apply(person, ['Hi'])); // Hi, my name is John
const boundFunc = sayHello.bind(person);
console.log(boundFunc('Hey')); // Hey, my name is John
위 코드에서 보이듯 call과 apply는 즉시 함수를 호출하면서 this를 특정 객체로 바인딩합니다.
bind는 this를 바인딩한 새로운 함수를 반환하므로, 나중에 함수를 호출할 수 있습니다.
면접 답변을 준비하면서 화살표 함수는 자신만의 this를 가지지 않는다는 특징이 있다고 공부했는데, 그럼 화살표 함수에서는 this를 어떻게 사용하게 될까요?
화살표 함수는 his바인딩 값을 상속받아서 본인만의this가 아닌 상위 스코프의 this를 사용하게 됩니다. 그래서 function으로 선언한 함수는 this가 동적으로 바인딩되는 반면 화살표 함수는 선언된 시점에서의 상위 스코프가 this로 바인딩 됩니다.
즉, 화살표 함수는 선언된 위치의 this를 그대로 사용하며, 일반 함수처럼 호출 방식에 따라 this가 변경되지 않습니다.
const obj = {
name: 'peter',
regularFunc: function () {
console.log(this.name); // peter
},
arrowFunc: () => {
console.log(this.name); // undefined (상위 스코프의 this가 전역 객체를 가리킴)
},
};
obj.regularFunc();
obj.arrowFunc();
위 예시에서 regularFunc는 this가 obj를 가리키지만, 현재 상황에서 arrowFunc는 상위 스코프인 전역 스코프의this를 참조하므로 name 자체가 정의되어 있지 않아 undefined가 출력됩니다.
this에 대해 정리해 봤는데, 역시 내용이 복잡해서 정리하면서도 여러번 책을 봤던것 같습니다..
아직 갈길이 멀지만 차근차근 이해한다면 나중에는 좀더 직관적으로 this가 이해되지 않을까 싶습니다 🥲
출처:
모던 자바스크립트 Deep Dive 22장 this (342p ~ 358p)