본래 JavaScript에서 함수를 선언할 땐 function
이란 키워드를 쓰죠. 하지만 ES6가 도입되면서 함수를 선언하는 새로운 문법이 등장했습니다. 바로 화살표 함수입니다. 화살표 함수는 말 그대로 화살표 (=>
) 표시를 사용해서 함수를 선언하는 방법입니다.
// 기존 함수
const foo = function () {
console.log('기존 함수');
}
// 화살표 함수
const foo = () => console.log('화살표 함수');
두 함수는 기능적으로 완전히 동일하게 작동합니다. 사실 최근에 JavaScript를 접하기 시작한 분들, 특히 React같은 라이브러리를 주로 접하신 분들은 화살표 함수가 function을 이용한 함수보다 익숙하실 것 같습니다.
언뜻 보기에도 화살표 함수를 사용하는 게 더 심플해 보이긴 합니다. 그런데 화살표 함수를 쓰는 이유는 단지 심플함 때문일까요? 심지어 화살표 함수에는 다음과 같은 제약이 있습니다.
- 무조건 익명함수로만 사용할 수 있음
- 메소드나 생성자 함수로 사용할 수 없음(이유는 뒤에 설명)
이런 제약이 있음에도 불구하고 화살표 함수를 사용하는 이유는 단 하나입니다. 바로 this 바인딩에 차이가 있기 때문이죠. 뜬금없이 this가 무슨 상관일까요?🤔 우선 JavaScript에서 this가 의미하는 바에 대해 알아봅시다.
Java와 같은 언어를 사용하시던 분들은 처음 JavaScript의 this를 접하면 충격과 공포에 빠집니다. 네 제 이야기입니다
JS의 this는 상황에 따라 다르게 바인딩됩니다. 대표적으로 this에 바인딩되는 값들은 이렇습니다.
1번과 2번은 그럴듯한데... 3번이 좀 이상한 것 같습니다. 함수를 호출했을 때 그 함수 내부의 this는 지정되지 않습니다. 그리고 this가 지정되지 않은 경우, this는 자동으로 전역 객체를 바라보기 때문에 함수를 호출하면 함수 내부에서의 this는 전역 객체가 된다고 정리할 수 있습니다. 음... 킹받지만 아쉽게도 자바스크립트 개발자 중 한 명인 더글라스 크락포드조차 이 점은 설계상의 오류라고 지적했습니다.🥲
그러니까, 그냥 함수를 호출한다면 다음과 같은 상황이 연출됩니다.
const cat = {
name: 'meow',
foo1: function() {
const foo2 = function() {
console.log(this.name);
}
foo2();
}
};
cat.foo1(); // undefined
cat.foo1()
메소드 호출 시 내부 함수 foo2
가 실행됨foo2
내부의 this는 지정되지 않아서 곧 전역 객체를 가리킴name
이란 속성은 존재하지 않으므로 undefined
가 뜸🤯 여러모로 복잡하고 전혀 의도했던 바가 아닙니다. 당연히 this.name
은 cat
객체의 내부에 지정한 'meow'일 줄 알았는데 undefined
라뇨! 함수를 호출할 때 그 함수를 호출한 배경의 상황이 this
에 담기면 정말 좋을텐데 말이죠. 그런데.... 놀랍게도 그것을 화살표 함수가 해냅니다.
const cat = {
name: 'meow',
foo1: function() {
const foo2 = () => {
console.log(this.name);
}
foo2();
}
};
cat.foo1(); // meow
위 코드와 달라진 점은 cat
객체의 내부함수 foo2
가 화살표 함수로 선언됐다는 점 뿐입니다. 그런데 이번엔 우리가 의도한대로 meow
가 잘 찍혔습니다. 어떻게 가능한걸까요?
이게 가능한 이유는 화살표 함수에는 ⭐️this가 아예 없기 때문⭐️입니다. 즉, function
으로 선언한 함수를 실행할 땐 this가 존재하긴 하지만 값을 지정하지 않는데, 화살표 함수로 선언한 함수에는 this가 없습니다. 아~ 있었는데? 아니 그냥 없어요.
JavaScript에서는 어떤 식별자(변수)를 찾을 때 현재 환경에서 그 변수가 없으면 바로 상위 환경을 검색합니다. 그렇게 점점 상위 환경으로 타고 타고 올라가다가 변수를 찾거나 가장 상위 환경에 도달하면 그만두게 되는 것이죠. 화살표 함수에서의 this 바인딩 방식도 이와 유사합니다. 화살표 함수에는 this라는 변수 자체가 존재하지 않기 때문에 그 상위 환경에서의 this를 참조하게 됩니다.
더 정확히는, function으로 선언한 함수가 메소드로 호출되냐 함수 자체로 호출되냐에 따라 동적으로 this가 바인딩되는 반면, 화살표 함수는 선언될 시점에서의 상위 스코프가 this로 바인딩됩니다.
조금 어렵고 헷갈리실 수 있습니다. 그냥 편하게 화살표 함수를 쓰면 내가 의도한 바로 그 this가 바인딩되는구나! 하고 생각하셔도 개발하는 데에는 별 지장 없을 것 같습니다!😇
이렇게 JavaScript의 함수 this 바인딩 문제를 깔끔하게 해결해 준 화살표 함수도 사용해선 안되는 때가 있습니다. 상위 환경의 this를 참조한다는 점이 문제가 될 수도 있거든요. 바로 다음과 같은 경우입니다.
const cat = {
name: 'meow';
callName: () => console.log(this.name);
}
cat.callName(); // undefined
이 같은 경우, callName
메소드의 this
는 자신을 호출한 객체 cat
이 아니라 함수 선언 시점의 상위 스코프인 전역객체를 가리키게 됩니다. 어차피 일반 함수를 사용해도 메소드로 호출하면 자신을 호출한 객체를 가리키기 때문에 메소드에서 화살표 함수를 쓸 필요는 없겠죠?😉
const Foo = () => {};
const foo = new Foo() // TypeError: Foo is not a constructor
화살표 함수를 생성자함수로 사용하면 에러가 납니다. 생성자 함수로는 사용할 수 없게 만들어졌어요!
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this); // Window
this.innerHTML = 'clicked';
});
button.addEventListener('click', function() {
console.log(this); // button 엘리먼트
this.innerHTML = 'clicked';
});
원래 addEventListener
의 콜백함수에서는 this에 해당 이벤트 리스너가 호출된 엘리먼트가 바인딩되도록 정의되어 있습니다. 이처럼 이미 this의 값이 정해져있는 콜백함수의 경우, 화살표 함수를 사용하면 기존 바인딩 값이 사라지고 상위 스코프(이 경우엔 전역 객체)가 바인딩되기 때문에 의도했던대로 동작하지 않을 수 있습니다. 물론 상위 스코프의 속성들을 쓰려고 의도한 경우라면 사용할 수 있습니다.
이렇게 화살표 함수를 쓰는 이유는 자바스크립트의 요상한 this 때문이었습니다. 역시 까면 깔수록 매력이 있는 언어네요~😠 틀린 점 지적은 언제나 환영입니다 💓
Reference
화살표 함수 - poiemaweb
화살표 함수 - MDN
너무 도움이 됐습니다! 감사해요!