새로운 배열 함수 추가
- ES2015+ 이후에 배열에서 새로운 메서드가 많이 추가되었다.
Array.of()
- 이산 인수로 전달한 값을 배열로 만들어 반환해준다.
const arr = Array.of("a", "b", "c");
console.log(arr); // ['a', 'b', 'c']
기능은 알겠는데, 언제 유용할까?
사실 배열 리터럴([]
) 이 더 간략하다.
서브 클래스로의 활용
class MyArray extends Array {
print() {
console.log(`[${this.join(", ")}]`);
}
sum() {
return this.reduce((ac, cur) => ac + cur, 0);
}
}
const myArray = MyArray.of(1, 2, 3);
myArray.print(); // [1, 2, 3]
console.log(myArray.sum()); // 6
MyArray
가Array
를 상속하면, 따로 생성자를 만들 필요가 사라진다.of()
메서드도 같이 상속 받기 때문에Array.of()
는 서브클래스가 사용할 용도로 좋다.
Array.from()
- 인자를 기반으로 배열을 생성한다.
- 타입에 따라 알맞게 배열을 생성해준다.
Array.from(arrayLike);
Array.from(arrayLike, mapFn, thisArg);
문자열로 생성하기
const str = "123";
const arr = Array.from(str);
console.log(arr); // (3) ['1', '2', '3']
- 이모지와 같이 4바이트로 이루어진 UTF-16 문자가 와도 적절하게 쪼개준다.
split
이 따로 필요 없다.
'배열과 비슷한 객체'로 생성하기
const arr = Array.from({ length: 2, 0: "one", 1: "two" });
console.log(arr); // (2) ['one', 'two']
- '배열과 비슷한 객체'란 객체인데 프로퍼티 이름이 숫자이며, 프로퍼티가 몇개 들어있는지에 대한 정보를
length
로 제공하는 객체이다.- 사실 자바스크립트의 기본 배열은 전부 '배열과 비슷한 객체'이다.
- 자바스크립트에서
ArrayBuffer
가 진짜 다른 언어에서 지원하는 배열과 같은 것이다.
생성하고 값 매핑하기
const str = "123";
const arr = Array.from(str, Number);
console.log(arr); // (3) [1, 2, 3]
- 모든 원소에
Number(element)
를 한 것과 동일한 결과다. - 두번째 인자는
mapFn
으로 매핑 함수를 넣어줄 수 있다.mapFn(value, index)
의 형태로 실행되는 것을 명심해야 한다.Number()
는 2번째 인자가 씹혀서 자동으로 첫번째 인자인value
만 적용된다.parseInt()
의 경우엔 2번째 인자가 진법을 나타내기 때문에 n번째 인덱스에 n진법이 적용되어 버려 의도치 않은 결과를 낼 수 있어 주의해야 한다.
const str = "123";
const arr = Array.from(str, parseInt);
console.log(arr); // (3) [1, NaN, NaN]
- 의도치 않게 원하지 않는 결과를 얻을 수 있다.
1
에선radix
에0
이 들어가 자동으로 10진법이 적용됐다.2
에선1
이 들어가1
진법이 적용됐는데1
진법은 지원하지 않는다. ->NaN
3
에선2
가 들어가2
진법이 적용됐는데2
진법에서3
이란 숫자는 사용할 수 없다.
const str = "123";
const mapToInt = (n) => parseInt(n, 10);
const arr = Array.from(str, mapToInt);
console.log(arr); // (3) [1, 2, 3]
- 기수(
radix
) 를 고정한 새로운 매핑 함수를 정의하여 간단히 해결할 수 있다. - 이 매핑 함수는 첫번째 인자만 사용하므로 인덱스는 무시된다.
범위 배열 만들기
- 아래의 예제는
arrayLike
오브젝트를 제공하고,mapFn
을 제공하여 범위 배열을 만든 예이다. arrayLike
배열에length
가 있어서arrayLike
객체의 길이는100
으로 인식되고, 내부의 원소는undefined
로 취급되고 있다.
arrayLike
배열로 구성
const arr100 = Array.from({ length: 100 }, (_, index) => index);
console.log(arr100); // (100) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Array
객체로 구성
const arr100 = Array.from(Array(100), (_, index) => index);
console.log(arr100); // (100) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Array.from()
에서 인자로 넣는mapFn
함수는 일반적인map()
함수를 쓰는 것과 큰 차이가 있다.- 배열 내부의 값이 초기화되지 않은 상태여도 매핑함수를 수행한다는 것이다.
const arr100 = Array(100).map((_, i) => i);
console.log(arr100); // (100) [empty × 100]
const arr100_2 = Array(100)
.fill()
.map((_, i) => i);
console.log(arr100_2); // (100) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
- 단순히
Array(100).map()
으로 매핑 함수를 적용하면 결과에 원하지 않는 값이 등장한다. arr100_2
의 예제처럼.fill()
메서드를 중간에 끼워넣어야map()
메서드의 적용 범위 안에 들어간다.- 이러한 점 때문에
Array.from()
이 범위 배열 생성 시에 좀 더 안정적으로 매핑함수를 적용할 수 있다.
유틸 함수 구성
const rangeArray = (start, end, step = 1) =>
Array.from(
{ length: Math.floor(Math.abs(end - start) / Math.abs(step)) },
(_, i) => start + i * step
);
Arrays.prototype.keys()
- 배열 키를 순회하는
이터레이터
를 반환한다. - 배열 엔트리의 이름은 기술적으로 문자열이지만, 숫자를 반환한다.
- 희소 배열이라도
0 <= n <= length
범위의 모든 인덱스 값이 이터레이터에 의해 반환된다. - 배열 인덱스가 아닌 열거 가능한 프로퍼티가 있더라도 무시된다.
const a = ["one", "two", "three"];
for (const index of a.keys()) {
console.log(index);
}
/*
출력 결과:
0
1
2
/*
Object.keys()
와의 비교
Object.keys()
와Arrays.prototype.keys()
는 유사한 목적을 가졌지만 동작이 다르다.Object.keys()
는 말 그대로 객체를 위한 메서드인데, 자바스크립트의 배열도 객체기 때문에Object.keys()
가 동작한다.
차이점은 아래와 같다.
- 반환 타입이 다르다.
Object.keys()
: 배열을 반환한다.Arrays.prototype.keys()
: 이터레이터를 반환한다.
const a = [1, 2, 3];
console.log(Object.keys(a)); // (3) ['0', '1', '2']
console.log(a.keys()); // Array Iterator {}
- 반환 값이 다르다.
Object.keys()
: 문자열을 반환한다. ex)['1', '2', '3']
Arrays.prototype.keys()
: 숫자를 반환한다. ex)1, 2, 3
const a = [1, 2, 3];
const objKeys = Object.keys(a);
const protoKeys = [];
for (const e of a.keys()) {
protoKeys.push(e);
}
console.log(objKeys); // (3) ['0', '1', '2']
console.log(protoKeys); // (3) [0, 1, 2]
- 공백인 인덱스에 대한 동작이 다르다.
Object.keys()
: 공백인 인덱스도 순회한다.Arrays.prototype.keys()
: 공백인 인덱스를 무시한다.
const sparseArr = [1, , , , , , , 2, 3];
const protoKeys = [];
for (const e of sparseArr.keys()) {
protoKeys.push(e);
}
console.log(protoKeys); // (9) [0, 1, 2, 3, 4, 5, 6, 7, 8]
console.log(Object.keys(sparseArr)); // (3) ['0', '7', '8']
- 열거 가능한 속성에 대한 동작이 다르다.
Object.keys()
: 열거 가능한 속성이 있다면, 결과에 포함한다.Arrays.prototype.keys()
: 열거 가능한 속성이 있어도 무시하고, 오직 인덱스만 순회한다.
const arr = [1, 2, 3];
arr.enumerableKey = "HELLO";
const protoKeys = [];
for (const e of arr.keys()) {
protoKeys.push(e);
}
console.log(protoKeys); // (3) [0, 1, 2]
console.log(Object.keys(arr)); // ['0', '1', '2', 'enumerableKey']
Arrays.prototype.values()
- 값을 순회하는 이터레이터를 반환한다.
const arr = [1, 2, 3];
for (const v of arr.values()) {
console.log(v); // 1, 2, 3
}
Object.values()
와의 비교
- 위에서
Arrays.prototype.keys()
와Object.keys()
를 비교한 것과 대체로 같은 공통점을 갖는다. - 비어있는 값도 길이에 맞게 잘 순회한다.
const arr = [1, , , , , 2, 3];
const prototypeValues = [];
for (const v of arr.values()) {
prototypeValues.push(v);
}
console.log(prototypeValues); // (7) [1, undefined, undefined, undefined, undefined, 2, 3]
console.log(Object.values(arr)); // (3) [1, 2, 3]
Arrays.prototype.entries()
- 엔트리를 순회하는 이터레이터를 반환한다.
- 엔트리란 객체에선
[키, 값]
을 엔트리라고 한다. - 배열은
arrayLike
객체기 때문에[인덱스, 값]
이 반환된다.
- 엔트리란 객체에선
const arr = ["one", "two", "three"];
for (const [index, value] of arr.entries()) {
console.log(index, value);
}
/*
출력 결과:
0 'one'
1 'two'
2 'three'
*/
Object.entries()
와 비교
- 위에서 비교했던 2가지 메서드와 같다.
const arr = [, undefined, , , "y"];
for (const [index, value] of arr.entries()) {
console.log(index, value, index in arr ? "present" : "absent");
}
/*
출력 결과:
0 undefined 'absent'
1 undefined 'present'
2 undefined 'absent'
3 undefined 'absent'
4 'y' 'present'
*/
- 위의 예에서
in
연산자는 앞쪽 피연산자가 뒤에 오는 피연산 객체의 프로퍼티로 존재하는지를 판단한다. - 아무것도 넣지 않으면,
arrayLike
객체는 인덱스 키를 갖지 않지만,undefined
를 명시적으로 넣으면 인덱스 키를 갖기 때문에present
가 출력된다.
Array.prototype.copyWithin()
- 배열 하나에서 원소를 복사할 때 이용하는 메서드이다.
- 범위를 지정해 배열의 원소를 복사할 수 있다.
target
,start
,end
를 순서대로 인자로 받는다.
- 기존의 배열을 수정하고, 수정된 배열을 반환하는 것도 동시에 한다.
- 단순
for
문으로 배열을 복사하는 경우 생길 수 있는 문제들을 해결할 수 있는 기능을 품고 있다.
단순 예제
const a = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"];
console.log("before: ", a); // (11) ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']
a.copyWithin(2, 8);
console.log("after: ", a); // (11) ['a', 'b', 'i', 'j', 'k', 'f', 'g', 'h', 'i', 'j', 'k']
target
인덱스가2
이고start
인덱스가8
이었다.2
번 인덱스의 알파벳이c
였고,8
번 인덱스의 알파벳이i
였다.c
자리에i
,j
,k
가 순서대로 복사된 것을 볼 수 있다.8
번 인덱스의 알파벳 이후로 쭉 복사된 것이다.
문제 해결 예제
const a = ["a", "b", "c", "d", "e", "f", "g"];
console.log("before: ", a); // (7) ['a', 'b', 'c', 'd', 'e', 'f', 'g']
a.copyWithin(4, 2);
console.log("after: ", a); // (7) ['a', 'b', 'c', 'd', 'c', 'd', 'e']
- 위의 예제는 일반적인
for
문을 이용한 복사였으면 결과가 다르다.- 마지막에
e
를g
에 넣어야 하는 순간e
에 새로 복사된c
가 있기 때문이다. - 그래서 인덱스를 이용하는
for
문을 통해 복사했다면, 마지막이c
,d
,c
로 끝났을 것이다. copyWithin()
은 해당 엘리먼트가 복사 전의 상태인지 확인한다.
- 마지막에
- 어레이가 확장되지 않고 복사가 중지된 모습도 볼 수 있다.
- 정해진 길이를 벗어나지 않는다.
이렇게 특이하게 동작하는 이유는 그래픽 작업에서 이러한 동작을 필요로 하기 때문이다.
공백 처리 예제
const arrayString = (a) => {
return Array.from(a.keys(), (key) => {
return key in a ? a[key] : "*공백*";
}).join(", ");
};
const a = ["a", "b", "c", "d", , "f", "g"];
console.log(arrayString(a)); // a, b, c, d, *공백*, f, g
a.copyWithin(1, 3);
console.log(arrayString(a)); // a, d, *공백*, f, g, f, g
- 공백마저 복사한다.
Array.prototype.find()
- 첫번째로 일치하는 원소를 찾는다.
- 찾지 못하면
undefined
를 반환한다.
기본 예제
const a = [1, 2, 3, 4, 5, 6];
const find2 = a.find((v) => v === 2);
const find7 = a.find((v) => v === 7);
console.log(find2); // 2
console.log(find7); // undefined
빈 배열 예제
const a = [];
const findTrue = a.find((v) => {
console.log("hello");
return true;
});
console.log(findTrue); // undefined
- 빈 배열을 대상으로 했을 때는 바로
undefined
를 반환한다.
배열을 중간에 수정하는 예제
const a = ["one", "two", "three"];
const x = a.find((value, index) => {
console.log(`Visiting index ${index}: ${value}`);
if (index === 0) {
a[2] = a[2].toUpperCase();
} else if (index === 1) {
a.push("four");
}
return value === "four";
});
console.log(x); // undefined
/*
출력결과:
Visiting index 0: one
Visiting index 1: two
Visiting index 2: THREE
undefined
*/
- 순회 중간에 배열에 원소가 추가되어도 추가된 원소까지 순회하지 않는다.
- 그러한 이유로 원소를 찾지 못해
undefined
를 반환한다.
const x = [1, 2, 3];
for (let i = 0; i < x.length; i++) {
if (i === 1) {
x.push(4);
}
console.log(x[i]);
}
/*
출력결과:
1
2
3
4
*/
- 일반
for
문은length
를 기준으로 하고 중간에length
가 업데이트 되기 때문에 추가된 원소까지 순회한다.
Array.prototype.findIndex()
find()
와의 유일한 차이는 인덱스를 반환한다는 것 뿐이다.
const arr = [1, 2, 3, 4, 5];
const index = arr.findIndex((v) => v === 5);
console.log(index); // 4
5
는 4번째 인덱스에 있다.
Array.prototype.fill()
- 배열에 특정한 값을 채운다.
const a = Array(5).fill("hello");
console.log(a); // (5) ['hello', 'hello', 'hello', 'hello', 'hello']
객체를 사용할 때 빠질 수 있는 함정
const a = Array(2).fill({});
a[0].name = "jake";
a[1].name = "jack";
console.log(a);
/*
출력 결과:
0: {name: 'jack'}
1: {name: 'jack'}
*/
- 분명 의도는
0
번 인덱스에는name
프로퍼티에jake
값을 넣고1
번엔jack
을 넣는 것이었다. - 이렇게 결과가 나온 이유는 객체가 참조하는 주소가 둘 다 똑같아서 그렇다.
Array.prototype.fill()
은 순회하지 않고 한번에 값을 넣는다는 것을 알아야 한다.
const a = Array.from({ length: 2 }, () => ({}));
a[0].name = "jake";
a[1].name = "jack";
console.log(a);
/*
출력 결과:
0: {name: 'jake'}
1: {name: 'jack'}
*/
Array.from()
을 이용해 해결하는 방법이다.
const a = Array(2)
.fill()
.map(() => ({}));
a[0].name = "jake";
a[1].name = "jack";
console.log(a);
/*
출력 결과:
0: {name: 'jake'}
1: {name: 'jack'}
*/
Array.prototype.fill()
과Array.prototype.map()
을 연계해 해결하는 방법이다.
Array.prototype.includes()
- 주어진 값이 배열에 있는지 확인한다.
const a = ["hello", "world"];
console.log(a.includes("world")); // true
console.log(a.includes("korea")); // false
SameValueZero
알고리즘
Array.prototype.includes()
는SameValueZero
알고리즘에 의해 주어진 값이 배열에 있는지 판단한다.- 이는
indexOf()
와 같은 메서드에서 사용하는엄격한 동등성 비교 알고리즘
과 다르다. NaN
의 처리에서 둘의 결과가 다르다.엄격한 동등성 비교 알고리즘
은NaN
과NaN
을 정확히 비교하지 못한다.
- 이는
const a = [NaN];
console.log(a.includes(NaN)); // true
console.log(a.indexOf(NaN)); // -1
이 외에
[-0].includes(+0)
은 엄격한 동등성 비교 알고리즘처럼true
를 반환한다.
이 점에서Object.is()
메서드와는 다르다.
Array.prototype.flat()
- 다차원 배열을
flat
하게 만든다. - 배열을 평평하게 만들기 위해 사용했던
concat()
메서드의 사용을 대체한다.
const original = [[1, 2, 3], 4, 5, [6, 7, 8]];
const flattened = original.flat();
console.log(flattened); // (8) [1, 2, 3, 4, 5, 6, 7, 8]
depth 조정
- 얼마나 깊은 depth 까지 flatten 을 진행할 것인지 인수로 설정 가능하다.
- 최대 값은
Infinity
이다.
const original = [
[1, 2, 3],
[[4, 5, 6], [[7, 8, 9]]],
];
const flat1 = original.flat();
console.log(flat1); // (5) [1, 2, 3, Array(3), Array(1)]
const flat2 = original.flat(2);
console.log(flat2); // (7) [1, 2, 3, 4, 5, 6, Array(3)]
const flatInfinity = original.flat(Infinity);
console.log(flatInfinity); // (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]
Array.prototype.flatMap()
map
이후flat
을 수행하는 메서드라고 보면 된다.- 단일 레벨만 평면화가 가능하다.
const original = [[1, 2, 3], 4, 5, [6, 7, 8]];
const flatMap = original.flatMap((e) => (typeof e === "number" ? e + 10 : e));
console.log(flatMap); // (8) [1, 2, 3, 14, 15, 6, 7, 8]
반응형
'자바스크립트 > 모던 자바스크립트' 카테고리의 다른 글
모던 자바스크립트, TypedArray (타입이 있는 배열) (0) | 2023.03.19 |
---|---|
모던 자바스크립트, ES2019 의 stable 내장 정렬 (Array.prototype.sort) (0) | 2023.03.15 |
모던 자바스크립트, 편의 유틸 문자열 메서드 (0) | 2023.03.12 |
모던 자바스크립트, UTF-16 이슈 해결에 관련된 문자열 함수 (0) | 2023.03.11 |
모던 자바스크립트, 템플릿 리터럴과 템플릿 태그 함수 (0) | 2023.03.08 |