this binding error는 일반함수
말고 화살표 함수
를 왜 쓰는지 의문을 가져 this binding error를 알아보게 되었다.
이를 공부하면서 정리한 것은 아래와 같다.
- 일반 함수는 호출 시 객체를 통한 호출이 아니면
전역 객체가 binding
된다.- arrow function안에 있는
this
는 arrow function을 감싸고 있는 가장 가까운일반함수와 binding
된다.- class내부의 method는 proptype으로 생성된다.
- arrow function은 constructor로 동작할 수 없다.
일단, this binding error가 어떤 경우에서 일어나는지 살펴보자
const counter = {
value: 1,
increase() {
this.value++;
},
};
counter.increase();
console.log(counter.value); // 1️⃣
const { increase } = counter;
increase();
console.log(counter.value); // 2️⃣
나는 처음에 이 코드를 봤을 때 1️⃣은 2, 2️⃣는 2에서 증가한 3이라고 예상했지만, 각각 2, 2가 된다.
1️⃣과 2️⃣를 살펴보면 차이점은 객체를 통한 호출이다.
다른 예시로 다음은 생성자 함수
를 통한 this binding error이다.
function Counter() {
this.value = 1;
this.increase = function () {
this.value++;
};
}
const counter = new Counter();
counter.increase();
console.log(counter.value); // 1️⃣
const { increase } = counter;
increase(); // 3️⃣
console.log(counter.value); // 2️⃣
위의 예시에서도 각각 2, 2가 나온다. 왜 이러한 일이 발생하는 것일까? 🤔
이유는 3️⃣에 있다. 3️⃣은 counter에서 increase함수를 받아온 것으로 this가 안에있다.
3️⃣에서 increase 함수가 호출되는 당시 this
를 binding해야 하는데 this가 선언된 내부에서 찾는 것이 아닌 전역에서 찾는다.
즉, 이를 정리하면 아래와 같다.
📒 객체를 통하지 않은 호출은 호출될 당시의 scope에서 this를 binding하므로 browser환경에서는
window
와 binding이 된다.
위에서 increase함수를 일반함수
로 작성했는데 arrow function
으로 작성하면 어떨까?
아래 예시에서는 decrease를 arrow function
으로 작성하였다.
const counter = {
value: 1,
increase() {
this.value++;
},
decrease: () => this.value--
};
counter.increase();
console.log(counter.value); // 1️⃣
const { decrease } = counter;
decrease();
console.log(counter.value); // 2️⃣
실행 결과를 살펴보면 2️⃣는 증가하지 않은 2라는 값을 가진다. arrow function도 일반함수와 동일하게 동작한다고 생각할 수도 있지만
아래 예시를 보면 arrow function이 일반함수와 다르게 동작
함을 알 수 있다.
function Counter() {
this.value = 1;
this.increase = function () {
this.value++;
};
this.decrease = () => this.value--;
}
const counter = new Counter();
counter.increase();
console.log(counter.value); // 1️⃣
const { decrease } = counter;
decrease();
console.log(counter.value); // 2️⃣
이제 출력되는 counter.value
의 값을 살펴보면 1이 출력되며 객체를 통한 호출이 아니어도
감소시키고자 한 의도와 맞게 코드가 돌아감을 확인할 수 있다.
내가 느낀 일반함수와 화살표함수의 가장 큰 차이점이라 생각한다.
📒 일반함수와 달리 arrow function은 호출 시에도 자신이 선언된 부분에서 자신을 감싸고 있는 가장 가까운 일반함수와 binding된다.
이해가 안 될 수 있어 추가적인 설명을 하자면, arrow function을 감싸고 있는 가장 가까운 함수는 Counter.
이다.
위에서 객체 안에 넣은 arrow function
은 arrow function을 object가 감싸고 있다. object는 일반함수와 다르기 때문에 선언된 부분에서 위로 가장 가까운 일반함수를 찾다가 window 객체와 바인딩되는 일이 일어나는 것이다.
하지만, Counter를 생성자 함수로 만들면 arrow function으로 선언한 decrease는 Counter함수(가장 가까운 일반함수)와 this가 binding(this = Counter)되어 감소시킬 수 있는 것이다.
내가 공부하면서 찾은 방법으로는 총 3가지가 있다.
- 일반함수가 아닌 arrow function 사용하기
- bind method 사용하기
- closure 이용하기
class Counter {
constructor(value){
this.value = value;
}
start(){
setInterval(function(){
this.value++;
console.log(this);
console.log(`value: ${this.value}`);
}, 1000)
}
}
const counter = new Counter(0);
counter.start();
실행결과
실행결과를 보면 this -> window, value -> NaN
가 된다. setInterval 실행 시 전역환경에서 실행되어
this가 window이며 window는 value라는 속성이 없어 window.value에 접근했을 때 undefined
가 나오게된다. 그리고 undefined++
++연산자를 통해 NaN
이라는 값이 나오는 것이다.
이제 arrow function을 이용하여 해결해 보자!!
class Counter {
...//
start(){
setInterval(() => {
this.value++;
console.log(this);
console.log(`value: ${this.value}`);
}, 1000);
}
}
...//
실행결과
이제 내가 원하던 방향으로 this가 바인딩이 되고 value값이 1초마다 1씩 증가하는 것을 알 수 있다.
이 부분에서 헷갈렸던 것이 있는데 start function이 arrow function을 감싸고 있는
일반함수의 경우 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.
화살표 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다. 동적으로 결정되는 일반 함수와는 달리 화살표 함수의 this 언제나 상위 스코프의 this를 가리킨다. 이를 Lexical this라 한다
For a function expression, this changes based on the context a function is called in. For arrow functions, this is based on the enclosing lexical scope. Arrow functions follow normal variable lookup. They look for this in the current scope, and if not found, they move to the enclosing scope.