17장 그 외

2023.06.16

17.1 BigInt

  • BigInt는 ES2020에 추가된 자바스크립트에서 큰 정수로 작업하기 위한 새로운 기본 타입이다.
  • BigInt는 사용 가능한 메모리 및/또는 자바스크립트 엔진 구현자가 부과하는 합리적인 제한에 의해서만 제한되는 모든 크기의 정수를 보유할 수 있다.
  • BigInt는 새로운 기본 타입이므로 typeof는 이를 “bigint”로 식별한다.
  • BigInt는 Math 메서드는 작동하지 않는다. 이러한 메서드는 일반 숫자 유형에 특화되었다.
  • Bigint를 사용하는 이유는 무엇일까? 두 가지 기본 사용 사례가 있다.
    • 이름이 말하듯이 Number 유형이 정확하게 표현할 수 있는 능력을 넘어서는 큰 정수(즉, 253보다 큰 정수)를 다루고 있다.
    • 금융 정보를 다루고 있다. 숫자 유형의 부동 소수점 부정확성(유명한 0.1 + 0.2 != 0.3문 제)으로 인해 금융 작업에 적합하지 않은 선택이 되지만, 순수한 자바스크립트 코드로 구동 되는 장바구니는 종종 이를 사용한다. 대신 BigInt를 사용할 수 있다. 통화의 가장 작은 단 위(또는 경우에 따라 더 작은 단위)를 사용하여 정수로 작업한다. 예를 들어, 미국에서는 $1 =100n을 사용할 수 있다. 즉, 달러가 아닌 센트 단위로 작동한다.

17.1.1 BigInt 생성하기

  • Bigint를 만드는 간단한 방법은 리터럴 표기법을 사용하는 것이다. 이는 정수에 대한 숫자의 리터 럴 표기법과 그 뒤에 문자 n이 있는 것과 (거의) 같다.
let i = 1234567890123456789012345678n;
console.log(i); // 1234567890123456789012345678n
  • 10진수, 16진수, 최신 8진수가 모두 지원된다.
// Decimal
console.log(10n); // 10n
// Hex
console.log(0x10n); // 16n
// Octal
console.log(0o10n); // 8n
  • 과학적 표기법(특히 1000의 경우 1e3과 같은 e 표기법)은 지원되지 않는다.
  • Bigint의 요점은 숫자 유형이 할 수 있는 것보다 훨씬 더 많은 수를 안정적으로 보유할 수 있다는 것이다.
  • BigInt 함수를 사용하여 BigInt를 생성하여 문자열이나 숫자를 전달할 수도 있다.
// Calling BigInt with a string:
let i = BigInt('1234567890123456789012345678');
console.log(i); // 1234567890123456789012345678n
// Calling BigInt with a number:
i = BigInt(9007199254740992);
console.log(i); // 9007199254740992n
  • 숫자 유형은 예의 대규모 숫자와 같은 크기의 숫자를 보유할 수 있지만 정밀도가 그렇게 높을 수는 없다. 숫자에서 해당 숫자를 얻으려고 하면 실제로 얻는 값은 610억 이상 오차가 발생한다.
// Don't do this
let i = BigInt(1234567890123456789012345678);
console.log(i); // 1234567890123456850245451776n ?!??!!
  • 그 이유는 숫자 리터럴 1234567890123456789012345678이 숫자를 정의하기 때문이다. Bigint 함수에 전달될 때쯤이면 이미 정밀도가 손실된다. 이 값은 이미 1,234,567,890,123,456,789,0167 대신 1,234,567,890,123,456,7845016이다. 이것이 Bigint가 있는 이유다.

17.1.2 명시적 또는 암시적 변환

  • Bigint와 Number는 암시적으로 다른 것으로 변환되지 않는다. 수학 연산자에 대한 피연산자로 혼합할 수 없다.
console.log(1n + 1);
// => TypeError: Cannot mix BigInt and other types, use explicit conversions
  • 이것은 주로 정밀도의 손실 때문이다. Number는 Bigint가 처리하는 큰 정수를 처리할 수 없고 Biglnt는 소수 값 Number가 처리할 수 없다.
  • BigInt 함수를 사용하여 Number에서 Bigint로 명시적으로 변환을 할 수 있다. Number에 소수 부분이 있으면 BigInt에서 오류가 발생한다.
console.log(BigInt(1.7));
// => RangeError: The number 1.7 cannot be converted to a BigInt
//    because it is not an integer
  • Number 함수를 사용하여 BigInt를 숫자로 변환한다. Number가 너무 커서 값을 정확하게 보유할 수 없는 경우 Number 타입과 마찬가지로 보유할 수 있는 가장 가까운 값이 선택되어 Number 타입이 자주 하는 것처럼 자동으로 정밀도가 손실된다.
  • 정밀도를 잃지 않는 변환을 원하면(대신 NaN을 던지거나 반환) 이에 대한 유틸리티 함수를 작성할 수 있다.
function toNumber(b) {
  if (typeof b !== 'bigint') {
    throw new TypeError('toNumber expects a BigInt');
    // (or return NaN, depending on your needs)
  }
  const n = Number(b);
  if (BigInt(n) !== b) {
    throw new RangeError(`Can't convert BigInt ${b}n to Number, loss of precision`);
    // (or return NaN, depending on your needs)
  }
  return n;
}
console.log(toNumber(1234567890123456789012345678n));
// => RangeError: Can't convert BigInt 1234567890123456789012345678n to Number,
//    loss of precision
  • 문자열과 달리 BigInt는 단항 빼기 또는 단항 더하기를 사용하여 숫자로 변환할 수 없다.
console.log(+'20'); // => 20
console.log(+20n); // => TypeError: Cannot convert a BigInt value to a number
  • BigInt는 암시적으로 문자열로 변환할 수 있다.
console.log('$' + 2n); // $2
  • BigInt는 toString과 toLocaleString도 지원한다.
  • BigInt는 암시적로 불로 변환될 수 있다.
    • 0n은 거짓이고 다른 모든 BigInt는 참이다.
  • BigInt에는 한 가지 종류의 0만 있다(Number와 같이 0과 음의 0이 아니라).
  • BigInt는 항상 유한하다(따라서 Bigint에 Infinity나 -Infinity가 없음).
  • BigInt는 항상 숫자 값을 갖는다(Bigint NaN이 없다).

17.1.3 성능

  • BigInt는 32비트 또는 64비트 정수 유형과 같이 고정된 크기가 아니기 때문에(대신 필요한 만큼 커질 수 있음) Bigint 성능은 Number 성능처럼 일정하지 않다.
  • 일반적으로 Bigint가 클수록 구현에 따라 달라질 수 있지만 작업 시간이 더 오래 걸린다.
  • 대부분의 새로운 기능과 마찬가지 로 Bigint의 성능은 자바스크립트 엔진 구현자가 실제 사용 정보를 수집하고 실제 이점을 제공할 엔트리에 대한 최적화를 목표로 함에 따라 의심할 여지없이 시간이 지남에 따라 향상될 것이다.

17.1.4 BigInt64Array와 BigUint64Array

  • 많은 응용 프로그램에는 Number가 보유할 수 있는 것보다 크지만 64비트 정수 타입으로 보유할 수 있는 정수가 필요하다.
  • Bigint 제안은 BigInt64Array와 BigUint64Array의 두 가지 추가 타입이 있는 배열을 제공한다. 이것은 자바스크립트 코드에서 가져올 때 값이 Bigint인 64비트 정수 배열이다.

17.1.5 유틸리티 함수

  • 유틸리티 함수 BigInt함수에는 부호 있는(asIntN) 또는 부호 없는(asUintN) 비트 수로 래핑된 Bigint 값을 가져 오는 방법을 제공하는 두 가지 메서드가 있다.
console.log(BigInt.asIntN(16, -20000n));
// => -20000n
console.log(BigInt.asUintN(16, 20000n));
// => 20000n
console.log(BigInt.asIntN(16, 100000n));
// => -31072n
console.log(BigInt.asUintN(16, 100000n));
// => 34464n
  • 첫 번째 피연산자는 비트 수이고 두 번째 피연산자는 잠(재적으로) 랩할 Bigint다.
  • 100,000은 16비트 정수에 들어갈 수 없으므로 값은 일반적인 2의 보수 방식으로 래핑된다.
  • Bigint를 부호 있는 값으로 래핑할 때 Bigint는 2의 보수 N 비트 유형에 기록되는 것처럼 처리된다. 부호 없는 값으로 래핑할 때 n이 비트 수인 2n의 나머지 연산과 같다.

17.2 새로운 정수 리터럴

  • Bigint 다음으로 ES2015는 두 가지 새로운 형태의 정수 리터럴(소수 양식이 없는 숫자 리터럴)인 2진수와 8진수를 추가했다.

17.2.1 이진 정수 리터럴

  • 이진 정수 리터럴은 이진법 (2진법, 즉 0과 1의 숫자)으로 작성된 숫자 리터럴이다.
  • 0b(0다음에 문자 b. 대소문자 구분 안함)로 시작하고 그 뒤에 숫자에 대한 이진수가 온다.
console.log(0b100); // 4 (10진법으로)
  • 16진법 리터럴과 마찬가지로 2진법 리터럴에는 소수점이 없다. 숫자 리터럴이 아닌 정수 리터럴이다. 정수를 쓰는 데만 사용할 수 있다. 즉, 16진수 리터럴과 마찬가지로 결과 숫자는 부동 소수점인 자바스크립트의 표준 숫자 타입이다.
  • b 다음에 선행 0을 원하는 수만큼 포함할 수 있다. 중요하지 않지만 코드를 정렬하거나 작업 중인 비트 필드의 너비를 강조하는 데 유용할 수 있다. 예를 들어, 8비트에 맞아야 하는 플래그를 정의 하는 경우(아마도 Uint8Array 엔트리에 들어갈 것) 다음과 같이 할 수 있다.
const bitFlags = {
  someThing: 0b00000001,
  someThingElse: 0b00000010,
  anotherThing: 0b00000100,
  anotherThingElse: 0b00001000,
};

// 0b 뒤에 오는 추가 0은 완전히 선택 사항이다.
const bitFlags2 = {
  someThing: 0b1,
  someThingElse: 0b10,
  anotherThing: 0b100,
  anotherThingElse: 0b1000,
};

17.2.2 8진수 정수 리터럴, 2번째 버전

  • ES2015는 새로운 8진법 정수 리터럴 양식을 추가했다. 접두사 0o(0 다음에 문자 o, 대소문자 구분 안 함)와 8진수(0에서 7까지)를 사용한다.
console.log(0o10); // 8 (10진법으로)
  • 16진법과 2진법 리터럴과 마찬가지로 정수 리터럴이지만 표준 부동 소수점 숫자를 정의한다.
  • 이전에는 선행 0과 8진수 숫자가 8진수로 정의된 숫자만 사용했다. 예를 들어, 06은 숫자 6을 정의하고 011은 숫자를 정의한다. 이 양식은 10진수와 너무 쉽게 혼동되기 때문에 문제가 있었고 숫자에 8 또는 9를 포함하면 자바스크립트 엔진이 숫자를 10진수로 구문 분석하여 011과 09가 모두 숫자 9를 정의하는 혼란스러운 상황으로 이어지기 때문이다.
// 느슨한 모드에서만
console.log(011 === 09); // true
  • 얼마나 혼란스러운가! 3판 사양(1999)에서 이 레거시 8진수 양식은 언어의 일부로 제거되었지만 구현에서 지원 여부를 선택할 수 있는 "호환성" 절에 남겨졌다.
  • ES5(2009)는 더 나아가 엄격 모드에서 자바스크립트 구현이 더 이상 레거시 8진수 리터럴을 지원하도록 허용되지 않고 "호환성” 정보를 브라우저 전용 부속서 B(Annex B)로 옮겼다고 말했다.
  • ES2015 09와 같은 8진수와 같은 10진수 리터럴을 더 허용하지 않는다.
  • 레거시 양식을 허용하지 않는 것은 엄격 모드를 사용하는 많은 이유 중 하나다. 011과 09는 모두 엄격 모드의 구문 오류이므로 혼동을 방지한다.
  • 이제 8진수를 작성하려면 새로운 0011 양식을 사용하자. 10진수를 쓰려면 불필요한 선행 0을 생략하자(예: 숫자에 09가 아닌 9를 사용).

17.3 새로운 수학 메서드

  • ES2015는 Math 객체에 모든 범위의 새로운 기능을 추가했다.
    • 다양한 응용 프로그램, 특히 그래픽, 기하학 등에서 유용한 일반 수학 함수
    • DSP(디지털 신호 처리) 및 다른 언어에서 자바스크립트로 컴파일된 코드와 같은 저수준 코드를 지원하는 함수

17.3.1 일반 수학 함수

  • ES2015는 주로 삼각 및 로그 함수와 같은 일반 수학 함수의 호스트를 추가했다.
  • 이러한 기능은 그래픽 처리, 3D 지오메트리 등에 유용하다.
| 함수           | 기능                                                                |
| -------------- | ------------------------------------------------------------------- |
| Math.acosh(x)  | X의 쌍곡선 코사인                                                   |
| Math.sinh(x)   | X의 역 쌍곡선 사인                                                  |
| Math.atanh(x)  | X의 역 쌍곡선 탄젠트                                                |
| Math.cbrt(x)   | x의 세제곱근                                                        |
| Math.cosh(x)   | x의 쌍곡선 코사인                                                   |
| Math.expm1(x)  | x의 지수함수에서 폐기(ex의 거듭제곱으로, 여기서 e는 자연 로그의 일) |
| Math.hypot(v1, | vl, ...) 인수의 제곱합의 제곱근                                     |
| Math.log10(x)  | x의 말이 10인 로그                                                  |
| Math.log1p(x)  | 1+x의 자연로그                                                      |
| Math.log2(x)   | x의 밑이 2인 로그                                                   |
| Math.sinh(x)   | x의 쌍곡선 사인                                                     |
| Math.tanh(x)   | x의 쌍곡선 탄젠트
  • Math.expm1(x)은 논리적으로 Math.exp(x) - 1과 같고 Math.log1p(x)는 논리적으로 Math.log(x+ 1)와 동일하기 때문에 Math.expm1과 Math.log1p는 처음에는 이상하게 보일 수 있다. 그러나 두 경우 모두 x가 0에 가까울 때 자바스크립트의 숫자 유형 제한으로 인해 expm1과 log1p가 exp 또는 log를 사용하는 동등한 코드보다 더 정확한 결과를 제공할 수 있다.
  • 구현은 자바스크립트의 숫자 타입이 지원하는 것보다 더 높은 정밀도를 사용하여 Math.expm1(x) 계산을 수행한 다음 Math.exp(x)를 수행하여 결과를 변환하는 대신 최종 결과를 자바스크립트 숫자로 변환(일부 정밀도 손실)할 수 있다.

17.3.2 저수준 수학 지원 함수

  • 지난 몇 년 동안 C, C++와 같은 다른 언어의 코드를 교차 컴파일하기 위한 대상으로 자바스크립트를 사용하는 데 많은 관심과 노력이 있었다.
  • 일반적으로 이 작업을 수행하는 도구는 자바스크립트의 고도로 최적화 가능한 하위 집합인 asm.js로 컴파일된다(또한 일반적으로 자바스크립트 대신 또는 추가로 웹어셈블리를 출력할 수 있는 기능이 있다).
  • 예를 들어 이를 수행하는 두 개의 프 로젝트는 Emscripten (LLVM 컴파일러의 백엔드)과 Cheerp (이전의 Duetto)이다
  • 저수준 작업은 종종 압축이나 디지털 신호 처리에도 유용하다.
  • ES2015는 이러한 도구를 지원하기 위해 몇 가지 기능을 추가했다.
    • Math.clz32(x)
    • Math.fround(x)
    • Math.imul(x, y)
    • Math.sign(x)
    • Math.trunc(x)

17.4 지수 연산자(**)

  • 지수 연산자(**)는 Math.pow 함수와 동일한 연산자다.
  • 이 연산자는 숫자를 다른 숫자의 거듭제곱으로 만든다
    • 사실. Math.pow의 정의는 **를 사용한 결과를 반환한다고 간단히 말하도록 업데이트 되었다.
console.log(2 ** 8); // 256
  • 주의해야 할 문제가 있다. 기본(x) 앞에 단항 연산자와 함께 xy를 사용하는 경우(예: -2**2)
  • 광범위한 의논과 토론 끝에 TC39 -2**2를 구문 오류로 만들었으므로 피하기 위해 대신 (-2)**2 또는 -(2**2)를 작성해야 한다.

17.5 Date.prototype.toString 변경

  • ES2018에서는 Date.prototype.toString이 처음으로 표준화되었다.
  • ES2017까지 반환된 문자열은 “... 사람이 읽을 수 있는 편리한 양식을 사용하여 현재 시간대의 날짜 및 시간으로 (날짜)를 나타내는 구현 중속 문자열 값"이었다. 그러나 모든 중요한 자바스크립트 엔진은 결국 서로 일관성 이 있었기 때문에 TC39는 일관성을 문서화하기로 결정했다.
  • 사양에 따라 이제 안정적이다.
    • 영어로 된 세 글자 요일 약어(예: "Fri")
    • 영어로 된 세 글자 월 약어(예: "Jan")
    • 요일. 필요한 경우 0으로 채워짐(예: "05)
    • 연도(예: "2019")
    • 24시간 양식의 시간(예: "19:27:11)
    • 시간대(GMT 다음에 +/- 및 오프셋 양식)
    • 선택적으로, 시간대 이름을 제공하는 괄호 안의 "구현 정의" 문자열(예: "태평양 표준시”)
  • 각각은 하나의 공백으로 이전 엔트리와 구분된다. 예를 들어 다음과 같다.
console.log(new Date(2018, 11, 25, 8, 30, 15).toString());
// => Tue Dec 25 2018 08:30:15 GMT-0800 (Pacific Standard Time)
// or
// => Tue Dec 25 2018 08:30:15 GMT-0800

17.6 Function.prototype.toString 변경

  • 최근 자바스크립트 사양은 Function.prototype.toString을 표준화했으며 ES2019는 가능하면 함수를 생성한 실제 소스 텍스트를 사용하여 반환되도록 하는 작업을 계속하고 있다.
  • 자바스크립트 엔진 또는 환경에서 제공하는 바인딩된 함수와 기타 함수는 다음 "네이티브 함수" 양식으로 함수 정의를 반환한다.
function name(parameters) { [native code] }
  • 자스크립트 소스 코드에 의해 직접 정의된 함수는 이를 정의한 실제 소스 텍스트 또는 앞의 예제와 같이 "네이티브 함수" 양식을 반환한다. 이것은 자바스크립트 엔진이 특정 기준과 일치하는 “... 구현 정의 문자열... "을 제공할 것이라고 말한 ES2018의 변경 사항이다.

17.7 숫자 추가

  • ES2015 Number 생성자에 몇 가지 새로운 속성과 메서드를 추가했다.

17.7.1 안전한 정수

  • 자바스크립트 숫자 타입이 완벽하게 정확하지 않다는 것을 알고 있을 것이다. 64비트만 사용하면서 분수 값을 포함한 방대한 범위의 값을 처리할 수 없음에도 여전히 처리하려고 한다. 종종 정확한 숫자 대신 숫자 타입이 숫자와 매우 가까운 근사값을 보유한다.
  • 예를 들어 숫자 유형은 0.1 을 완벽하게 유지할 수 없다. 대신 0.1에 매우 가까운 숫자를 보유한다.
    • 0.2와 0.3도 마찬가지이며 3.1 + 0.2 != 0.3이라는 유명한 예가 나온다.
  • 숫자가 충분히 크면 정수에서도 부정확성이 발생한다.
  • 자바스크립트에는 안전한 정수(sate integer)라는 개념이 있다.
  • 숫자는 값이 -2의 53제곱보다 크고 2의 53제곱보다 작은 정수인 경우 안전한 정수다
    • 53제곱은 숫자 타입이 효과적으로 비트의 이진 정밀도를 갖는다는 사실에서 비롯된다. 나머지 비트는 지수다.
  • 정수는 숫자형으로 정확히 표현된다.
  • 정수는 부동 소수점 부정확성 덕분에 다른 정수로 반올림된 결과가 아님이 보장된다.
    • 예를 들어 2의 53제곱은 숫자 타입에서 정확히 표시되지만 숫자 타입은 2의 53제곱 +1 을 나타낼 수 없다.
const a = 2 ** 53;
console.log(a); // 9007199254740992 (2**53)
const b = a + 1;
console.log(b); // 9007199254740992 (2**53) (again)
  • 반올림의 결과일 수 있으므로 2의 53제곱은 "안전"하지 않다. 그러나 숫자 타입이 부정확한 정수를 반올림하지 않기 때문에 2의 53제곱 - 1은 안전하다.
const a = 2 ** 53 - 1;
console.log(a); // 9007199254740991
const b = a + 1;
console.log(b); // 9007199254740992
const c = a + 2;
console.log(c); // 9007199254740992
const d = a + 3;
console.log(d); // 9007199254740994
  • 코드에서 253와 -(253)과 같은 모호한 마법 숫자를 작성하지 않아도 되도록 Number 생성자에는 두 가지 속성이 있으며 아마도 더 중요한 도움이 될 것이다.
  • Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER
    • Number.MAX_SAFE_INTEGER는 최대 안전 정수인 2의 53제곱 - 1다.
    • Number.MIN_SAFE_INTEGER는 숫자 -2의 53제곱 + 1, 최소 안전 정수다.
  • Number.isSafeInteger
    • Number.isSafeInteger는 인수를 받고 인수가 숫자 타입이고 정수이고 안전한 정수 범위에 있는 경우 true를 반환하는 정적 메서드다.
    • 숫자가 아니거나 정수가 아니거나 범위를 벗어나면 isSafeInteger는 false를 반환한다.
console.log(Number.isSafeInteger(42)); // true
console.log(Number.isSafeInteger(2 ** 53 - 1)); // true
console.log(Number.isSafeInteger(-(2 ** 53) + 1)); // true
console.log(Number.isSafeInteger(2 ** 53)); // false (not safe)
console.log(Number.isSafeInteger(-(2 ** 53))); // false (not safe)
console.log(Number.isSafeInteger(13.4)); // false (not an integer)
console.log(Number.isSafeInteger('1')); // false (string, not number)

17.7.2 Number.isInteger

  • Number.isInteger는 인수를 받고 인수가 숫자이자 정수인 경우 true를 반환하는 정적 메서드다.
  • 인수를 숫자로 강제 변환하지 않으므로 Number.isInteger("1")는 false다.

17.7.3 Number.isFinite, Number.isNaN

  • Number.isFinite와 Number.isNaN은 확인을 수행하기 전에 인수를 숫자로 강제 변환하지 않는다는 점을 제외하고는 전역에서 같은 기능인 isFinited와 isNaN과 같다.
  • 대신 숫자가 아닌 값을 전달하면 false를 반환한다.
  • Number.isFinite는 인수가 숫자인지, 해당 숫자가 유한한지 여부를 결정한다.
const s = '42';
console.log(Number.isFinite(s)); // false: it's a string, not a number
console.log(isFinite(s)); // true: the global function coerces
console.log(Number.isFinite(42)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite(1 / 0)); // false: in JavaScript x / 0 = Infinity
  • Number.isNaN은 인수가 숫자인지 확인하고, NaN 값 중 하나인지 여부를 결정한다.
const s = 'foo';
console.log(Number.isNaN(s)); // false: it's a string, not a number
console.log(isNaN(s)); // true: the global function coerces
const n1 = 42;
console.log(Number.isNaN(n1)); // false
console.log(isNaN(n1)); // false
const n2 = NaN;
console.log(Number.isNaN(n2)); // true
console.log(isNaN(n2)); // true

17.7.4 Number.parseInt, Number.parseFloat

  • 이들은 전역 parseInt와 parseFloat와 동일한 기능이다
    • 말 그대로 Number parseInt === parseInt true다.
  • 기본 전역 변수에 대한 의존도를 줄이기 위한 지속적인 움직임의 일부다.

17.7.5 Number.EPSILON

  • Number. EPSILON은 자바스크립트 숫자 값으로 나타낼 수 있는 1과 1보다 큰 가장 작은 값의 차이가 값인 데이터 속성이다(약 2.2204460492503130808472633361816 × 10-16).
  • 이 용어는 수치 분석에서 부동 소수점 반올림 오차의 측정값인 기계 엡실론에서 유래했으며 그리스 문자 엡실론(ε) 또는 굵은 로마자 u로 표시된다.

17.8 Symbol.isConcatSpreadable

  • 알다시피 배열의 concat 메서드는 임의의 수의 인수를 허용하고 원래 배열의 엔트리와 사용자가 제공한 인수를 사용하여 새 배열을 만든다. 인수 중 하나라도 배열이면 배열의 엔트리를 결과 배열로 “평평화”한다.
const a = ['one', 'two'];
const b = ['four', 'five'];
console.log(a.concat('three', b));
// => ["one", "two", "three", "four", "five"]
  • ES2015부터 concat이 업데이트되어 표준 배열(Array.isArray에 따름)이거나 참으로 평가되는 값을 가진 Symbol.isConcatSpreadable 속성이 있는 모든 인수를 퍼뜨린다.
  • 예를 들어, 다음 예에서 obj는 배열과 유사하지만 concat에 의해 결과에 퍼뜨려지지 않는다. 대신 객체가 결과 배열에 배치된다.
const a = ['one', 'two'];
const obj = {
  0: 'four',
  1: 'five',
  length: 2,
};
console.log(a.concat('three', obj));
// => ["one", "two", "three", {"0": "four", "1": "five", length: 2} ]
  • 참으로 평가되는 값으로 Symbol.isConcatSpreadable을 추가하면 concat은 해당 엔트리도 퍼뜨린다.
const a = ['one', 'two'];
const obj = {
  0: 'four',
  1: 'five',
  length: 2,
  [Symbol.isConcatSpreadable]: true,
};
console.log(a.concat('three', obj));
// => ["one", "two", "three", "four", "five" ]
  • Array에서 상속하지 않는 배열과 유사한 클래스의 프로토타입에 유용할 수 있다.
class Example {
  constructor(...entries) {
    this.length = 0;
    this.add(...entries);
  }
  add(...entries) {
    for (const entry of entries) {
      this[this.length++] = entry;
    }
  }
}
Example.prototype[Symbol.isConcatSpreadable] = true;

const a = ['one', 'two'];
const e = new Example('four', 'five');
console.log(a.concat('three', e));
// => ["one", "two", "three", "four", "five"]
  • ES2015가 정의될 때 DOM의 NodeList에 대한 이야기가 있었고 이와 유사하게 이 속성을 추가하여 확장 가능하도록 연결했지만 아직 일어나지 않았따.
  • 자바스크립트 표준 라이브러리의 객체에는 기본적으로 Symbol.isConcatSpreadable 속성이 없다.

17.9 다양한 구문 수정

17.9.1 널 병합

  • || 연산자는 왼쪽 피연산자를 평가하고 해당 값이 참이면 해당 값을 결과로 취한다. 그렇지 않으면 오른쪽 피연산자를 평가하고 해당 값을 결과로 사용한다.
const delay = this.settings.delay || 300;
  • 이 예에서 는 delay 상수를 this.settings.delay 값이 참으로 평가되는 값이면 이 값으로 설정하고 this. settings.delay가 거짓으로 평가되는 값이면 300으로 설정한다.
  • 하지만 문제가 있다. 프로그래머는 속성이 없거나 값이 정의되지 않은 경우에만 300을 사용하려고 했지만 this.settings.delay 가 0이면 이 거짓이기 때문에 해당 코드도 300을 사용한다.
  • ES2020의 새로운 연산자 널 병합 연산자(??)가 이 문제를 해결한다.
const delay = this.settings.delay ?? 300;
  • 이 예는 this.settings.delay 값이 null이 아니거나 정의되지 않은 경우 delay를 설정하고 this. settings.delay가 null 또는 undefined인 경우 300으로 설정한다.
  • ??를 사용하는 다음 코드는 this.settings.delay가 ?? 식에서 한 번만 평가된다는 점을 제외하고 조건 연산자를 사용하는 것 과 같다.
//== null (느슨한 동등성)은 'null'과 'undefined'를 모두 확인한다.
const delay = this.settings.delay == null ? 300 : this.settings.delay;
  • 널 병합 연산자가 같은 방식으로 단축 평가되기 때문에 || 왼쪽 피연산자가 null이 아니거나 undefined인 경우 오른쪽 피연산자는 전혀 평가되지 않는다. 즉, 오른쪽 피연산자의 부작용이 사용되지 않으면 수행되지 않는다.
const obj = {};
let nextId = 1;

obj.id = obj.id ?? nextId++;
console.log(obj.id, nextId); // 1 2

obj.id = obj.id ?? nextId++;
console.log(obj.id, nextId); // 1 2 again, `nextId` wasn't incremented

17.9.2 옵셔널 체이닝

  • 아래와 같은 코드를 작성한 적이 있는가?
x = some && some.deeply && some.deeply.nested && some.deeply.nested.value;

if (x) {
  x.callback();
}
  • ES2020의 옵셔널 체이닝 연산자를 사용하여 다음과 같이 작성할 수 있다.
x = some?.deeply?.nested?.value;
x.callback?.();
  • **?. 연산자는 왼쪽 피연산자를 평가하고 해당 값이 null이거나 undefined인 경우 결과를 undefined로 하고 나머지 체인을 평가하지 않는다. 그렇지 않으면 속성 접근을 수행하고 체인을 계속할 수 있다.**

17.9.3 선택적인 catch 바인딩

  • ES2019부터는 괄호와 바인딩(e)을 완전히 없앨 수 있다.
try {
  theOperation();
} catch (e) {
  // e를 전혀 사용하지 않는다.
  doSomethingElse();
}

// ES2019 이후

try {
  theOperation();
} catch {
  doSomethingElse();
}

17.9.4 JSON에서 유니코드 줄 바꿈

  • 개념적으로 JSON은 자바스크립트의 엄격한 하위 집합이지만 이전에 이스케이프해야 했던 문자열 리터럴에서 유니코드의 "줄 구분자"(U+2028)와 "단락 구분자"(U+2029) 문자가 이스케이프 처리되지 않은 상태로 표시되도록 ES2019가 변경되기 전까지는 사실이 아니었다.
  • JSON은 문자열에서 이스케이프되지 않도록 허용했지만 자바스크립트는 그렇지 않아 사양이 불필요하게 복잡 해졌다.
  • ES2019부터 둘 다 이스케이프할 필요 없이 자바스크립트 문자열 리터럴에서 유효하다.

17.9.5 JSON.stringify에서 올바른 양식의 JSON

  • 기술적으로, 일부 극단적인 경우에는 JSON.stringify가 유효하지 않은 JSON을 생성하고 있다. 자바스크립트 문자열은 유효하지 않은(짝이 없는) 써로게이트를 허용하는 일련의 UTF-16 코드 단위라는 것을 기억할 것이다. 문자열화되는 문자열 값에 짝을 이루지 않은 써로게이트가 포함된 경우 결과 JSON에 는 짝이 없는 써로게이트가 리터럴 문자로 포함되어 잘못된 JSON이 된다.
  • 변경 사항(ES2019에서)은 단순히 유니코드 이스케이프시 짝을 이루지 않은 대리 출력을 갖는 것이다.
  • 예를 들어, 짝을 이루지 않은 써로게이트 U+DEAD를 리터럴 문자로 출력하는 대신 유니코드 이스케이프 시퀀스 \uDEAD를 출력한다.

17.10 다양한 표준 라이브러리 / 전역 추가

17.10.1 Symbol.hasInstance

function FakeDate() {}
Object.defineProperty(FakeDate, Symbol.hasInstance, {
  value(value) {
    return value instanceof Date;
  },
});
console.log(new Date() instanceof FakeDate); // true

17.10.2 Symbol.unscopables

// Loose mode only, since it uses `with`
const obj = {
  a: 1,
  b: 2,
};
with (obj) {
  console.log(a, b, typeof toString); // 1 2 "function"
}
// Loose mode only, since it uses `with`
const obj = {
  a: 1,
  b: 2,
  [Symbol.unscopables]: {
    b: true, // `b`의 범위를 지정할 수없도록 만들고, `with` 블록에서 제외한다.
  },
};
with (obj) {
  console.log(a, b, typeof toString); // ReferenceError: b is not defined
}
function kindKeys(arrayLikes) {
  var keys = [];
  with (Array.prototype) {
    forEach.call(arrayLikes, function(arrayLike) {
      push.apply(keys, filter.call(arrayLike, function(value){
        return rexIsKey.test(value);
      }))
    }
  }
  return keys;
}

17.10.3 globalThis

17.10.4 심볼 설명 속성

const s = Symbol('example');
console.log(s); // Symbol(example)
console.log(s.description); // example

17.10.5 String.prototype.matchAll

const s = 'Testing 1 2 3';
const rex = /\d/g;
let m;
while ((m = rex.exec(s)) !== null) {
  console.log(`"${m[0]}" at ${m.index}, rex.lastIndex: ${rex.lastIndex}`);
}
// => "1" at 8, rex.lastIndex: 9
// => "2" at 10, rex.lastIndex: 11
// => "3" at 12, rex.lastIndex: 13
const rex = /\d/g;
for (const m of 'Testing 1 2 3'.matchAll(rex)) {
  console.log(`"${m[0]}" at ${m.index}, rex.lastIndex: ${rex.lastIndex}`);
}
// => "1" at 8, rex.lastIndex: 0
// => "2" at 10, rex.lastIndex: 0
// => "3" at 12, rex.lastIndex: 0
  • zxc
const s = 'Testing a-1, b-2, c-3';
for (const {
  index,
  groups: { type, num },
} of s.matchAll(/(?<type>\w)-(?<num>\d)/g)) {
  console.log(`"${type}": "${num}" at ${index}`);
}
// => "a": "1" at 8
// => "b": "2" at 13
// => "c": "3" at 18

17.13 과거 습관을 새롭게

17.13.1 이진 정수 리터럴 사용

  • 타당한 경우 새로운 이진 정수 리터럴을 대신 사용하자.
const oldFlags = {
  something: 0x01,
  somethingElse: 0x02,
  anotherThing: 0x04,
  yetAnotherThing: 0x08,
};

const flags = {
  something: 0b0000001,
  somethingElse: 0b0000010,
  anotherThing: 0b0000100,
  yetAnotherThing: 0b0001000,
};

17.13.2 다양한 수학적 해결 방법 대신 새로운 수학 함수 사용

  • 적절한 경우 Math.imul이나 Math.trunc와 같은 새로운 수학 함수를 대신 사용하자.

17.13.3 기본값을 위해 널 병합 사용

  • 모든 거짓으로 평가되는 값(예: 0)이 기본 값을 트리거하지 않도록 널 병합을 대신 사용하자.
const delay = this.settings.delay ?? 300;

17.13.4 && 검사 대신 옵셔널 체이닝 사용

  • 적절한 경우 옵셔널 체이닝을 사용하자.
document.getElementById('optional')?.addEventListener('click', function () {
  //
});

17.13.5 오류 바인딩(e)을 사용하지 않을 때는 작성하지 않는다

  • 최신 구분을 사용할 때 (e) 부분을 작성하지 않는다.
try {
  theOperation();
} catch {
  doSomething();
}

17.13.6 Math.pow 대신 지수 연산자(**) 사용

  • Math.pow보다 짧게 쓸 수 있고 지수 연산자를 사용하면 Math에 대한 식별자 조회나 pow에 대한 속성 조회가 필요하지 않으므로 지수 연산자를 대신 사용하는 것을 고려하자.