ECMAScript 2023 사양이 최근 확정되었습니다. 여기에는 자바스크립트 프로그램을 더욱 예측 가능하고 유지보수하기 쉽게 만드는 데 도움이 되는 배열 객체에 대한 몇 가지 새로운 메서드가 포함되었습니다.
toSorted
, toReversed
, toSpliced
및 with
메서드를 사용하면, 기존 배열의 데이터를 변경하지 않고 복사본을 만든 뒤 복사본을 변경하여 배열에 대한 연산을 수행합니다.
배열 객체에는 항상 몇 가지 특이한 점이 있었습니다. 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
메서드는 원본 배열을 복사하고 복사본을 변경한 후 반환합니다. 하나의 함수만 호출하면 되기 떄문에 코드를 작성하는 데에도 더 쉬워지고 배열을 먼저 복사할 필요가 없으므로 읽기도 더 쉬워집니다. 그렇다면 각 메서드는 무엇을 수행할까요?
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' ]
toReversed 함수를 사용하면 순서가 반전된 새 배열이 반환됩니다.
const languages = ['JavaScript', 'TypeScript', 'CoffeeScript'];
const reversed = languages.toReversed();
console.log(reversed); // ['CoffeeScript', 'TypeScript', 'JavaScript']
reverse
의 결과를 새 변수에 할당하는 것은 원래의 배열도 변경되었기 때문에 오류가 발생할 가능성이 있습니다. 이제 배열을 복사하고 복사본을 변경하는 대신 toReversed
또는 toSorted
를 사용할 수 있습니다.
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']
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']
일반 배열 객체 뿐만 아니라 모든 TypedArray
에 toSorted
, 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