화살표 함수 표현식은 일반 함수 표현식의 경제적인 대안이다. 하지만 모든 상황에서 사용하기에는 제약이 있다.
화살표 함수와 일반 함수 사이에는 다음과 같은 제약사항 들이 있다.
- 화살표 함수는 스스로 바인딩된 this,. arguments, super가 없어서, 메소드로 사용하면 안된다.
- 화살표 함수는 new.target 키워드에 접근할 수 없다.
- 화살표 함수는 call, apply, bind 메소드들에 적합하지 않다. 그 메소드들은 일반적으로 범위 설정에 의존한다.
- 화살표 함수는 생성자로 사용할 수 없다.
- 화살표 함수 본문에 yield를 사용할 수 없다.
일반 함수와 화살표 함수 비교
"일반 익명함수"를 "화살표 함수"로 한단계씩 분해해보자.
Note: 각각의 단계는 유효한 "화살표 함수" 이다.
// 일반 익명 함수
(function (a) {
return a + 100;
})
// 화살표 함수로 쪼개기
// 1. "function" 을 없애고, 아규먼트와 body 중괄호 사이에 화살표를 둔다.
(a) => {
return a + 100;
}
// 2. 바디 중괄호와 "return"을 없앤다. 바디 중괄호가 없으면 암묵적으로 return 한다.
(a) => a + 100;
// 3. 아규먼트의 소괄호를 없앤다.
a => a + 100;
{ 중괄호 } 와 ( 소괄호 ) 그리고 "return"은 상황에 따라 요구된다.
예를 들어, 만약 여러개의 아규먼트가 있거나 아규먼트가 없는 경우, 아규먼트를 소괄호로 감싸야 한다.
// 일반 익명 함수
(function (a, b) {
return a + b + 100;
})
// 화살표 함수
(a, b) => a + b + 100;
// 일반 익명 함수 (아규먼트 없음)
const a = 4;
const b = 2;
(function() {
return a + b + 100;
})
// 화살표 함수 (아규먼트 없음)
const a = 4;
const b = 2;
() => a + b + 100;
마찬가지로, 만약 바디에서 추가적인 작업을 해야 한다면, 중괄호와 return을 추가해야 한다. (화살표 함수는 언제 어떻게 return하기를 원하는지 유추하지 못한다):
// 일반 익명 함수
(function (a, b) {
const chuck = 42;
return a + b + chuck;
})
// 화살표 함수
(a, b) => {
const chuck = 42;
return a + b + chuck;
}
마지막으로, 이름지어진 함수를 익명함수에 적용할 때는 변수선언처럼 적용한다:
// 일반 함수
function bob(a) {
return a + 100;
}
// 화살표 함수
const bob = (a) => a + 100;
문법
기초 문법
하나의 파라미터일 때, 간단한(한줄짜리) 표현식에서 return은 필요하지 않다 (암묵적으로 리턴):
param => expression
(param) => expression
여러개의 파라미터는 소괄호가 필요하다. 한줄짜리 표현식에서 return은 필요하지 않다:
(param1, paramM) => expression
여러 줄의 구문에서는 중괄호와 리턴이 필요하다:
// 하나의 파라미터라면 소괄호는 선택사항이다.
(pram) => {
const a = 1;
return a + param;
}
여러개의 파라미터들은 소괄호가 필요하다. 여러줄의 구문은 중괄호와 리턴을 써야 한다:
(param1, paramN) => {
const a = 1;
return a + param1 + paramN;
}
심화 문법
객체를 리턴하기 위해서는 표현식 주위에 소괄호를 써야한다:
(param) => ({ foo: "a" }) // { foo: "a" } 객체 리턴
rest 파라미터를 지원한다. rest 파라미터를 쓸 때는 항상 소괄호를 써야한다:
(a, b, ...r) => expression
default 파라미터를 지원한다. 마찬가지로 항상 소괄호를 써야한다:
(a=400, b=20, c) => expression
분할할당을 지원한다. 항상 소괄호를 써야 한다:
([a, b] = [10, 20]) => a + b; // result is 30
({a, b} = { a: 10, b: 20 }) => a + b; // result is 30
설명
화살표 함수를 메소드로 사용하면...
앞서 명시했듯이, 화살표 함수 표현식은 메소드 함수로는 적합하지 않다. 화살표 함수를 메소드로 사용하면 어떤일이 일어나는지 알아보자. (여기서 메소드는 객체를 통해서 사용하는 함수이다.)
const obj = { // 새로운 범위를 만들지 않는다.
i : 10,
b: () => console.log(this.i, this),
c() {
console.log(this.i, this);
}
}
obj.b(); // undefined, Window { /* ... */ }
obj.c(); // 10, {i:10, b:f, c:f}
화살표 함수는 스스로 this를 가질 수 없다.
다음은 Object.defineProperty()에 관한 예이다.
const obj = {
a: 10,
};
Object.defineProperty(obj, 'b', {
get: () => {
console.log(this.a, typeof this.a, this); // undefined 'undefined', Window{...}
return this.a + 10; // this가 전역객체인 window를 나타낸다.
// 그래서 'this.a'는 'undefined'를 리턴한다.
}
});
클래스에서 화살표함수를 사용할 때는, 클래스 본문에 this 컨텍스트가 있다. 클래스 필드로서의 화살표 함수는 클래스의 this 컨텍스트가 닫힐 때 동작한다. 화살표 함수의 본문에 있는 this는 정확하게 그 인스턴스를 짚는다. ( 정적필드라면 클래스를 짚는다.) 이렇게 정확하게 짚는 것은, this컨텍스트가 닫힐 때 화살표함수가 동작하면서 클로져가 일어나기 때문이다. 함수가 스스로 this를 바인딩하기 때문에 짚는 것이 아니다. 클로져에 기반하기 때문에, 실행을 기반으로 this의 값이 변하지는 않는다.
class C {
a = 1;
autoBoundMethod = () => {
console.log(this.a);
}
traditionalMethod () {
console.log(this.a);
}
}
const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod, traditionalMethod } = c;
autoBoundMehtod(); // 1
traditionalMethod(); // Uncaught TypeError: Cannot read properties of undefined (reading 'a')
// 분할할당을 했을 때, 화살표함수는 클로져로 인해 a를 찾지만,
// 일반적인 메소드는 this가 Windows 라서 a를 찾지 못한다.
화살표 함수 프로퍼티들은 종종 "자동 바운드 메소드" 라고 한다. 왜냐하면 일반 메소드에 bind를 한것과 동일하기 때문이다.
class C {
a = 1;
constructor() {
this.method = this.method.bind(this);
}
method() {
console.log(this.a);
}
}
const c = new C();
const { method } = c;
method() // 1
Note: 클래스 필드들은 프로토타입이 아니라 인스턴스에 정의된다. 그래서 모든 인스턴스 생성은 새로운 함수 참조와 새로운 클로져를 할당한다. 일반 함수 언바운드 보다 잠재적으로 더 많은 메모리를 사용한다.
call, apply, bind 메소드
화살표함수는 call, apply 와 bind 메소드에 적합하지 않다. 이 메소드들은 다른 범위에 있는 메소드를 실행하도록 디자인 되었다. 화살표 함수는 화살표 함수가 정의된 곳 외곽의 this를 사용하기 때문에 적합하지 않다.
예를들어, call, apply 그리고 bind는 일반적인 함수들 에서 사용하도록 만들었다. 각각의 메소드에 바인딩된 this를 사용하기 때문이다.
// this.num을 사용할 수 있는 객체
const obj = {
num: 100,
};
// window에 num을 선언해도 사용되지 않는다.
window.num = 2020
// this.num을 실행하기 위한 간단한 함수
const add = function (a, b, c) {
return this.num + a + b + c;
};
// call
const result = add.call(obj, 1, 2, 3); // "obj"의 범위에서 add를 생성해서 호출한다.
console.log(result); // result 106
// apply
const arr = [1, 2, 3];
const result = add.apply(obj, arr); // "obj"의 범위에서 arr을 적용한다.
console.log(result); // result 106
// bind
const result = add.bind(obj); // "obj"의 범위에서 생성한다.
console.log(result(1,2,3)); // result 106
화살표 함수에서는, add 함수가 본질적으로 선언된 곳이 window (전역) 범위이기 때문에, this는 window를 가르킨다.
// this.num을 가지고 있는 객체
const obj = {
num: 100,
};
// window에 num을 선언
window.num = 2020;
// 화살표 함수
const add = (a, b, c) => this.num + a + b + c;
// call
console.log(add.call(obj, 1, 2, 3)); // result 2026
// apply
const arr = [1, 2, 3];
console.log(add.apply(obj, arr)); // result 2026
// bind
const bound = add.bind(obj);
console.log(bound(1, 2, 3)); // result 2026
화살표 함수를 사용하는 가장 좋은 예는 아마도 setTimeout() 이나 EventTarget.addEventListener() 같이, 함수가 적절한 범위에서 실행되도록 하기 위해 일종의 closure, call, apply 또는 bind가 필요한 것들일 것이다.
일반 함수 예제
const obj = {
count: 10,
doSomethingLater() {
setTimeout(function () { // window 범위에서 실행되는 함수
this.count++;
console.log(this.count);
}, 300);
},
};
obj.doSomethingLater(); // NaN을 프린트할 것이다.
// 'count'프로퍼티가 window에 없기 때문.
화살표 함수 예제
const obj = {
count: 10,
doSomethignLater() {
// 일반함수의 this에 obj의 컨텍스트가 바인딩이 됐다.
setTimeout(() => {
// 화살표 함수는 스스로의 this에 대한 바인딩은 없기 떄문에,
// setTimeout (함수호출로서) 스스로 바인딩을 만들지 않는다.
// 일반함수의 "obj" 컨텍스트가 사용된다.
this.count++;
console.log(this.count);
}, 300);
},
};
obj.doSomethingLater();
화살표함수는 아규먼트를 바인딩하지 않는다.
화살표함수는 스스로 arguments 객체를 가지지 않는다. 그래서, arguments는 외곽의 범위의 arguments를 참조한다.
const arugments = [1, 2, 3];
cosnt arr = () => arguments[0];
arr(); // 1
function foo(n) {
const f = () => arguments[0] + n; // foo의 arguments가 암묵적으로 바인딩 된다.
// arguments[0] 은 n 이다.
return f();
}
foo(3); // 3 + 3 = 6
일반적인 경우, rest 파라미터는 arguments 객체를 사용하는 좋은 대안이다.
function foo(n) {
const f = (...args) => args[0] + n;
return f(10);
}
foo(1); // 11
새로운 오퍼레이터의 사용
화살표 함수는 생성자로 사용할수 없고, new 를 사용하면 에러를 던진다.
const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor
프로토타입 프로퍼티의 사용
화살표함수는 프로토타입 프로퍼티를 가지지 않는다.
const Foo = () => {};
console.log(Foo.prototype); // undefined
yield 키워드의 사용
yield 키워드는 화살표 함수의 본문에서는 사용하지 않는다 (단, 함수가 중첩되어 사용하는 경우 제외). 결과적으로, 화살표함수는 generators로서 사용할 수 없다.
화살표 함수 본문
화살표 함수는 간결한 본문과 평범한 블럭 본문 중 하나를 사용할 수 있다.
간결한 본문에서는, 암묵적으로 값을 리턴하는 표현식만 필요하다. 블럭 본문에서는, 명시적으로 return 절을 사용해야 한다.
const func = (x) => x * x;
// 간결한 본문 문법, 암묵적으로 리턴한다.
const func2 = (x, y) => { return x + y; };
// 블럭 본문에서는, 명시적인 리턴이 필요하다.
객체 리터럴 리턴하기
간결한 본문 문법에서는 (params) => {object:literal}이 기대한 대로 동작하지 않을 것이다.
const func = () => { foo: 1 };
// func()를 호출하면 undefined를 리턴한다!
const func2 = () => {foo: function() {} };
// SyntaxError: function statement requires a name
const func3 = () => { foo() {} };
// SyntaxError: Unexpected token '{'
중괄호 안에있는 코드가 객체가 아니라 본문으로 인식되기 때문이다.(즉, foo는 객체 리터럴의 키값이 아니라, 라벨로 다뤄진다)
객체 리터럴을 소괄호로 묶어야만 한다:
const func = () => ({ foo: 1 });
라인 브레이크(줄바꿈)
화살표 함수는 파라미터와 화살표 사이에 줄바꿈을 할 수 ㅇ벗다.
const func = (a, b, c)
=> 1;
// SyntaxError: Unexpected token '=>'
하지만, 화살표 다음에 줄바꿈을 해서 수정하거나, 소괄호/ 중괄호로 코드를 예쁘고 가볍게 유지할 수 있다. 또한 아규먼트들 사이에 줄바꿈을 할 수 있다.
const func = (a, b, c) =>
1;
const func2 = (a, b, c) => (
1
);
const func3 = (a, b, c) => {
return 1;
};
const func4 = (
a,
b,
c
) => 1;
// no SyntaxError thrown
파싱 순서
비록 화살표함수에 있는 화살표가 오퍼레이터는 아니지만, 화살표 함수는 일반적인 함수와 비교해서 우선순위에서 다르게 상호작용하는 특별한 파싱 규칙이 있다.
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok
예제
기본 사용법
// 빈 화살표 함수는 undefined를 리턴한다
const empty = () => {};
(() => 'foobar')();
// 'foobar'를 리턴한다
// (함수 표현식을 즉시 호출한다)
const simple = (a) => a > 15 ? 15 : a;
simple(16); // 15
simple(10); // 10
const max = (a, b) => a > b ? a : b;
// 배열 filter, map, 등을 쉽게 사용한다
const arr = [5, 6, 13, 0, 1, 18, 23];
const sum = arr.reduce((a, b) => a + b);
// 66
const even = arr.filter((v) => v % 2 === 0);
// [6, 0, 18]
const double = arr.map((v) => v * 2);
// [10, 12, 26, 0, 2, 36, 46]
// 프로미스 체인을 좀 더 간결하게 유지한다.
promise
.then((a) => {
// ...
})
.then((b) => {
// ...
});
// 파라미터가 없는 화살표 함수는 읽기에 더 수월해보인다.
setTimeout(() => {
console.log('I happen sooner');
setTimeout(() => {
console.log('I happen later');
}, 1);
}, 1);
'front > js' 카테고리의 다른 글
[js] 배열의 참조를 유지하면서 특정 값을 모두 제거하는 방법. (0) | 2022.08.09 |
---|---|
[js] Memory Management (0) | 2022.08.02 |
[javascript] falsy (0) | 2022.07.26 |
[에러]TypeScript language service died unexpectedly 5 times in the last 5 minutes (0) | 2022.07.23 |
자바스크립트 클로져(Closures ) (0) | 2022.06.30 |
댓글