...
화살표 함수 (Arrow function)
자바스크립트에서 함수를 만들 때는 function 키워드를 사용하여 정의할 수 있다. 예를들어 두 수의 합을 구하는 함수는 다음과 같이 작성할 수 있다.
function sum(a, b) {
return a + b;
}
ES6부터는 화살표 함수(arrow function)라는 새로운 문법 방식으로 함수를 만들 수 있게 되었다. 화살표 함수는 => 기호를 사용하여 function 키워드를 생략할 수 있다.
그래서 위의 함수를 화살표 함수로 바꾸면 아래와 같이 구성할 수 있다.
let sum = (a, b) => {
return a + b;
}
// 만일 함수가 단순한 리턴문만 있다면 한줄로 더 심플하게 줄일 수 있다
const sum = (a, b) => a + b;
화살표 함수 문법 표현
자바스크립트의 화살표 함수를 표현할 수 있는 여러가지 방식은 다음과 같다.
매개변수 표현
매개변수가 하나뿐이면 매개변수의 괄호 부분을 생략할 수 있다.
// 매개변수가 없을 경우
() => { ... }
// 매개변수가 한 개인 경우, 소괄호를 생략할 수 있다.
x => { ... }
// 매개변수가 여러 개인 경우, 소괄호를 생략할 수 없다.
(x, y) => { ... }
함수 몸체 표현
함수 몸체 내의 코드가 한줄이고 단순히 return문 밖에 없다면 중괄호와 return 키워드를 생략할 수 있다.
// single line block
x => { return x * x }
// 함수 몸체가 한줄의 구문이라면 중괄호를 생략할 수 있으며 암묵적으로 return된다. 위 표현과 동일하다.
x => x * x
객체 리터럴 반환
객체 리터럴을 반환하려면 소괄호로 감싸야 한다. 왜냐하면 딸랑 중괄호 { } 쓰면 얘가 함수 블록인지 객체 블록인지 판단할수 없기 때문이다.
() => { return { a: 1 }; }
() => ({ a: 1 }) // 위 표현과 동일하다. 객체 반환시 소괄호를 사용한다.
고급 조합 표현
// 매개변수 기본값
(a = 1, b = 2) => a + b;
// 나머지 매개변수
(...args) => args;
// 구조 분해 할당
([a, b] = [1, 2]) => a + b;
let age = prompt("나이를 알려주세요.", 18);
let welcome = (age < 18) ?
() => alert('안녕') :
() => alert("안녕하세요!");
welcome();
화살표 함수의 특징
자바스크립트의 화살표 함수는 function 함수를 축약하여 표현하는 문법이지만, 일반적인 함수와 다른 몇 가지 고유한 특징을 가진다. 이러한 특징은 화살표 함수를 유용하게 만들기도 하지만, 함정이 되기도 하기 때문에 유의깊게 살펴보아야 한다.
화살표 함수엔 this가 없다
우선 화살표 함수에는 this가 존재하지 않는다. 그래서 화살표 함수에서 this 키워드로 접근하면, 자신이 아닌 외부에서 값을 가져오게 된다.
예를들어 다음 user 라는 객체의 sayHi의 프로퍼티가 어떠한 형태의 함수로 이루어져 있느냐에 따라 this.name 이 가리키는 값이 달라지게 된다. 화살표 함수는 자신만의 this를 가지지 않기 때문에, 자신을 감싸는 외부 환경의 this를 그대로 따르기 때문이다. 즉, 화살표 함수에서 this는 정적으로 결정된다.
var name = "Global";
let user = {
name: "Inpa",
sayHi: function() { // 일반 함수
console.log(this.name);
}
};
user.sayHi(); // Inpa
var name = "Global";
let user = {
name: "Inpa",
sayHi: () => { // 화살표 함수
console.log(this.name);
}
};
user.sayHi(); // Global
따라서 이러한 특징 때문에 화살표 함수 내에서 this 키워드를 사용할때 굉장희 주의 깊게 따져야 한다. 사람이 생각하는 this와 엔진이 할당하는 this 값이 완전히 다르기 때문이다.
그런데 이러한 화살표 함수의 this 특징 때문에 자바스크립트의 콜백 함수에서 유용히 쓰일 수도 있다. 예를들어 group 이라는 객체에 class 와 students 프로퍼티를 정의하고 이 정보를 forEach() 반복문으로 출력하는 showList() 메서드가 있다고 해보자.
/* 일반 함수로 쓸 경우 */
let group;
group = {
class: "1반",
students: ["짱구", "철수", "훈이"],
showList() {
group.students.forEach(
function(student) {
// this ==== window
console.log(this.class + ': ' + student)
}
);
}
};
group.showList();
만일 일반 함수로서 forEach() 의 콜백 함수에 넣게 된다면, 이 콜백 함수 내의 this는 group이 아닌 window 객체를 가리켜 결과적으로 this.class 의 출력값은 undefined 이 되게 된다. (정의하지 않았으니까)
반면, 화살표 함수를 사용하게 되면, forEach() 의 콜백 함수 내 this 는 정확히 group 객체를 가리키게 된다. 왜냐하면 콜백 함수는 별도의 자신만의 this 를 가지게 되어 window 객체를 가리키게 되지만, 화살표 함수는 자신만의 고유의 this가 없기 때문에 외부(group)의 this를 따르기 때문이다.
/* 화살표 함수로 쓸 경우 */
// 화살표 함수의 this는 상위의 this를 그대로 가져온다.
let group;
group = {
class: "1반",
students: ["짱구", "철수", "훈이"],
showList() {
group.students.forEach(
(student) => {
// this ==== group
console.log(this.class + ': ' + student)
}
);
}
};
group.showList();
즉, 화살표 함수 내의 this.title은 group.title과 같게 된다.
화살표 함수엔 arguments 가 없다
자바스크립트 함수의 arguments는 일반적인 함수가 호출될 때 전달된 인수들을 담고 있는 유사 배열 객체다.
function argsFunc() {
console.log(arguments);
}
argsFunc(1, 2, 3); // [1, 2, 3]
화살표 함수는 일반 함수와는 다르게 모든 인수에 접근할 수 있게 해주는 유사 배열 객체 arguments를 지원하지 않는다. 따라서 실행하면 다음과 같이 에러가 발생하게 된다.
let argsFunc = () => {
console.log(arguments);
}
argsFunc(1, 2, 3); // ! Error
대신에 나머지 매개변수(rest parameter)라는 문법을 사용하여 인수들을 배열로 받을 수 있다.
let argsFunc = (...args) => { // ...나머지 매개변수
console.log(args);
}
argsFunc(1, 2, 3); // [1, 2, 3]
화살표 함수는 생성자 함수가 없다
생성자 함수란 new 연산자와 함께 호출되어 객체를 생성하는 함수를 말한다.
function User(name) {
this.name = name;
}
let user = new User("Alice");
console.log(user.name); // Alice
역시 화살표 함수는 this가 없기 때문에 new와 함께 실행할 수 없다. 따라서 화살표 함수는 new와 함께 호출할 수 없다.
let User = (name) => {
this.name = name;
}
let user = new User("Alice"); // TypeError: User is not a constructor
따라서 화살표 함수는 객체를 생성하는 용도로 사용할 수 없다. 그래서 화살표 함수는 보통 콜백 함수나 익명 함수로서 사용되는 편이다.
추가로 화살표 함수는 클래스 관련 키워드인super도 없다.
화살표 함수를 남용해서는 안되는 경우
위에서 살펴본 바와 같이 화살표 함수는 일반 함수와는 달리 자신만의 this를 가지지 않는다. 그래서 화살표 함수를 자바스크립트에서 사용함에 있어 몇가지 제약이 생긴다.
- 일반 객체의 메소드 사용 X (this가 없으니까)
- prototype 메소드로 사용 X (마찬가지 프로토타입 객체 this참조 불가능)
- 생성자 함수로 사용 X (화살표 함수는 prototype 프로퍼티를 가지고 있지 않다)
- addEventListener 함수의 콜백함수는 조심해서
- 화살표 함수는 call, apply, bind 메소드를 사용하여 this를 변경할 수 없다
일반 객체의 메소드 사용 X
만일 this 참조를 자주 사용해야할 메서드 로직이라면, 일반 객체의 메서드로서 화살표 함수 사용은 피해야 한다. 왜냐하면 this가 없으니까 메소드를 호출한 객체를 가리키지 않고 상위 컨택스트인 전역 객체 window를 가리키게 때문이다.
const person = {
name: 'Lee',
sayHi: () => console.log(`Hi ${this.name}`)
};
person.sayHi(); // Hi undefined
prototype 메소드로 사용 X
화살표 함수로 정의된 메소드를 prototype 객체로서 할당하는 경우도 위와 같이 동일한 문제가 발생한다. 따라서 prototype에 메소드를 할당하는 경우 일반 함수를 할당하도록 한다.
// 모든 객체는 기본적으로 Object객체에 프로토타입 체이닝 되어있다.
const person = {
name: 'Lee',
};
Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);
person.sayHi(); // Hi undefined
생성자 함수로 사용 X
생성자 함수는 prototype 프로퍼티를 가지며 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용한다. 하지만 화살표 함수는 prototype 프로퍼티를 가지고 있지 않기 때문에, 따라서 위와 같은 이유로 사용하면 안된다.
const Foo = (name) => {
this.name = name
};
// 화살표 함수는 prototype 프로퍼티가 없다
console.log(Foo.hasOwnProperty('prototype')); // false
const foo = new Foo("FFF"); // TypeError: Foo is not a constructor
Object.getOwnPropertyNames()메소드는 객체의 모든 속성 이름을 포함하는 배열을 반환한다.
addEventListener의 콜백 함수는 조심해서
화살표 함수를 addEventListener 함수의 콜백 함수로 사용할 때도 주의해야 한다. addEventListener 함수는 이벤트가 발생할 때마다 콜백 함수를 호출하는데, 이때 콜백 함수의 this는 이벤트가 발생한 요소를 가리키게 된다.
<button>Click me!</button>
<script>
let button = document.querySelector('button');
button.addEventListener('click', function() {
console.log(this); // <button> 요소
});
</script>
하지만 화살표 함수를 콜백 함수로 사용하면, this는 외부 렉시컬 환경의 this를 가리키므로, 원하는 결과가 나오지 않을 수 있다.
<button>Click me!</button>
<script>
let button = document.querySelector('button');
button.addEventListener('click', () => {
console.log(this); // window 객체
});
</script>
따라서 addEventListener 함수 내에서 this 키워드를 사용하는 경우에는 화살표 함수 대신 일반적인 함수를 사용하는 것이 올바른데, 하지만 현업에서는 forEach 와 더불어 addEventListener 함수의 콜백 함수를 코드 표현의 간략화를 위해 화살표 함수를 쓰는 사례가 많다. 따라서 이런 경우에는 this 키워드를 사용하지 않고, addEventListener 함수의 매개변수인 event 객체의 currentTarget 혹은 target 프로퍼티를 사용하여 이벤트가 발생한 요소에 접근하는 편이다.
<button>Click me!</button>
<script>
let button = document.querySelector('button');
button.addEventListener('click', (event) => {
console.log(event.target); // <button> 요소
});
</script>
call, apply, bind 로 this를 변경할 수 없다
보통 자바스크립트에서 this 키워드를 조작하는 대표적인 방법으로 call, apply, bind 함수를 이용한 기법이 있는데, 이는 화살표 함수에는 통하지 않는다. 간단히 생각하면 된다. 화살표 함수는 this 자체가 없기 때문이다.
const obj1 = { message: 'Hello from obj1' };
const obj2 = { message: 'Hello from obj2' };
// 일반 함수
function normalFunction() {
console.log(this.message);
}
// 화살표 함수
const arrowFunction = () => {
console.log(this.message);
};
// 일반 함수에서 this 변경
normalFunction.call(obj1); // 'Hello from obj1'
normalFunction.call(obj2); // 'Hello from obj2'
// 화살표 함수에서 this 변경 (변경이 되지 않음)
arrowFunction.call(obj1); // undefined
arrowFunction.call(obj2); // undefined
이처럼 화살표 함수는 ES6에서 도입된 새로운 방식으로, 간단하고 간결한 문법으로 유연함과 표현력을 높여주는 함수를 만들 수 있지만, 모든 상황에 화살표 함수를 사용할 수 있는 것은 아니므로, 주의해야 할 점도 있다. 따라서 화살표 함수의 특징과 한계를 잘 이해하고, 적절한 상황에 맞게 화살표 함수와 일반적인 함수를 적절하게 선택하고 활용하는 것이 중요하다.
# 참고자료
https://ko.javascript.info/arrow-functions-basics
https://poiemaweb.com/js-event
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.