화살표 함수와 this
, super
, 그 외
화살표 함수
화살표 함수는 ES6 에서 새로 도입되었다.
쉼표 연산자 (Comma Operator) 를 사용하여 map 반환하기
handlers = handlers.map((handler) => (unregister(handler), register(handler)));
- 쉼표로 이어진 값을 평가하고 가장 마지막 값을 반환한다.
- map 의 본문을 간략화하고 싶을 때 사용할 수 있다.
- 이런식으로 사용하는 것을 추천한다기보다는 간혹 이런식으로 작성된 코드를 볼 수 있어서 알아두면 좋다.
화살표 함수의 this
this
를 갖지 않는다. 대신 바로 바깥 스코프의 this
를 가진다.
화살표 함수는 new
키워드로 생성할 수 없지만, 가볍다.
- 화살표 함수는 일반
function
선언처럼new
키워드로 오브젝트화 할 수 없다. - 그래서 일반 함수보다 가벼워질 수 있다.
Function
이 갖는 다양한 프로토타입을 가지지 않을 수 있다.
function normalFunction() {}
const arrowFunction = () => {};
normalFunction.prototype; // {constructor: ƒ}
arrowFunction.prototype; // undefined
- 화살표 함수에는
.prototype
이 존재하지 않는다. - 그래서
constructor
가 없기 때문에new
로 생성할 수도 없다.
기본 값
function animate(type, duration) {
if (duration === undefined) {
duration = 300;
}
}
function animate(type, duration = 300) {}
위의 두 코드는 동일하다.
기본 값은 표현'식'이다.
function animate(type, duration = getDuration(type)) {}
- 위와 같은 표현이 가능하다.
- 단
type
이 뒤에 나오면ReferenceError: type is not defined
를 던진다. - 외부 범위에 있는 것은 기본 값으로 참조 가능하다.
- 함수 본문에 있는 것은 당연히 참조 불가능하다.
function e(
callback = (o) => {
with (o) {
return answer;
}
}
) {
console.log(callback({ answer: 42 }));
}
- 기본 값에 함수를 넣는 것도 가능하다.
내부 동작
function animate(duration = 1000, type = "default") {}
위와 같은 기본 값이 있다면, 실제 동작은 아래와 매우 흡사하다.
function animate() {
let duration = arguments[0];
if (duration === undefined) {
duration = 1000;
}
let type = arguments[1];
if (type === undefined) {
type = "default";
}
const animationImplementation = () => {
// ...
};
}
- 파라미터와 함수 내용은 범위가 구분된다.
'단순하지 않은' 매개변수 목록
- 파라미터에 기본 값이 있는 경우, 이를 '단순하지 않은' 매개변수 목록 이라고 한다.
- 단순하지 않은 매개변수 목록이 있는 함수는
"use strict"
지시문을 가질 수 없다. - 단순하지 않은 매개변수 목록이 이미 느슨한 모드 코드로 정의되었는데, 지시문이 함수 내에서 엄격 모드를 활성화하기 때문에 에러가 난다.
- 단순하지 않은 매개변수 목록이 있는 함수는
"use strict" 를 사용할 수 없는 케이스 - MDN
function example(answer = 42) {
"use strict" // Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
}
기본 값이 있는 파라미터를 포함하여 이후의 파라미터는 함수의 인자 수에 포함되지 않는다.
function fn1(one, two, three) {}
console.log(fn1.length); // 3
function fn2(one = 1, two, three) {}
console.log(fn2.length); // 0
함수.length
를 찍으면 파라미터의 개수가 나온다.- 파라미터에 기본 값이 있으면, 그 파라미터를 포함하여 전부 파라미터의 개수에 포함되지 않는다.
나머지 매개변수
나머지 매개변수 이전의 코드
function extend(target) {
for (let n = 1; n < arguments.length; n++) {
const source = arguments[n];
// source 에는 객체 형태만 와야 한다.
Object.keys(source).forEach((key) => {
target[key] = source[key];
});
}
return target;
}
console.log(extend({}, { a: 1 }, { b: 2 })); // {a:1, b:2}
- 느슨한 모드에서는 양식 매개변수에 연결되는 방식으로 성능 문제가 발생
- 엄격 모드를 통해 해결 가능
- 인수를 전부 이용해야 해서 확장이 불편하다.
arguments
는 배열 프로토타입을 물려받지 않아서, 배열 기능 이용이 불가능하다.- 화살표 함수에는
arguments
기능이 없다.
나머지 매개변수 이후의 코드
function extend(target, ...sources) {
sources.forEach((source) => {
Object.keys(source).forEach((key) => {
target[key] = source[key];
});
});
return target;
}
console.log(extend({}, { a: 1 }, { b: 2 })); // {a:1, b:2}
target
뒤에 오는 모든 매개변수를나머지 매개변수
로 묶었다.- 자연적으로
Array.prototype
을 상속하게 되어Array.prototype
의 메서드를 사용할 수 있게 된다.forEach
를 사용했다.
- 이제
arguments
객체가 필요 없으니extend
자체를 화살표 함수로 구성할 수 있다는 점도 주목할만 하다.
나머지 매개변수에 빈 값이 온다면?
function rest(...args) {
console.log(args);
}
console.log(rest(1, 2, 3)); // [1, 2, 3]
console.log(rest()); // []
- 빈 값이 오면, 빈 배열(
[]
)을 제공하여 일관성 있는 반환값을 제공한다.
후행 쉼표와 함수 호출
function additionalComma(
a,
b,
c,
) {
console.log(a, b, c);
}
additionalComma(
"a",
"b",
"c",
); // a b c
- 이젠 함수 파라미터나, 함수 인자에 추가적인 쉼표가 들어가도 에러가 아니다.
- 매개변수나 인수를 엔터로 구분할 때 유용하다.
함수의 name
속성
name
속성을 통해 함수의 이름을 알 수 있다.- 이름을 알게 되면, 오류에 대한 호출 스택에서 오류가 난 곳을 정확히 파악하는데 도움이 된다.
function foo() {}
console.log(foo.name); // foo
const f = function foo() {};
console.log(f.name); // foo
let foo = function () {};
console.log(foo.name); // foo
// 나중에 함수를 할당
let bar;
bar = function () {};
console.log(bar.name); // bar
// 화살표 함수
let b = () => {};
console.log(b.name); // b
// 오브젝트 속성 함수
const obj = {
foo: function () {},
};
console.log(obj.foo.name); // foo
// 함수를 기본 매개변수 값으로 사용
(function (callback = function () {}) {
console.log(callback.name); // "callback"
})();
name
이 없는 특수한 경우
const obj = {};
obj.foo = function () {};
console.log(obj.foo.name); // "" - 이름이 없음
- 기존 객체의 객체 속성에 할당하면, 이름이 없다.
- TC39 는 이를 너무 많은 정보 유출로 간주했다. (?)
cache[getUserSecret(user)] = function () {};
- 핸들러 함수를 타사 코드에 제공했을 때
getUserSecret(user)
의 값이 제공될 수 있어서 일부러 생략했다.
이유에 대해서는 사실 잘 이해가 안 된다.
블록 내 함수 선언
- 블록 내 함수 선언은 ES2015 부터 공식적으로 사양의 일부가 되었다.
- 이전까지는 사용은 가능했으나, 공식 사양은 아니었다.
블록 내 함수 선언의 문제
간단한 함수 선언은 문제 없었으나, 다음을 어떻게 처리할지에 대해서 여러 논의가 있었다.
function branching(num) {
console.log(num);
if (num < 0.5) {
function doSomething() {
console.log("true");
}
} else {
function doSomething() {
console.log("false");
}
}
doSomething();
}
위의 코드를 처리하기 위해 아래와 같은 방법들이 나왔다.
function branching(num) {
console.log(num);
var doSomething;
if (num < 0.5) {
doSomething = function doSomething() {
console.log("true");
};
} else {
doSomething = function doSomething() {
console.log("false");
};
}
doSomething();
}
- 실제로 함수 표현식인 것 처럼 만든다.
function branching(num) {
function doSomething() {
console.log("true");
}
function doSomething() {
console.log("false");
}
console.log(num);
var doSomething;
if (num < 0.5) {
} else {
}
doSomething();
}
- 호이스트 선언처럼 처리한다.
- 후에 선언된 함수가 이겨 항상
"false"
만이 출력된다.
결국 위의 방식으로 코딩하면, 브라우저마다 동작이 다르다. 이 외에도 가장 확실하게 구문 오류로 처리하는 옵션도 있었다.
블록 내 함수 선언: 표준 의미론
가장 간단한 처리는 표준 시맨틱으로, 항상 엄격 모드로 적용되는 것이다.
"use strict";
function branching(num) {
console.log(num);
if (num < 0.5) {
console.log("true branch, typeof doSomething = " + typeof doSomething);
function doSomething() {
console.log("true");
}
} else {
console.log("false branch, typeof doSomething = " + typeof doSomething);
function doSomething() {
console.log("false");
}
}
doSomething();
}
branching(Math.random());
/*
true branch, typeof doSomething = function
Uncaught ReferenceError: doSomething is not defined
at branching (<anonymous>:17:3)
at <anonymous>:20:1
*/
- 에러 메세지를 보면, 블록 내에서
function
인 것을 인지한 것을 보아 블록 내에서 호이스팅은 되었다. - 그러나 함수 내용 마지막에
doSomething()
호출이ReferenceError
를 내며 실패한 것으로 보아, 외부에서doSomething
함수를 참조할 수는 없는 것으로 보인다.
위의 코드는 엄격모드에서 아래와 같이 변환된다.
"use strict";
function branching(num) {
console.log(num);
if (num < 0.5) {
console.log("true branch, typeof doSomething = " + typeof doSomething);
let doSomething = function doSomething() {
console.log("true");
};
} else {
console.log("false branch, typeof doSomething = " + typeof doSomething);
let doSomething = function doSomething() {
console.log("false");
};
}
doSomething();
}
branching(Math.random());
위의 코드는 당연히 에러가 난다. let
은 블록스코프인데, doSomething()
을 호출하는 곳은 스코프 밖이다.
아래와 같이 변환하면 실행이 가능하다.
"use strict";
function branching(num) {
let f;
console.log(num);
if (num < 0.5) {
console.log("true branch, typeof doSomething = " + typeof doSomething);
f = doSomething;
function doSomething() {
console.log("true");
}
} else {
console.log("false branch, typeof doSomething = " + typeof doSomething);
f = doSomething;
function doSomething() {
console.log("false");
}
}
f();
}
branching(Math.random());
블록의 함수 선언: 레거시 웹 의미 체계
function branching(num) {
console.log(`num = ${num}, typeof doSomething = ${typeof doSomething}`);
if (num < 0.5) {
console.log("true branch, typeof doSomething = " + typeof doSomething);
function doSomething() {
console.log("true");
}
console.log("end of true block");
} else {
console.log("false branch, typeof doSomething = " + typeof doSomething);
function doSomething() {
console.log("false");
}
console.log("end of true block");
}
doSomething();
}
branching(Math.random());
이는 레거시 웹 의미 체계에서는 아래와 같이 번역된다.
- 함수는 단일 블록 내에서만 선언되고 참조된다.
- 함수는 단일 블록 내에서 선언되고 사용 가능하지만, 동일한 블록 내에 포함되지 않은 내부 함수 정의에 의해 참조된다.
- 함수는 단일 블록 내에서 선언되고 사용 가능하지만, 후속 블록 내에서 참조된다.
function branching(num) {
var varDoSomething;
console.log(num);
if (num < 0.5) {
let letDoSomething = function doSomething() {
console.log("true");
};
console.log("true branch, typeof doSomething = " + typeof doSomething);
varDoSomething = letDoSomething; // 선언이 있던 곳
console.log("end of true block");
} else {
let letDoSomething = function doSomething() {
console.log("false");
};
console.log("false branch, typeof doSomething = " + typeof doSomething);
varDoSomething = letDoSomething; // 선언이 있던 곳
console.log("end of true block");
}
doSomething();
}
branching(Math.random());
레거시 의미 체계에서는 블록 내부에서 호이스팅과 같은 현상이 일어나고, 바깥 스코프에서도 사용 가능하도록 조정해준다.
이러한 레거시 웹 의미 체계에 의존하는 코드를 더이상 작성하지 말자.
액션 플랜
화살표 함수를 사용하는 경우
- 콜백 함수에서 호출 컨텍스트
this
에 접근하고 싶을 때 사용하자. Function.prototype.bind
대신 사용하자.var self = this
와 같은 구시대의 유물이 있을 때 리팩토링하자.
this
혹은 인수를 사용하지 않을 때는 콜백에 화살표 함수를 사용하자.
someArray.sort((a, b) => a - b);
화살표 함수가 더 간결하다.
함수 호이스팅이 필요 없다면, 화살표 함수를 사용하자.
가끔은 전통적 function
을 이용하여 함수 호이스팅을 사용해야 할 때도 있다.
호출자가 이 값을 제어해야 할 때는 화살표 함수를 사용하지 마라.
때로는 콜백함수에서 지원하는 this
가 유용하게 쓰일 때도 있으니, function
도 두번째 선택지에 두자.
addEventListener
로 연결한 DOM 이벤트에 응답할 때this
를 이용한 객체간 공유 메서드를 작성할 때
기본값을 제공하는 코드 대신 기본 매개변수를 사용하자.
function fn(param1) {
if (param1 === undefined) {
param1 = 5000;
}
}
function fn(param1 = 5000) {}
코드가 간결하고 의도가 명확해진다.
인수 키워드 (arguments
) 대신 나머지 매개변수를 사용하자.
function fn() {
// arguments 를 이용한 코드
}
function fn(...args) {
// args 를 이용한 코드
}
나머지 인수를 사용한다는 의도를 명확히 드러낼 수 있다.
후행 쉼표를 고려하자.
function additionalComma(
a,
b,
c,
) {
console.log(a, b, c);
}
additionalComma(
"a",
"b",
"c",
); // a b c
때때로 가독성이 좋아진다.
'자바스크립트 > 웹개발자를 위한 자바스크립트의 모든 것' 카테고리의 다른 글
웹 개발자를 위한 자바스크립트의 모든 것 6장 이터러블, 이터레이터, 제너레이터 (0) | 2023.01.16 |
---|---|
웹 개발자를 위한 자바스크립트의 모든 것 5장 새로운 객체 기능 (0) | 2023.01.02 |
웹 개발자를 위한 자바스크립트의 모든 것 4장 클래스 (0) | 2022.12.29 |
웹 개발자를 위한 자바스크립트의 모든 것 2장 let, const 정리 (0) | 2022.12.18 |
웹 개발자를 위한 자바스크립트의 모든 것 1장 ES2015 - ES2020 정리 (0) | 2022.12.17 |