JavaScript의 this는 함수 내에서 현재 객체를 참조하는 데 사용되는 특별한 키워드이다. this는 어디서 어떻게 호출하느냐에 따라 가리키는 대상이 결정된다.
일반적으로 함수가 일반적으로 호출될 때 this는 전역 객체(브라우저에선 window)를 참조한다.
function callThis(){
console.log(this);
}
calllThis();
// Window
// {window: Window, self: Window, document: document, name: '', …}
그런데 왜 함수를 호출하면 this가 전역객체를 가리키게 될까?
⭐ 함수 호출 방식과 관련이 있다.
자바스크립트의 함수 호출 방식에는 일반 함수 호출, 메소드 호출, 생성자 함수 호출, apply/call/bind 호출 등이 있다.
이 중에서 일반 함수 호출에서는 함수를 호출할 때, 별도로 this가 가리킬 객체를 지정하지 않는다. 따라서 this는 전역 객체에 바인딩된다.
하지만 메소드 호출이나 생성자 함수 호출 등에서는 this가 다른 객체를 참조하게 된다. 메소드 호출에서는 해당 메소드가 속한 객체가 this에 바인딩되고, 생성자 함수 호출에서는 생성자 함수로부터 생성될 인스턴스가 this에 바인딩된다.
const obj = {
name: 'John',
sayName: function() {
console.log(this.name);
}
};
obj.sayName(); // John
function Person(name, age) {
this.name = name;
this.age = age;
}
const john = new Person('John', 30);
console.log(john.name); // John
console.log(john.age); // 30
apply/call/bind 호출에서는 this를 바인딩하고 싶은 객체로 직접 지정할 수 있다.
call() 메소드는 함수를 호출하는 동시에, 함수 내부에서 this 값을 지정할 수 있다. 첫 번째 인자로는 this가 가리킬 값이 들어가며, 두 번째 인자부터는 호출할 함수에게 넘겨줄 인자들이 들어간다.
function sayHello() {
console.log("Hello " + this.name);
}
let person = { name: "John" };
sayHello.call(person);
이 코드는 sayHello()함수를 호출할 때, call메소드를 사용하여this값을 person객체로 설정해주고 있다. 이로 인해 본래 일반적으로 함수는 this에 전역객체가 바인딩되지만, call메소드를 사용하여console.log()에서 출력되는 문자열은 Hello John이 된다.
반면에 bind()메소드는 함수를 호출하는 것이 아니라, 함수의 this값을 영구적으로 바인딩하는 함수를 반환하는 것이다. 인자로는 this에 바인딩 할 값이 들어간다.
let person = {
name: "John",
sayHello: function() {
console.log("Hello " + this.name);
}
};
let person2 = {name: "Tom"};
let greet = person.sayHello.bind(person2);
greet(); // Hello Tom
person.sayHello(); // Hello John
위의 코드는 person.sayHello() 메소드를 호출하지 않고, person 객체에서 sayHello() 메소드를 가져와 greet 변수에 할당하고 있다. 이때, bind() 메소드를 이용하여 sayHello() 메소드 내부에서 this 값으로 사용할 객체를 지정하고 있다. greet() 함수를 호출하면, sayHello() 함수 내부에서 this 값으로 person2 객체가 사용되기 때문에, console.log()에서 출력되는 문자열은 Hello Tom이 된다. 이후로 greet() 함수를 호출하더라도, 항상 this 값으로 person2 객체가 사용되기 때문에 같은 결과가 출력된다.
따라서 this가 전역 객체를 참조하는 것은 함수 호출 방식에서 일반 함수 호출에서만 발생하는 것이다.
화살표 함수는 자신만의 this를 생성하지 않고, 상위 스코프의 this를 그대로 사용한다. 즉, this가 바인딩 되는 방식이 일반 함수와 다르다.
화살표 함수 내에서 this는 함수가 정의 된 컨텍스트에서 상속된다. 이것은 this의 값이 함수가 호출되는 곳에 따라 달라지는 동적바인딩과 다른 정적바인딩이다.
예를 들어, 전역 스코프에서의 화살표 함수 this는 상위 스코프의 this인 전역 객체를 참조한다.
// 전역 스코프에서의 this
console.log(this); // 전역 객체를 출력
// 전역 스코프에서의 화살표 함수
const arrowFunc = () => {
console.log(this); // 전역 객체를 출력
};
arrowFunc();
반면에 화살표 함수가 객체에 포함되어 있거나 함수가 중첩된 경우 this는 가장 가까운 상위 스코프의 this를 참조한다.
// 객체 리터럴 내부에서의 this
const obj1 = {
prop: 'hello',
func: function() {
console.log(this); // obj를 출력
const arrowFunc = () => {
console.log(this); // obj를 출력
};
arrowFunc();
}
};
obj1.func();
// 객체 리터럴 내부에서 프로퍼티로 사용된 화살표 함수
const obj2 = {
prop: 'hello',
func: () => {
console.log(this); // obj의 상위 스코프 this인 window를 출력
}
}
obj2.func();
이렇듯 화살표 함수는 정적바인딩이 되기 때문에 call/bind와 같은 기능을 사용할 수 없다.
콜백함수를 사용할 때의 this를 추가로 알아보자. addEventListener에서 이벤트를 등록할 때 콜백함수로 화살표 함수를 사용할 때와 function함수를 사용할때의 this값이 다르게 설정된다.
document.querySelector("body").addEventListener('click', () => {
console.log(this); //window를 가리킴
})
document.querySelector("body").addEventListener('click', function() => {
console.log(this); // event의 target을 가리킴
})
자바스크립트에서
this의 값이 결정되는 방식은 다소 복잡할 수 있으므로 코드에서 명확하게 표시하는 것이 중요하다. 따라서this를 사용하기 전에 현재this값이 무엇인지 확인하는 것이 좋다.