화살표 함수를 남용하면 안되는 이유

Kimaramy·2020년 3월 10일
13

프론트엔드 개발

목록 보기
7/14
post-thumbnail

화살표 함수에 없는 것들

ES6 화살표 함수(Arrow function) 방식은 작성에 편리하고 보기에도 간결하기 때문에 한번 익히고 나면 이 방식만 고수하는 경우도 종종 있다. 하지만 사람이 쓰고 읽기에 좋다는 이유만으로 남용하게 되면 의도치 않은 실수를 범할 수 있다.

기존 function 키워드 선언 방식과 달리 화살표 선언 방식은 this, prototype, arguments 정보를 생성하지 않는데, 이 3가지가 없음으로 인해 발생하는 실수들을 정리했다.

1. this

일반 함수와 달리, 화살표 함수는 자신이 호출되면서 생성된 실행 콘텍스트에서 thisBinding 정보를 생성하지 않는다. 쉽게 말해서 호출이 되더라도 '누가' 자신을 호출하는지에 대한 정보를 생성하지 않는 것이다. 그래서 만약 화살표 함수 내부에서 this가 참조된다면, 그 this화살표 함수가 호출될 때 생성되는 실행 콘텍스트의 thisBinding 정보가 아니라, 화살표 함수가 선언되어 있는 실행 콘텍스트가 참조하는 thisBinding 정보를 참조한다. 즉, 스코프 상의 this를 참조하는 것이다. this가 위치한 스코프에서 this가 무엇인지 찾고, 해당 스코프에 this가 없다면 스코프 체인을 타고 올라가며 가장 가까운 this를 참조하게 된다.

2. prototype

화살표 함수는 prototype이 존재하지 않는다. 그래서 그래서 만약 화살표 함수로 선언된 함수를 new와 함께 호출하더라도, prototype 객체가 없기 때문에 자신의 인스턴스 객체가 만들어질 수 없다. 그렇기 때문에 화살표 함수는 생성자 함수의 역할을 할 수 없다.

3. arguments

화살표 함수는 arguments 프로퍼티를 생성하지 않는다. 그래서 화살표 함수 내부에서 arguments가 참조된다면 ReferenceError가 발생하거나, 상위 실행 콘텍스트가 있다면 스코프상의 arguments 객체를 참조하게 된다. 이는 this와 같은 맥락이다. 단, 파라미터에 ...args을 넣어 args 배열을 참조함으로써 arguments 객체를 완전히 대체할 수 있다.

참고 : Arrow function and arguments - raram2

화살표 함수로 인해 자주하는 실수

1. 프로퍼티 함수로 선언할 때

const obj = {
  fn1: function() { console.log(this); },
  fn2: () => { console.log(this); },
}

obj.fn1(); // this === myObj
obj.fn2(); // this === Window

fn2를 호출한 myObj가 fn2의 호출자(this)가 될 것이라 생각하기 쉽지만, fn2는 화살표 함수이므로 아무리 호출이 된다고 한들 this는 존재하지 않는다. 그래서 fn2 내부의 this는 상위에 있는 실행 콘텍스트의 this를 찾아 참조한다. 위 예시에서는 상위 실행 콘텍스트가 전역이므로 this는 전역 객체가 된다.

2. 이벤트 핸들러로 선언할 때

addEventListener

1 document.body.innerHTML += '<button>버튼1</button><button>버튼2</button>';
2 const btnList = document.querySelectorAll('button');
3 const [btn1, btn2] = [btnList[0], btnList[1]];

4 btn1.addEventListener("click", function() { console.log(this); });
5 // btn1 클릭시 btn1 객체 출력 (this === btn1)
6 btn2.addEventListener("click", () => { console.log(this); });
7 // btn2 클릭시 Window 객체 출력 (this === Window)

addEventListener은 기본적으로 callback을 자신의 this로 바인딩한다. 그래서 콜백을 화살표 함수로 선언한다면, 설계된 로직상 바인딩 자체가 일어날 수 없게 된다. 이 외에도 addEventListener와 같이 내부 설계상 콜백 함수에 this가 강제 바인딩된 고차 함수의 경우도 마찬가지이니 이점 주의가 필요하다.

이벤트 속성

8 btn1.onclick = function() { console.log(this); }
9 // btn1 클릭시 btn1 객체 출력 (this === btn1)
10 btn2.onclick = () => { console.log(this); }
11 // btn2 클릭시 Window 객체 출력 (this === Window)

이벤트 속성 선언 방식도 이벤트 리스너의 경우와 마찬가지다. 10번째 줄에서 btn2.onclick의 값이 화살표 함수로 선언되었으므로, btn2를 클릭하더라도btn.onclick 내부의 this는 스코프상의 this, 즉 이 예시에서는 전역을 참조하게 된다.

화살표 함수가 유용한 경우

콜백 함수와 내부 함수

function 키워드로 함수를 선언하게 되면, 그 함수을 호출하는 방법에 따라 함수의 this가 동적으로 변하게 된다. 그래서 ES6 이전 고차(High-Order) 함수에서는 콜백 함수의 this를 명확하게 설정하기 위해 자신의 thisself 또는 that 변수에 할당한 후, 콜백에 그 변수를 강제 바인딩(bind) 하는 방법을 사용했다. forEach, map, filter와 같은 고차 함수에는 이런 방법이 적용되어 있으며, 마지막 인자로 콜백을 바인딩할 대상(thisArg)를 넣을 수 있게 옵션을 제공한다. 이렇게 하면 콜백 함수 내부에 사용되는 this가 누구인지 분명해지기 때문에 디버깅이 수월해지기 때문이다.

하지만 콜백(또는 내부 함수)에 화살표 함수를 사용하면 이러한 수고를 하지 않아도 된다. 이들 내부에 사용되는 this는 자연스럽게 상위에 있는 스코프, 즉 고차 함수(또는 내부 함수를 포함하고 있는 함수)의 this가 되기 때문이다. 이처럼 화살표 함수는 같은 맥락에 있어야하는 함수가 다른 this를 가리키게 되는 문제점을 원천차단해준다.

마치며

화살표 함수가 일반 함수보다 겉보기에는 간결하기에 막 사용했었다. 하지만 그 이면에 달라진 모습은 보지 못했고, 이로 인해 일어난 역효과들을 파악하느라 애를 먹었다. 비교적 최신 기술이라고 일단 사용하기보다, 왜 이 방식이 등장했는지, 원래는 어떤 문제점이 있었고 어떤 원리로 보완하는지에 대해 파악해보는 습관을 길러야겠다.

더 알아보기


profile
스타트업에서 Software Engineer로 일하고 있습니다

1개의 댓글

comment-user-thumbnail
2022년 2월 15일

좋은 정리 감사합니다.

답글 달기