ES6+ 향상된 함수 기능 (2) - Arrow function
ES6에서 함수의 기능을 온전하게 완성했다고 볼 수 있다.
- 매개변수에 기본값을 줄 수 있게 됨.
- 나머지 매개변수를 통해 가변 길이 매개변수를 좀 더 명시적으로 표현할 수 있음.
- Named parameter(명명된 매개변수)를 통해서 함수를 호출하는 코드의 가독성이 훨씬 좋아졌다.
- Arrow function(화살표 함수)가 추가되면서 함수 코드가 간결해졌고, this 바인딩에 대한 고민을 덜 수 있게 되었다.
1. 함수를 정의하는 새로운 방법
#1 Arrow fucntion (화살표 함수) 간단 사용법
ES6부터 함수를 정의하는 방법이 하나 추가되었다.
이 Arrow function을 이용하면 함수를 간결하게 작성할 수 있다.
const plus = (a,b) => a+b;
console.log(plus(1,2)); // 3
const plus10 = (a) => a + 10;
console.log(plus10(5)); // 15
const plusReturnObj = (a,b) => ({result: a+b});
console.log(plusReturnObj(2,5)); //{result: 7}
plus 와 plus10의 경우처럼 중괄호를 생략하면 계산의 결괏값이 리턴이 된다.
return 키워드 또한 생략할 수 있다.
plusReturnObj의 경우처럼 객체를 반환하기 위해서는 () 소괄호로 감싸야 한다.
#2 Arrow fucntion (화살표 함수)의 코드가 여러 줄인 경우
코드를 { } 중괄호로 감싸고 return 키워드를 사용한다.
const plus = (a, b) => {
if(!a || !b){
throw new Error('zero is not applicable');
}
return a+b;
};
#3 this 와 arguments가 바인딩 되지 않는 Arrow fucntion
Arrow function에서는 this 와 arguments를 사용할 수 없다.
만약 arguments를 사용하고 싶다면 rest parameter(나머지 매개변수)를 사용하자.
const printText = (...rest) => console.log(rest);
printText("Hello", "World!"); //["Hello", "World!"]
그리고 일반 함수에서 this 바인딩 때문에 버그가 발생하는 경우를 살펴보자.
window["resultValue"] = 1
const otherObj = {
resultValue: 1,
increase: function(){
console.log(this);
this.resultValue++;
},
};
otherObj.increase();
console.log(otherObj.resultValue); // 2
const otherIncrease = otherObj.increase;
otherIncrease();
console.log(otherObj.resultValue); // 2 변하지 않음
console.log(resultValue) // 2
otherObj 오브젝트의 increase 함수는 일반 함수로 정의하였다.
increase 함수의 this는 자신을 정의한 otherObj 오브젝트를 가리킨다.
하지만 otherIncrease 함수의 this는 자신을 정의한 오브젝트 즉, 브라우저 환경에서 window 객체가 된다.
#4 Constructor (생성자) 함수 내부의 화살표 함수의 this
javascript 의 모든 개체( Object.create(null)로 만든 개체 제외 )에는 생성자 함수가 있습니다.
function Tree(name) {
this.name = name
}
let theTree = new Tree('Redwood')
console.log('theTree.constructor is ' + theTree.constructor)
이 생성자 함수 내부에서 화살표 함수에서 this를 사용해 봅시다.
function Calculator() {
this.value = 1
this.increase = () => this.value++;
}
const obj = new Calculator();
obj.increase();
console.log(obj.value); // 2
const increaseFn = obj.increase;
increaseFn();
console.log(obj.value); // 3
화살표 함수의 this는 new Calculator()로 생성된 개체가 된다.
반면 일반 함수로 정의하면
function Calculator() {
this.value = 1
this.increase = function (){
this.value++;
}
}
const obj = new Calculator();
obj.increase();
console.log(obj.value); // 2
const increaseFn = obj.increase;
increaseFn();
console.log(obj.value); // 2
앞서 살펴본 일반 함수의 this 정의와 같이
브라우저 환경이라면 increaseFn 함수를 정의한 window 객체가 this가 될 것이다.
#5 SetInterval 함수 사용 시 this 문제
다음의 1초마다 this.value를 증가시키는 코드에서 this가 어떤 객체를 참조하는지 생각해보자.
function IncreaseFn() {
this.value = 1;
setInterval(function increase(){
this.value++;
}, 1000);
}
const obj = new IncreaseFn();
// 몇 초뒤
console.log(obj.value); // 1
setInterval 함수의 인자인 increase 함수는 new IncreaseFn() 시점에 정의되면서
이때 this는 브라우저 환경에서 window 객체가 된다.
ES5에서는 편법으로 이 문제를 해결하였습니다.
function IncreaseFn() {
this.value = 1;
var $this = this;
setInterval(function increase(){
$this.value++;
}, 1000);
}
const obj = new IncreaseFn();
// 몇 초뒤
console.log(obj.value); // value 변경 됨
바로 Closure(클로저)를 이용해서 미리 저장해둔 $this 변수를 통해 this 객체에 접근한다.
잠시 Closure(클로저)에 대해서 알아봅시다.
클로저는 javascript에서 내부 함수가 외부 함수의 scope( 영역 )에 접근할 수 있는 기능입니다.
함수가 생성될 때마다 클로저가 생성됩니다.
이 클로저가 접근할 수 있는 변수는 그 함수를 감싸고 있는 외부 상위 함수들의 매개변수와 내부 변수들입니다.
const suffix = " -log.txt"; // 전역변수
function makeLogFn(prefix) { // 외부함수
return function log(text){ // 내부함수
return prefix + text + suffix;
}
}
const infoLog = makeLogFn("INFO : ");
console.log(infoLog("somthing")); // 1번
// INFO : somthing -log.txt
const errorLog = makeLogFn("ERROR : ");
console.log(errorLog("somthing")); // 2번
// ERROR : somthing -log.txt
console.log(infoLog("somthing")); // 3번
// INFO : somthing -log.txt
log 함수는 외부 함수의 매개변수에 접근할 수 있다.
1번에서 infoLog 함수가 생성된 이후에도 생성 당시 매개변수 값을 참조할 수 있다.
2번에서는 다른 매개변수로 함수가 생성되었음을 할 수 있다.
3번에서는 2번에서 errorLog 함수 생성 시 infoLog 함수에 영향을 주지 않는 것을 확인할 수 있다.
따라서 Log 함수 별로 각각의 클로저 환경이 생성된 것을 확인할 수 있다.
이를 화살표 함수를 이용해 편법을 사용하지 않고도 원하는 기능을 구현할 수 있습니다.
function IncreaseFn() {
this.value = 1;
setInterval(() => {
this.value++;
}, 1000);
}
const obj = new IncreaseFn();
// 몇 초뒤
console.log(obj.value); // value 변경 됨
위에 예에서 화살표 함수에는 자기 자신의 this는 없습니다.
이 화살표 함수를 둘러싼 scope(범위)가 this가 되는 것입니다.
화살표 함수는 일반 변수 참조 규칙을 따릅니다.
그래서 this 찾기 위해 화살표 함수의 가까운 scope를 참조합니다.
화살표 함수는 화살표 함수가 정의된 scope를 기반으로 this를 생성합니다.
그래서 화살표 함수는 각기 다른 scope에서 실행될 수 있도록 설계 된 call, apply, bind 메서드에 적합하지 않습니다.
일반적인 예제입니다.
// 간단한 Object 생성
var obj = {
num: 100
}
// window 객체에도 num을 생성 합니다.
// 사용되지 않는 다는것을 증명하기 위해서입니다.
window.num = 2020;
// 일반 함수에서의 this
var add = function (a, b, c) {
return this.num + a + b + c;
}
// call
var result = add.call(obj, 1, 2, 3) // obj scope(범위) 생성
console.log(result) // 106
// apply
const arr = [1, 2, 3]
var result = add.apply(obj, arr) // obj scope(범위) 생성
console.log(result) // result 106
// bind
var result = add.bind(obj) // obj scope(범위) 생성
console.log(result(1, 2, 3)) // 106
다음은 window(global) 내에서 add 화살표 함수가 생성되는 것이라고 가정하에 어떤 결과가 나오는지 알아 봅시다.
// 간단한 Object 생성
var obj = {
num: 100
}
// window 객체에도 num을 생성 합니다.
// 이것이 어떻게 사용되는지를 확인하기 위해서 생성
window.num = 2020;
// Arrow Function
var add = (a, b, c) => this.num + a + b + c;
// call
console.log(add.call(obj, 1, 2, 3)) // 2026
// apply
const arr = [1, 2, 3]
console.log(add.apply(obj, arr)) // 2026
// bind
const bound = add.bind(obj)
console.log(bound(1, 2, 3)) // 2026
이처럼 화살표 함수 사용의 최대 장점은
일반적으로 클로저 같은 기능, call, apply 또는 bind 기능이 꼭 필요한 DOM-level 메서드 ( setTimeout, setInterval, addEventListener)에 사용 해 적절한 scope(범위) 에서 함수가 실행 되도록 하기 위한 것입니다.