class 내부의 함수를 콜백함수로 전달해서 사용하려고 하는데 에러가 있어서 콜백함수 내부에서의 this 바인딩에 대해서 공부했다.
그런데 관련해서 코드를 작성할 때 일반함수가 아닌 화살표 함수로 작성을 할 때의 차이점이 있다는 것을 알고 이어서 화살표함수에 대해서도 집중적으로 알아보려고 한다.
다음은 MDN과 Medium (Arrow functions vs regular functions in JavaScript) 을 바탕으로 작성한 내용이다..
화살표 함수는 ES6 부터 자바스크립트에 포함되었으며 심플하지만 강력하게 쓰이고 있다.
일반함수보다 간결한 만큼 일반함수와 달리 제한되는 것이 있기 때문에 사용을 지양되는부분도 존재한다.
그렇기 때문에 각 쓰임에 대해서 알고 사용해야 한다.
화살표 함수는 항상 익명으로 쓰이며 매개변수의 개수에 따라서 중괄호를 생략할 수도 있으며
짧은 구문으로 간결하게 사용할 수 있다.
하지만 일반함수와 달리 this, arguments 등을 바인딩하지 않으며 생성자로도 사용할 수 없다.
const animals = [
'cat',
'dog',
'bird',
'rabbit'
]
const someAnimal = animals.map((lives, index) => {
return [lives, index];
})
// [["cat", 0], ["dog", 1], ["bird", 2], ["rabbit", 3]]
const animalLength = animals.map(function (lives) {
return lives.length;
});
// [3, 3, 4, 6]
animals 배열의 각 요소의 length를 추출해서 새로운 배열로 만들려고 한다.
일반함수로 위와 같이 코드를 작성할 수 있지만 화살표함수로 다음과 같이 작성할 수 있다.
// 화살표함수
const animalLength = animals.map(lives => {
return lives.length;
})
let add = (x, y) => {
return x + y;
}
일반적으로 화살표함수는 () => { }
방식으로 쓰고,
매개변수가 하나일 경우에는 감싸고 있는 소괄호를 생략할 수 있다.
const animalLength = animals.map(lives => lives.length);
let add = (x, y) => x + y;
화살표 함수 내에 하나의 return문 만이 존재한다면 중괄호를 생략하고 한줄로 나타낼 수도 있다.
일반 함수와 달리 화살표 함수에는 고유한 this가 없으며 인자를 바인딩할 수 없다.
렉시컬 스코프 (lexical scope)란, 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것을 뜻한다. 화살표함수는 자신의 this를 가질 수 없기 때문에 자신의 상위의 렉시컬 범위(lexical scope)에서 this를 검색해서 사용한다.
var name ="Arrow function"
let me = {
name: "Regular function",
thisInArrow:() => {
console.log("Example of " + this.name); // --- (1)
},
thisInRegular(){
console.log("Example of " + this.name); // --- (2)
}
};
me.thisInArrow();
me.thisInRegular();
(1) Example of Arrow function 을 출력
화살표함수 내부에서는 this값이 없기 때문에 자신과 가까운 상위 스코프에서 this 값을 찾는다.
this바인딩을 못하므로, me 객체 또한 바인딩하지 않는다.
thisInArrow 함수가 아닌 상위스코프 즉 전역의 객체를 가리키므로
this는 전역에 정의되어 있는 name을 가리킨다.
만약 전역에서도 찾지못했을 경우 window를 가리키고, strict-mode였다면 undefined를 리턴했을 것이다.
(2) Example of Regular function 을 출력
thisInRegular 화살표함수가 아니므로 me.thisInRegular();
메서드 호출 방식을 따라서
this는 me를 가리키고 this.name 은 "Regular function"이 된다.
const foo = () => {};
console.log(typeof foo) // "function"
console.log(foo.prototype); // undefined
화살표함수도 type은 분명히 함수가 맞다.
하지만 이들에겐 prototype 속성이 없다.
화살표 함수가 심플하면서 강력한 코드지만, 일반 함수를 대체해서 모든 부분에서 사용하기엔 좋지 않다.
다음은 화살표 함수를 사용하지 않기를 권장하는 항목들이다.
var dog = {
lives: 9,
jumps: function () {
this.lives--;
console.log(this.lives); // 8
}
}
dog.jumps();
dog.jumps();
를 실행하면 콘솔에는 dog.lives 9에서 1이 차감된 8이 출력된다.
jumps 함수를 화살표함수로 바꿔서 실행해보겠다.
var cat = {
lives: 9,
jumps: () => {
console.log(this); // ---(1) window
console.log(this.lives); // ---(2) undefined
this.lives--;
console.log(this.lives); // ---(3) NaN
}
}
cat.jumps();
일반 함수에서 화살표 함수로만 바꿨을 뿐인데, lives가 제대로 출력되지 않는다.
화살표함수에서는 this를 바인딩하지 못하기 때문에 상위 스코프에서 this를 찾으므로
(1)에서 this의 값으로 window가 출력되지만,
(2)에선 window 전역에 lives라는 객체는 존재하지 않으므로 undefined가 출력된다.
undefined 값에 연산을 하게 되면 NaN이 출력되므로 (3)에는 NaN이 출력된다.
const pressButton = document.querySelector('.press');
pressButton.addEventListener('click', () => {
console.log(this); // window
this.classList.toggle('push');
});
버튼을 클릭했을 때 push 속성을 추가하는 코드가 있다.
하지만 이 이벤트 핸들러는 작동하지 않고 다음과 같은 에러를 뱉는다.
"error"
"TypeError: Cannot read property 'toggle' of undefined
this.classList.toggle('push');
에서 this는 pressButton에 바인딩 되지 않고
대신 부모 스코프에 바인딩되기 때문에 에러가 발생한다.
또한 화살표함수는 일반함수 보다는 가독성이 떨어지는 부분이 있기 때문에
남용할 경우 자신을 포함한 다른사람들이 봤을 때 해석하기 어려운 코드가 될 수도 있다.