ES2023 새로운 메서드

Y·2023년 6월 14일
0
post-thumbnail

최근 ECMAScript 2023 사양이 확정되었다. 자바스크립트 프로그램을 더욱 예측 가능하고 유지보수하기 쉽게 만드는 데 도움이 되는 배열 객체에 대한 몇 가지 새로운 메서드가 포함되었다.

Mutation and side effects

배열 객체에는 항상 몇 가지 특이한 점이 있었다. sort, reverse, splice와 같은 메서드는 원본 배열을 변경한다. 반면 concat, map, filter와 같은 메서드는 배열의 복사본을 만든 다음 복사본에 대해서 연산한다. 객체 자체를 변경시키는 연산을 수행하면 사이드 이펙트가 발생하여 시스템 내에서 예기치 않은 동작이 발생할 수 있다.

예를 들어 배열의 순서를 거꾸로 변경할때, 아래와 같은 일이 발생할 수 있다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const reversed = languages.reverse();
console.log(reversed); // [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(languages); // [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(Object.is(languages, reversed)); // true

원본 배열이 거꾸로 뒤집어졌으며, 배열을 뒤집은 결과를 새로운 변수에 할당했지만 두 변수 모두 동일한 배열을 가리키고 있다는 것을 볼 수 있다.

추가적으로, 아래 예제에서

const x = [1, 2, 3];
const y = x.reverse();
y.push(0); 
console.log(y); // [3, 2, 1, 0]
console.log(x); // [3, 2, 1, 0]

마치 y만 바꾼 것처럼 보이지만, reverse가 실제 호출된 원본 배열 x를 수정하기 때문에 x와 y 가 함께 반전된다. 이후 0을 추가하면, 모두 같은 배열에 대한 참조이기 때문에 둘 다 [3, 2, 1, 0] 이 된다.

이러한 유형의 버그는 잡기 어렵다.

Mutating arrays and React

원본 배열을 변경하는 메서드를 사용했을 때 발생할 수 있는 가장 알려진 이슈 중 하나는, React 컴포넌트에서 배열을 사용할 때이다. 배열 자체가 동일한 객체이기 때문에, 배열을 변경한 후 새로운 상태를 설정하려고 해도 새 렌더링이 되지 않는다. 대신 배열을 먼저 복사한 다음 복사본을 변경하고 이를 새로운 상태로 설정해야한다. React 문서에서는 이 때문에 배열의 상태를 업데이트하는 방법을 설명한다.

Copy first, then mutate

이를 해결하기 위해서는, 배열을 먼저 복사하나 다음 변경하는 것이다. 배열의 복사본을 만드는 방법에는 Array.from, spread operator(...), 인수가 없는 slice 함수 호출 등 여러 방법이 있다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const reversed = Array.from(languages).reverse();
// => [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(languages);
// => [ 'JavaScript', 'TypeScript', 'CoffeeScript' ]
console.log(Object.is(languages, reversed));
// => false

해결 방법이 있어서 다행이지만, 상태 변경 전 복사를 먼저 수행해야함을 기억해야한다는 것은 좋지 않다.

New methods change by copy

이것이 바로 새로운 메서드들이 등장한 이유이다. toSorted, toReversed, toSpliced, 그리고 with 메서드는 원본 배열을 복사하고 복사본을 변경한 후 반환한다. 하나의 함수만 호출하면 되기 때문에 코드를 작성하기도 더 쉬워지고 배열을 먼저 복사할 필요도 없으므로 읽기도 쉬워진다.

⭐️ Array.prototype.toSorted

toSorted 함수는 새로운 정렬된 배열을 반환한다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const sorted = languages.toSorted();
console.log(sorted);
// => [ 'CoffeeScript', 'JavaScript', 'TypeScript' ]
console.log(languages);
// => [ 'JavaScript', 'TypeScript', 'CoffeeScript' ]

sort 함수에는 복사하는 것 외에 예상치 못한 동작이 몇 가지 존재하는데, toSorted 함수도 그 동작을 공유한다. 악센트 기호를 포함한 문자열이나 숫자를 정렬하는 경우 여전히 주의해야한다. 원하는 결과를 생성하는 비교함수(String의 localeCompare와 같은)를 작성해야한다.

const numbers = [5, 3, 10, 7, 1];
const sorted = numbers.toSorted();
console.log(sorted);
// => [ 1, 10, 3, 5, 7 ]
const sortedCorrectly = numbers.toSorted((a, b) => a - b);
console.log(sortedCorrectly);
// => [ 1, 3, 5, 7, 10 ]
const strings = ["abc", "äbc", "def"];
const sorted = strings.toSorted();
console.log(sorted);
// => [ 'abc', 'def', 'äbc' ]
const sortedCorrectly = strings.toSorted((a, b) => a.localeCompare(b));
console.log(sortedCorrectly);
// => [ 'abc', 'äbc', 'def' ]

⭐️ Array.prototype.toReversed

순서가 반전된 새로운 배열이 반환된다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const reversed = languages.toReversed();
console.log(reversed); // [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(languages); // ["JavaScript", "TypeScript", "CoffeeScript"];

Sonar에는 reverse와 같이 오류의 소지가 있는 메서드를 다루는 규칙이 있다. reverse의 결과를 새 변수에 할당하는 것은 원래의 배열도 변경되었기 때문에 오류가 발생할 가능성이 있다. 이제 배열을 복사하고 복사본을 변경하는 대신 toReversed 또는 toSorted를 사용할 수 있다.

⭐️ Array.prototype.toSpliced

toSpliced 함수는 기존의 splice와는 조금 다르다. splice는 제공된 인덱스 값에 요소를 삭제하고 추가하며 기존 배열을 변경하고, 삭제된 요소를 포함하는 배열을 반환하지만, toSpliced 함수는 제거된 요소 없이 추가된 요소만 포함하여 새로운 배열을 반환한다. 작동 방식은 아래와 같다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const spliced = languages.toSpliced(2, 1, "Dart", "WebAssembly");
console.log(spliced);
// => [ 'JavaScript', 'TypeScript', 'Dart', 'WebAssembly' ]

반환 값에 splice를 사용하는 경우, toSpliced는 대체되지 않는다. 기존 배열을 변경하지 않고 삭제된 요소도 알고 싶다면 복사 메서드인 slice를 사용해야 한다.
안타깝게도 spliceslice는 필요한 인수가 다르다. splice는 인덱스와 해당 인덱스 뒤의 제거한 요소의 수를, slice는 시작과 끝의 두 인덱스를 인수로 받는다.splice 대신 toSpliced를 사용하면서 삭제된 요소도 가져오고 싶다면 다음과 같이 원래 배열에 toSplicedslice를 적용하면 된다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const startDeletingAt = 2;
const deleteCount = 1;
const spliced = languages.toSpliced(
  startDeletingAt,
  deleteCount,
  "Dart",
  "WebAssembly"
);
const removed = languages.slice(startDeletingAt, startDeletingAt + deleteCount);
console.log(spliced);
// => [ 'JavaScript', 'TypeScript', 'Dart', 'WebAssembly' ]
console.log(removed);
// => [ 'CoffeeScript' ]

⭐️ Array.prototype.with

with 함수는 배열의 한 요소를 변경하기 위해 대괄호 표기법을 사용하는 것과 같은 복사 기능이다. 따라서 아래와 같이 배열을 직접 변경하는 대신,

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
languages[2] = "WebAssembly";
console.log(languages);
// => [ 'JavaScript', 'TypeScript', 'WebAssembly' ]

배열을 복사하고 요소를 변경할 수 있다.

const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const updated = languages.with(2, "WebAssembly");
console.log(updated); // [ 'JavaScript', 'TypeScript', 'WebAssembly' ]
console.log(languages); // [ 'JavaScript', 'TypeScript', CoffeeScript' ]

⭐️ Array.prototype.findLast와 Array.prototype.findLastIndex

find 메서드는 배열에서 조건을 만족하는 첫 번째 요소를 찾는다. findLast는 반대로 마지막 요소를 찾는다. findLastIndex는 해당 요소의 인덱스를 찾는다.

const arr = ['orange', 'apple', 'bananba', 'orange', 'kiwi', 'peach']
arr.find(item => item === 'orange') // 'orange'
arr.findIndex(item => item === 'orange') // 0

arr.findLast(item => item === 'orange') // 'orange'
arr.findLastIndex(item => item === 'orange') // 3

References

profile
기록중

0개의 댓글