자바스크립트 this 바인딩에 대한 이야기
● this 키워드?
this 키워드는 자신이 속한 객체또는 생성자 함수를 통해 생성할 인스턴스 를 가르킨다.
예를 들어 메소드에서 this 키워드를 사용하면 어떤 객체에서 호출이 되었는지 확인이 가능하고 생성자 함수를 통해 생성될 인스턴스 또한 참조가 가능하다.
● 객체에서 재귀호출로 참조하기
const obj = {
data: 10,
getData() {
return obj.data;
}
};
console.log(obj.getData());
하지만 위 코드는 일반적이지 않고 바람직하지 않다고 한다.
객체에서는 재귀호출로 참조가 가능하지만 생성자 함수에서는 위 방법이 불가능하다.
그 이유는 생성자 함수의 경우 new 키워드로 인스턴스가 생성되어야 참조가 가능하기 때문이다.
● 메소드에서의 this 바인딩
객체 내부 메소드에서 this 바인딩은 메소드를 호출한 객체를 바인딩 한다. 즉 객체를 가르킨다.
const obj = {
data: 10,
getData() {
console.log("getData 메서드 내부 this : ", this);
},
};
obj.getData(); // { data: 10, getData: [Function: getData] }
메소드를 호출한 객체는 obj 이므로 obj.getData()를 하게될 경우 obj 자체가 출력된다.
● 일반 함수에서의 this 바인딩
일반함수로 호출된 모든 함수 내부의 this 에는 전역객체가 바인딩 된다.
일반함수로 호출된 콜백함수, 중첩함수 등 모든 함수가 이에 적용된다.
- 일반함수로 호출한 콜백 함수
var data = 1;
const obj = {
data: 100,
getData() {
setTimeout(function () {
console.log('obj의 data : ', this.data);
})
}
};
obj.getData();
이처럼 obj 객체 내부에서 정의한 data가 전역객체에 존재하는 data 값이 출력된다.
- 일반함수로 호출된 중첩함수
var age = 10;
function person() {
let age = 20;
function findPerson() {
console.log(`findPerson의 this.age : ${this.age}`);
}
findPerson();
}
person();
동일하게 전역객체 age의 값을 가져온다.
● 생성자함수에서의 this 바인딩
생성자함수에서의 this 바인딩은 생성자함수로 생성될 인스턴스를 가르키게 된다.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function() {
console.log(`${this.name}의 나이는 ${this.age} 입니다.`);
}
const firstPerson = new Person('Kim', 23);
const secondPerson = new Person('Lee', 25);
firstPerson.getInfo();
secondPerson.getInfo();
각각 생성된 firstPerson 과 secondPerson 의 인스턴스에 정의된 this.name, this.age 를 출력한다.
● 화살표함수에서의 this 바인딩
화살표함수의 경우 this는 상위스코프의 this를 바인딩하게 된다.
var age = 10;
const person = {
age: 20,
getAge() {
setTimeout(() => {
console.log(this.age);
});
},
}
person.getAge();
위 결과처럼 전역객체의 프로퍼티인 age(10)가 아닌 person 객체 내부에서 정의된 age(20)를 가져오게 된다.
● function의 메서드 apply, call, bind
일반함수에서는 this 바인딩이 항상 전역객체를 가르키기 때문에 원하는 로직으로 동작을 안할수도 있다.
이때 Function.prototype 내부에 정의된 apply, call, bind 메서드를 통해 이를 해결할 수 있다.
- Function.prototype.apply
/**
* Function.prototype.apply
* 이 메서드는 this 객체와 인수 리스트를 배열로 받아서 전달해준다.
* @param thisArg - this에 바인딩할 객체
* @param argsArray - 배열로된 인수 리스트
*/
function getThis() {
console.log("arguments: ", Array.from(arguments));
return this;
}
const obj = { x: 1 };
console.log(getThis()); // Window {...}
console.log(getThis.apply(obj, [1, 2, 3])); // arguments: [ 1, 2, 3 ], { x: 1 }
일반함수로 호출하게되면 전역객체를 바인딩하게 된다.
하지만 apply 메소드를 사용하여 obj를 thisArg로 넘기게되면 this에는 obj({x: 1})가 바인드된다.
또한 apply 메소드의 2번쨰 인자로 파라미터값을 넘겨주면 arguments로 확인이 가능하다.
- Function.prototype.call
/**
* Function.prototype.call
* 이 메서드는 this 객체와 인수를 콤마로 구분하여 전달해준다.
* @param thisArg - this에 바인딩할 객체
* @param arg1, arg2, ... - 콤마로 구분된 인수 리스트
*/
function getThis() {
console.log("arguments: ", Array.from(arguments));
return this;
}
const obj = { x: 1 };
console.log(getThis()); // Window {...}
console.log(getThis.call(obj, 1, 2, 3)); // arguments: [ 1, 2, 3 ], { x: 1 }
apply 메소드와 유사하나 인수목록을 배열형태가 아닌 콤마로 구분해서 받는다는 차이점이 있다.
- Function.prototype.bind
apply, call 메서드는 매개변수를 넘겨줬지만 bind는 this객체만 넘겨주는 메서드이다.
또한 apply, call과는 다르게 함수호출을 하지 않으니 명시적으로 호출을 해줘야한다.
/**
* Function.prototype.bind
* 이 메서드는 this 객체와 인수를 콤마로 구분하여전 달해준다.
* @param thisArg - this에 바인딩할 객체
*/
function getThis() {
return this;
}
const obj = { x: 1 };
console.log(getThis()); // Window {...}
console.log(getThis.bind(obj)); // [Function: ...]
console.log(getThis.bind(obj)()); // { x : 1 }
위 결과처럼 명시적으로 호출을 해주지 않으면 함수 선언부를 그대로 출력하게 된다.