해당 내용은 블로그를 이전하면서 기존에 작성된 글을 옮겼습니다.
자바스크립트는 변수나 함수가 선언되면 스코프가 결정되는 정적 스코프(lexical scope) 방식을 따릅니다.
하지만, this는 전역 스크립트가 실행되거나 함수가 호출될 때, 자바스크립트 내부 규칙에 따라서 동적으로 결정됩니다. 이 때문에, 우리는 this를 컨트롤 하기 힘들어하고 어려워 합니다.
this를 사용하다보면, 이전에 ‘this 바인딩 이해하기’ 편에서처럼 우리 예상과 달리 다른 객체 또는 변수에 바인딩 되어 있는 경우가 있어, 바인딩 하는 객체를 변경할 수 있는 call, apply, bind에 대해서 이해해보겠습니다.
function.prototype.call(thisArg[, arg1[, arg2[, …]]]);
기본적인 this 바인딩 변경하기
const animal1 = {
name: 'cat',
sayAnimalName(){
console.log(`this animal is ${this.name}`);
}
}
const animal2 = {
name: 'dog'
}
animal1.sayAnimalName(); // this animal is cat
animal2.sayAnimalName(); // TypeError: animal2.sayAnimalName is not a function
animal1.sayAnimalName.call(animal2); // this animal is dog
animal1이라는 객체에 있는 메서드를 호출할 때, call이라는 메서드를 붙여, call 메서드의 인자로 바인딩할 객체를 animal2로 바꿔주면, this.name에는 animal2의 name 키의 값인 ‘dog’이 담겨 출력됩니다.
클래스 생성자 내부에서 call을 사용하여 다른 생성자 함수의 함수를 활용하기
const GetAndSetFunc = function() {
// 획득자
this.getName = () => {
return this.name;
}
// 설정자
this.setName = (name) => {
this.name = name;
}
}
class Animal {
constructor(name, age){
GetAndSetFunc.call(this);
this.name = name;
this.age = age;
}
sayName(){
console.log(this.name);
}
}
const dog = new Animal('dog', 10);
console.log(dog.getName()); // dog
dog.setName('hotdog');
console.log(dog.getName()); // hotdog
Animal이라는 클래스 내부의 생성자에 GetAndSetFunc라는 생성자 함수가 call 메서드를 붙인 채로 this 바인딩을 Animal이라는 클래스의 생성자로 변경하였습니다. 그럼 이 this는 new라는 연산자로 만든 인스턴스인 dog을 가리킬 것입니다.
따라서, this로 전달된 dog이라는 인스턴스는 GetAndSetFunc라는 생성자 함수가 지닌 getName과 setName이라는 함수를 사용할 수 있게 됩니다.
따라서 위와 같은 결과를 볼 수 있는 것이겠죠?
this와 함께 매개변수 넘기기
const myBirthday = {
getMyBirthdayNum(year, date){
const shortenYear = year.slice(2,4);
const monthAndDay = date;
return shortenYear + monthAndDay;
}
}
const person = {
name: 'kyle',
age: 32,
}
console.log(myBirthday.getMyBirthdayNum.call(person, '1990', '1231')); // 901231
call 메서드를 사용해서 호출할 함수가 인자를 받는 경우, 바인딩 시켜줄 this 외에도 다른 매개변수들을 넘겨줘야 합니다.
따라서, myBirthday라는 객체에 담긴 getMyBirthdayNum이라는 메서드에 person이라는 객체를 바인딩 시켜주고, 인자로 1990, 1231이라는 string을 인자로 넘겨주면, 901231이라는 결과를 출력할 수 있습니다.
function.prototype.apply(thisArg, [argsArray]);
apply는 call 메서드와 기능은 동일하지만 일부 차이점이 있습니다. 인자를 전달하는 방식에서 차이가 있는데요.
call 메서드의 예시 3번에서, call로 호출할 함수가 인자를 여러 개 받는 경우, 그 인자들을 단순히 나열했다면, apply 메서드를 사용할 때는 나열한 인자를 배열로 하나로 묶어 매개변수로 전달한다는 차이점이 있습니다.
const myBirthday = {
getMyBirthdayNum(birthYear, birthDate){
const year = birthYear.slice(2);
const month = birthDate.slice(0,2);
const date = birthDate.slice(2);
return `${this.name}'s birthday is ${year}-${month}-${date}`
}
}
const person = {
name: 'steven',
}
console.log(myBirthday.getMyBirthdayNum.apply(person, ['1990','1231'])); // steven's birthday is 90-12-31
call, apply 메서드처럼 함수 내부에서 this가 바인딩하는 객체를 변경하지만, 차이점은 함수를 실행하지 않는다는 점 입니다.
const os = {
name: 'mac',
callOsName(){
console.log(`this OS is ${this.name}`);
}
}
const window = {
name: 'window'
}
os.callOsName(); // this OS is mac
os.callOsName.call(window); // this OS is window
console.log(os.callOsName.bind(window)); // [Function: bound callOsName]
const newCallOsNameFunc = os.callOsName.bind(window);
newCallOsNameFunc(); // this OS is window
코드의 마지막에서 두 번째 줄을 보면, 상단의 함수를 호출하는 코드와 call 메서드로 window 객체를 this에 바인딩하고 함수를 호출하는 코드와 달리, bind 메서드를 사용하면, window라는 객체를 this에 바인딩만 하고 함수를 호출하지는 않습니다. 따라서, 함수가 바운딩 되었다는 출력값을 얻을 수 있습니다.
함수를 실행하게 하려면, 새로운 변수에 bind 메서드를 사용하는 코드를 담아 호출 해주면 결과를 얻을 수 있습니다.
이렇게 this 바인딩을 변경하는 3가지 메서드들에 대해서 알아보았습니다. 조금 더 this에 대해서 익숙해지는 시간을 가진 것 같습니다.
더 공부해서, 어떻게 심화하여 다룰 수 있는지 나중에 다뤄보면 좋을 것 같습니다.
reference
https://poiemaweb.com/js-this