ES2023에서 도입된 자바스크립트의 새로운 배열 복사 메서드

이윤우·2023년 5월 31일
0

JavaScript

목록 보기
32/34
post-thumbnail

ECMAScript 2023 사양이 최근 확정되었습니다. 여기에는 자바스크립트 프로그램을 더욱 예측 가능하고 유지보수하기 쉽게 만드는 데 도움이 되는 배열 객체에 대한 몇 가지 새로운 메서드가 포함되었습니다.
toSorted, toReversed, toSplicedwith 메서드를 사용하면, 기존 배열의 데이터를 변경하지 않고 복사본을 만든 뒤 복사본을 변경하여 배열에 대한 연산을 수행합니다.

변경과 사이드 이펙트

배열 객체에는 항상 몇 가지 특이한 점이 있었습니다. sort, reverse 그리고 splice와 같은 메서드는 원본 배열을 변경합니다. 반면 concat, map, filter와 같은 메서드는 배열의 복사본을 만든 다음 복사본에 대해 연산합니다. 객체 자체를 변경하는 연산을 수행하면 사이드 이펙트가 발생하여 시스템 내에서 예기치 못한 동작이 발생할 수 있습니다.
예를 들어 배열의 순서를 거꾸로 뒤집을 때 아래와 같은 일이 발생할 수 있습니다.

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

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

먼저 복사한 다음 변경하기

이 문제를 해결하는 방법은 배열을 먼저 복사한 다음 변경하는 것입니다. 배열의 복사본을 만드는 방법에는 Array.from, 스프레드 연산자(...), 인수가 없는 slice함수 호출 등 여러 가지 방법이 있습니다.

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

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

새로운 메서드는 복사를 통해 변경합니다.

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

Array.prototype.toSorted

toSorted 함수는 새롭게 정렬된 배열을 반환합니다.

const languages = ['JavaScript', 'TypeScript', 'CoffeeScript'];
const sorted = languages.toSorted();
console.log(sorted) // ['CoffeeScript', 'TypeScript', 'JavaScript']
console.log(languages) // ['JavaScript', 'TypeScript', 'CoffeeScript']
console.log(Object.is(sorted, languages)) // false

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

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

toReversed 함수를 사용하면 순서가 반전된 새 배열이 반환됩니다.

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

reverse의 결과를 새 변수에 할당하는 것은 원래의 배열도 변경되었기 때문에 오류가 발생할 가능성이 있습니다. 이제 배열을 복사하고 복사본을 변경하는 대신 toReversed 또는 toSorted를 사용할 수 있습니다.

Array.prototype.toSpliced

toSpliced 함수는 기존의 splice와 조금 다릅니다. splice는 제공된 인덱스 값에 요소를 삭제하고 추가하며 기존 배열을 변경하고 삭제된 요소를 포함하는 배열을 반환하지만, toSpliced 함수는 제거된 요소 없이 추가된 요소만 포함하여 새로운 배열을 반환합니다.

let months = ['January', 'February', 'Monday', 'Tuesday'];
let days = months.splice(2, 2, 'March', 'April');

console.log(days); // ['Monday', 'Tuesday']
console.log(months); // ['January', 'February', 'March', 'April']
const languages = ['JavaScript', 'TypeScript', 'CoffeeScript'];
const spliced = languages.toSpliced(2, 1, 'Dart', 'WebAssembly');
console.log(languages); // ['JavaScript', 'TypeScript', 'CoffeeScript']
console.log(spliced); // ['JavaScript', 'TypeScript', 'Dart', 'WebAssembly']

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']

Not just Arrays

일반 배열 객체 뿐만 아니라 모든 TypedArraytoSorted, toReversed, with를 사용할 수 있습니다. TypedArray에는 splice 메서드가 없으므로 toSpliced 메서드 또한 사용할 수 없습니다.

주의사항

map, filter, concat와 같은 메서드는 이미 복사를 수행한다고 이야기 했습니다. 하지만 이러한 메서드와 새로운 복사 메서드 사이에는 차이가 있습니다. 내장된 Array 객체를 확장한 인스턴스에서 map, flatMap, filter 혹은 concat를 사용하면 동일한 타입의 새 인스턴스가 반환됩니다. Array를 확장하고 toSorted, toReversed, toSpliced 혹은 with를 사용하면 일반 Array를 반환합니다.

class MyArray extends Array {}
const languages = new MyArray('JavaScript', 'TypeScript', 'CoffeeScript');
const upcase = languages.map((language) => language.toUpperCase())
console.log(upcase instanceof MyArray); // true

const reversed = languages.toReversed();
console.log(reversed instanceof MyArray); // false

MyArray.from을 사용하여 다시 커스텀 Array로 되돌릴 수 있습니다.

class MyArray extends Array {}
const languages = new MyArray('JavaScript', 'TypeScript', 'CoffeeScript');
const reversed = MyArray.from(languages.toReversed());
console.log(reversed instanceof MyArray); // true

0개의 댓글