자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 특수한 식별자가 필요하다. 이를 위해 자바스크립트는 this라는 특수한 식별자를 제공한다.
this를 통해서 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.
이때, this가 가르키는 값(=bind,binding)은 함수를 호출하는 방식으로 동적으로 결정된다.
기본적으로 this는 코드 어디에서든 호출할 수 있으며 전역 객체(window, global)가 바인딩 된다.
객체의 메서드와 생성자 함수 내에서 사용하는 this는 해당 객체나 생성할 인스턴스를 가리킨다.
function innerFunc() {
console.log(this);
}
const obj1 = {
outer: function() {
console.log(this) // (1)
innerFunc(); (2)
}
}
const obj2 = {
method : innerFunc,
}
obj1.outer();
obj2.method(); (3)
(1)은 obj 객체
(2)는 전역 객체
(3)은 obj2 객체를 가리킨다.
위 처럼 함수를 어떻게 호출하냐에 따라서 this의 값이 달라진다
this의 목표 자체가 자신이 속한 객체나 자신이 생성한 인스턴스의 프로퍼티나 메서드를 참조하기 위해 존재하는 만큼 일반 함수에서 굳이 this를 사용할 일이 없다. (전역 객체 이용하려는 경우를 제외)
// 일반 함수에서 this 호출 시 전역 객체를 바인딩한다
function func() {
console.log(this) // window
}
func()
function globalFunction() {
console.log(this);
function nestedFunction() {
console.log(this);
}
nestedFunction(); // window
}
outer() // window
하지만, 객체 메서드 내에서 정의한 중첩함수 또는 메서드에게 전달한 콜백함수가 일반함수로 호출이 된다면 이때는 this가 필요할 수 있는데 this가 없으면 동작하기 힘들 수 있다.
예를 들어 다음과 같이 객체 메서드 foo를 실행하면 setTimeout의 콜백함수 내에서 obj.value의 값을 조작하는 뭔가 함수가 필요한데 이를 일반 함수를 사용하게 된다면 setTimeout의 콜백함수 내 this는 전역 객체를 참조하게 된다.
const obj = {
value: 100,
foo() {
setTimeout(function() {
// obj.value를 이용한 무언가 조작
this.value ???
},100);
}
}
위와 같은 코드 예에서 우회적으로 this를 바인딩 하여 다음과 같은 방식으로 사용할 수 있다.
const obj = {
value : 100,
foo() {
const that = this;
setTimeout(function() {
// obj.value를 이용한 무언가 조작
that.value++;
console.log(that.value); // 101
},100);
}
}
혹은 다음과 같이 화살표 함수를 이용할 수 있다.
화살표 함수는 실행 컨텍스트를 생성할 때 this binding 과정 자체가 없어서 this는 이전의 값, 상위 값이 유지가 된다. 화살표 함수와 일반 함수와 가장 큰 차이점이 이 점이다.
const obj = {
value : 100,
foo() {
setTimeout(() => {
this.value++;
console.log(this.value); //101
},100);
}
}
위 방법 이외에도 this를 명시적으로 바인딩 할 수 있는데 자바스크립트는 이를 위해 apply, call, bind 메서드를 제공한다.
apply와 call은 본질적인 기능은 함수를 호출하여 실행하는것이다. 이때 첫번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩한다.
apply와 call의 차이는 호출한 함수에 인수를 전달하는 방식만 다를 뿐 동일하게 동작한다.
예를 들어 다음과 같은 함수가 있다고 하자
function forEach(func) {
for(let i = 0,len = this.length;i<len;i++) {
func(this[i],i,this);
}
}
이 함수에 array나 arraylike객체를 넣어서 실행을 하면 다음과 같이 사용할 수 있다.
function forEach(func) {
for (let i = 0, len = this.length; i < len; i++) {
func(this[i], i, this);
}
}
const arr = [1, 2, 3, 45];
const arrLike = {
0: 1,
2: 3,
length: 2,
};
forEach.call(arr, console.log);
forEach.apply(arr, [console.log]);
둘이 동일한 값을 반환한다. forEach의 매개변수에 arr를 넣을 필요가 없이 this를 이용하여 위와 같이 구현할 수 있다.
감탄.
이를 활용하면 다음과 만들 수 있을 것 같다.forEach 함수를 하나를 만들어서 배열뿐 아니라 객체, 유사배열까지 이용할 수 있다. forEach는 조금 더 다양한 곳에 사용될 수 있게 됐다.
function forEach(func) {
if (typeof this === 'object' && !this.length) {
for (const key in this) {
func(this[key], key, this);
}
return;
}
for (let i = 0, len = this.length; i < len; i++) {
func(this[i], i, this);
}
}
forEach.call(infoMap, console.log);
forEach.call([1, 2, 3, 4, 5], console.log);
forEach.call({ 0: 1, length: 1 }, console.log);
forEach.call(document.querySelectorAll('div'),console.log);
const func = funcion(a,b,c,d) {
console.log(this,a,b,c,d);
}
const bindedFunc1 = func.bind({x: 1});
bindFunc1(5,6,7,8) // {x: 1}, 5,6,7,8
// 부분 적용 함수
const bindedFunc2 = func.bind({x:1},4,5);
bindedFunc2(5,6) // {x: 1}, 4,5,6,7
bindedFunc2(7,8) // {x:1},4,5,7,8