[완벽 가이드] 7장. 배열

Ninto·2023년 1월 23일
2

학습 기록

목록 보기
9/17

📌 목차

  • 🌱 배열과 특징

    • 자바스크립트의 배열 (Array)
    • 희소 배열 Sparse Array (= 성긴 배열)
    • 문자 배열 (= 배열인 문자열)
    • 다차원 배열
    • 유사 배열 객체 (= 배열 비슷한 객체)
    • 이터러블과 이터레이터
  • 🌱 배열 조작과 메서드

    • 배열 생성하기
    • 배열 조작하기


📌 자바스크립트 배열

배열이 배열이 아니라고? 😨

'자바스크립트의 배열은 배열이 아니다.' 라던가 '자바스크립트의 배열에는 타입이 없다.' 라는 말을 들어본 적이 있을 것 입니다.

실제로 아래와 같이 console에 배열의 타입을 찍어보면 'array'가 아닌 'object'가 나옵니다.

typeof Array() // 'object'

그 이유를 알기 위해선 먼저, 자료구조에서 말하는 배열에 대해 알 필요가 있습니다.

배열은 어느 프로그래밍 언어에나 존재하는 자료구조 입니다.
기본적으로 값의 순서있는 집합이며, 안의 값을 요소(element)라고 합니다.

배열은 프로토타입으로 탐색과 변형 작업을 수행하는 메서드를 갖는, 리스트와 비슷한 객체(list-like objects)입니다. -MDN

여기서 말하는 리스트는 자료구조에서 말하는 배열과 비슷합니다.
자료구조에서 말하는 배열(Array)은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 의미합니다.

위의 그림과 같이 각 요소가 동일한 데이터 크기를 가지며, 빈틈없이 연속적으로 이어져 있습니다. 이러한 배열을 밀집 배열(Dense Array)이라고 하며, 인덱스를 통해 단 한 번의 연산으로 임의의 요소에 접근이 가능합니다.

하지만, 자바스크립트의 배열은 요소가 동일한 크기의 메모리 공간을 갖지 않아도 되며, 연속적으로 이어져 있지 않을 수 도 있습니다.

또한, 원시값, 객체, 함수, 배열 등 어떤 타입의 값이라도 요소가 될 수 있습니다.

const arr = [
  1, 'a', true, null, undefined, , [], {}, function(){}, Symbol(),
    ];

위 구조를 보면, 배열은 인덱스와 요소, 길이를 나타내는 length 프로퍼티를 가지고 있는 것을 확인할 수 있습니다.
그런데 얼핏보면, 인덱스를 키로 가지고 요소를 값으로 가지고 있는 객체와 유사해 보입니다.
또한 자세히 살펴보면, 길이는 10으로 찍히지만 5번째 요소는 비어있음으로 찍히며 포함되어 있지 않은 것을 볼 수 있습니다.

그 이유는 자바스크립트의 배열이 일반적인 배열의 동작을 흉내 낸 특수한 객체이기 때문입니다.

console.log(Array.isArray(arr)); // true
console.log(arr.constructor === Array); // true
console.log(Object.getPrototypeOf(arr) === Array.prototype); //true

이 코드만 살펴보아도 자바스크립트의 배열이 객체라는 것을 알 수 있습니다. 하지만 일반적인 객체와는 차이점이 있는 특수한 객체라는 점을 살펴보아야 합니다.

console.log(Object.getOwnPropertyDescriptors(arr));

자바스크립트 배열의 내부를 살펴보면, 이러한 해쉬테이블로 구현되있는 것을 확인할 수 있습니다.

자바스크립트의 배열은 사실 인덱스를 나타내는 문자열을 프로퍼티 key로 가지며, value 값으로 배열의 요소를 갖고, length 라는 프로퍼티를 갖는 특수한 객체입니다.

인덱스를 문자열 프로퍼티 key로 가지기 때문에, 배열의 요소에 접근할때 대괄호안에 인덱스를 넣는 것 만으로도 해당 요소의 값을 가져 올 수 있습니다.

arr[0]; // 1
arr[1]; // 'a'
arr['1']; // 'a'
arr[1] === arr['1'] // true

일반적으로 배열 인덱스는 0이상 2의 23승 - 2(약 42억) 이하의 정수를 가지며, 그 외의 경우는 객체의 프로퍼티로 취급합니다.

arr[-9999]; // undefined
arr[9999]; // undefined
arr['a'] // undefined

그렇기 때문에 존재하지 않는 인덱스로 요소에 접근해도 에러없이 undefined를 반환할 뿐입니다.



📌 희소 배열 Sparse Array

본 책에서는 성긴 배열이라고 기재되었지만, 희소 배열과 같은 말입니다.

희소배열은 요소들의 인덱스가 연속적이지 않은 배열입니다.

자바스크립트의 배열이 자료구조에서 말하는 밀집 배열(Dense Array)과 다른 점은 이러한 희소 배열(Sparse Array)을 허용하기 때문입니다.

자바스크립트 배열은 내부적으로 해쉬 테이블로 구현되어 있기 때문에 메모리 주소가 연속적으로 나열되지 않습니다.
또한 배열의 요소가 동일한 메모리 크기를 갖지 않아도 되기에 여러가지의 타입을 하나의 배열 안에 넣을 수 있습니다.

그렇다면, 자바스크립트는 왜 밀집배열이 아닌 희소배열을 택했을까요? 🤔

일반적인 배열자바스크립트 배열
인덱스로 요소 접근O(1)O(n)
요소 탐색,추가,삭제평균의 경우 O(n)평균의 경우 O(1)

일반적인 배열의 경우 인덱스로 요소에 빠르게 접근이 가능하지만, 자바스크립트의 배열은 해시 테이블로 구현된 객체이므로 일반적인 배열보다 성능적으로 느립니다.

만약 인덱스로 주어진다면 해당 인덱스와 같은 키값을 찾으려고 O(n)의 시간복잡도를 가지는 순차검색을 해야하기 때문입니다.

하지만 탐색, 추가, 삭제에서의 성능은 자바스크립트의 배열이 일반적인 배열에서의 시간 복잡도보다 더 빠른 성능을 가지게 됩니다.

이런식으로 자바스크립트는 탐색, 추가, 삭제에 의한 장점이 더 크다고 판단해 희소 배열인 해쉬 테이블로 배열을 구현한 것입니다.

자바스크립트 배열과 일반 객체의 성능비교

// 배열에서의 인덱스로 요소접근 약 340ms
const arr = [];
for (let i = 0; i < 10_000_000; i++) {
	arr[i] = i;
}

// 일반객체에서의 인덱스로 요소접근 약 600ms
const obj = {};
for (let i = 0; i < 10_000_000; i++) {
	obj[i] = i;
}

모던 자바스크립트 엔진은 인덱스로 요소에 접근할 때의 단점을 보완하기 위해 배열을 일반 객체와 구별하여 좀 더 배열처럼 동작하도록 최적화하여 구현했습니다.


희소배열을 만드는 방법

희소배열을 만드는 방법은 여러가지가 있습니다.

// 길이가 3인 희소배열 만들기

const arr1 = new Array(3); // [empty, empty, empty]

const arr2 = [,,,]; // [empty, empty, empty]

const arr3 = [];
arr3.length = 3; // [empty, empty, empty]

const arr4 = [];
arr4[2] = 0; // [empty, empty, 0]

const arr5 = [1,2,3];
delete arr5[2]; // [1, 2, empty]

arr1[0] // undefined
arr4[0] // undefined
arr5[2] // undefined

값을 생략한 위치에 실제로 배열 요소가 존재하지는 않지만, 인덱스를 통해 요소에 접근하게 되면 undefined가 반환됩니다.

희소배열은 이렇듯 실제 요소의 개수보다 length가 큽니다.

Q. 아래 배열 arr에 남아있는 요소와 길이는 몇 일까요? 🤔

const arr = [,];

arr[2] = 'a';
arr.shift();
arr.push('b');
delete arr[2];

arr.length;

왜 이런 결과가 나오는지는 배열의 length프로퍼티가 가지고 있는 특징을 함께 살펴보면 이해하기 쉽습니다.

Array.prototype.length

length프로퍼티는 몇 가지 특별한 동작을 수행합니다.
일반적으로 length프로퍼티는 배열의 길이를 반환하며, length 속성에 값을 설정할 경우 배열의 길이를 변경합니다.

자바스크립트의 배열이 일반객체와 다른점은 이런 length프로퍼티를 가지고 있다는 점입니다.

배열에 새 요소를 추가할 때마다 length 프로퍼티의 value값이 자동으로 업데이트됩니다.

자바스크립트에서 정의하는 배열의 길이(length프로퍼티의 value값)는 배열에서 가장 높은 인덱스보다 더 큰 양수의 32 비트 정수값입니다.

즉, length 프로퍼티는 value값으로 0이상의 가장 높은 인덱스만을 기준으로 잡고 +1을 한 값으로 업데이트 됩니다.

const arr = [];
arr[5] = 'el';
arr.length // 6
const arr = [1,2,3];
arr[-1] = 0;
console.log(arr);
/*
0: 1
1: 2
2: 3
-1: 0
length: 3
*/

delete arr[1];
console.log(arr);
/*
0: 1
2: 3
-1: 0
length: 3
*/

위의 코드를 살펴보면 인덱스의 조건에 맞지 않은 값은 객체 프로퍼티로 인식하여 실제 length의 길이에 영향을 주지 않는 것을 확인할 수 있습니다.
또한, delete로 가장 높은 인덱스를 건들이지 않고 중간 요소를 삭제해도 길이는 변하지 않은 점을 볼 수 있습니다.

그렇다면, delete로 배열의 가장 높은 인덱스를 삭제하게 된다면 길이는 변할까요? 🤔

const arr = ['el'];
delete arr[0];
arr.length; // 1
arr; // [empty]

delete는 배열 요소를 삭제해도 배열 길이는 영향을 받지 않습니다. 이것은 배열의 마지막 요소를 삭제하더라도 유지됩니다.

const arr = ['el'];
arr.pop();
arr.length; // 0
arr; // []
const arr = ['el'];
arr.length = 0;
arr; // []

반면, length에 값을 할당하여 조작하거나 pop()을 사용하여 배열의 마지막 요소를 삭제하게 되면 배열의 길이에 영향을 미치게 되게 됩니다.



📌 문자 배열 (= 문자열)


▲ C언어에서의 문자열

문자열은 문자형 배열로, 문자열을 저장하는 하나의 변수와 같은 역할을 합니다.

const word = 'Love';
word[0]; // 'L'
word.length; // 4

기본적으로 문자열은 배열과 비슷하게 0이상의 정수로 시작하는 인덱스에 각각의 값이 담겨져 있으며, length 프로퍼티를 사용할 수 있습니다.

자바스크립트의 문자열은 읽기 전용 배열처럼 동작합니다.

기본적으로 문자열은 불변인 값이므로 기존 배열을 수정하는 push(), sort(), reverse(), splice() 등... 같은 메서드는 동작하지 않습니다.

const word = 'Love';
word[0] = 'D';
word.length = 2;
console.log(word); // 'Love'

또한 인덱스로 직접적인 값을 할당하거나, length의 길이를 조작해도 에러없이 조용히 실패하기만 합니다.



📌 다차원 배열

자바스크립트에서는 다른 언어에서의 다차원 배열을 직접 지원하지는 않지만 배열안에 배열을 만들어 대략적으로 흉내 낼 수 있습니다.

const arr = [[]];
arr[0][0] = 1;

// 반복문으로 10 행 10 열의 2차원 배열 만들기
const table = new Array(10);
for (let i = 0; i < table.length; i++) {
	table[i] = new Array(10);
}

table[9][0] = 'el'; //[세로][가로] 로 접근 
table;

2차원 배열에서 인덱스로 요소에 접근할 때, 첫 번째 대괄호에는 세로 크기, 두 번째 대괄호에는 가로 크기로 표기합니다.



📌 유사 배열 객체

유사 배열 객체는 일반 객체가 인덱스로 프로퍼티 값에 접근할 수 있고, length 프로퍼티를 가지고 있는 경우를 의미합니다. (length 프로퍼티만 가지고 있어도 유사배열객체)

const obj1 = { length: 10 };

const obj2 = {
	0: 'a',
	1: 'b',
	2: 'c',
	3: 'd',
	4: 'e',
	length: 5,
}
Array.isArray(obj2); // false
obj2.hasOwnProperty(Array.prototype.length); // true

유사 배열 객체는 배열의 length 프로퍼티를 가지고 있지만 모든 속성이 true로 변한 것을 확인할 수 있습니다.

// 5의 길이를 갖는 유사 배열 객체 만들기
const obj2 = {};
for (let i = 0; i < 5; i++) {
	obj2[i] = i;
  	obj2.length = i + 1;
}
Array.isArray(obj2); // false

for (let i = 0; i < obj2.length; i++) {
	console.log(obj2[i]); // 0 1 2 3 4
}

obj2[0]; // 0
obj2.0; // SyntaxError
obj2.push(5) // TypeError

유사 배열 객체는 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있고, 인덱스를 나타내는 숫자 형식의 문자열을 프로퍼티 키로 가지므로 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있습니다.

하지만, 유사 배열 객체는 배열이 아니므로 Array.prototype을 상속하지 않습니다.

유사 배열 객체에서 배열 메서드를 사용하려면 call을 사용하거나 Array.from()으로 복사를 한 뒤 사용해야 합니다.



📌 이터러블과 이터레이터

ES6 에서는 순회 가능한 자료구조를 이터러블로 통일하여 for…of 문, 스프레드 문법, 배열 디스트럭처링 할당(구조 분해 할당)의 대상으로 사용할 수 있도록 일원화하였습니다.

iterable을 단어적으로 해석하면 순환하다, 반복하다라는 뜻을 가집니다.

이터러블Symbol.iterator 메서드를 호출했을때 이터레이터 인스턴스를 반환합니다.

자바스크립트는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공합니다.

빌트인 이터러블Symbol.iterator 메서드
ArrayArray.prototype[Symbol.iterator]
StringString.prototype[Symbol.iterator]
MapMap.prototype[Symbol.iterator]
SetSet.prototype[Symbol.iterator]
TypedArrayTypedArray.prototype[Symbol.iterator]
argumentsarguments[Symbol.iterator]
DOM 컬렉션NodeList.prototype[Symbol.iterator], HTMLCollection.prototype[Symbol.iterator]

유사 배열 객체의 경우 Symbol.iterator를 포함하고 있지 않은 일반 객체이기 때문에 이터러블에 해당하지 않습니다.

const obj2 = {};
for (let i = 0; i < 5; i++) {
	obj2[i] = i;
  	obj2.length = i + 1;
}
// for...of문을 사용하면 TypeError: obj2 is not iterable
for(let i of obj2) {
    console.log(i);
}

// 구조분해할당을 사용하면 TypeError: obj2 is not iterable
[ x, y ] = [obj[0], obj[1]];

// 일반 객체의 스프레드 문법 사용
console.log({...obj2}); // {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, length: 5}

// 배열의 스프레드 문법 사용
console.log([...obj2]); // TypeError: obj2 is not iterable

유사 배열 객체에 for...of문과 구조분해 할당을 사용하게 되면 TypeError가 발생하지만, 스프레드 문법을 사용했을 땐 일반 객체의 스프레드 문법이 사용 되는 것을 볼 수 있습니다.


배열은 기본적으로 이터러블이기 때문에 이터레이터 메서드들을 가지고 있습니다.

이터러블이 Symbol.iterator 메서드를 호출해 반환된 이터레이터가 next 메서드를 소유하고, next 메서드를 호출하면 이터러블을 순회하며 value와 done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환할 때 이를 이터레이터라고 합니다.

대표적으로 forEach(), map(), filter(), find(), findIndex(), every(), some(), reduce(), reduceRight() 등...

배열 이터레이터 메서드는 배열 요소를 순서대로 함수에 전달하는 방식으로 동작합니다.

모두 첫 번째 인자로 함수를 받으며 각 배열 요소(또는 일부 요소)에 대해 그 함수를 한 번씩 호출합니다.

const arr = [1, ,3]; // [1, empty, 3]

// 배열 이터레이터 메서드에서의 희소배열 반복
arr.forEach((value, index) => {
  console.log(value, index);
});
/*
1 0
3 2
*/

// for...of문에서 희소배열 반복 
for(i of arr) {
    console.log(i);
}
/*
1
undefined
3
*/

// 일반 for문에서 희소배열 반복
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i], i);
}
/*
1 0
undefined 1
3 2
*/

배열 이터레이터 메서드는 희소 배열처럼 존재하지 않는 요소(empty)는 호출하지 않습니다.

이터레이터 메서드는 호출하는 함수에서 받게되는 인자로 배열 요소의 값, 인덱스, 배열 자체를 받는데 실제로 첫 번째 인자인 배열 요소의 값만 사용하고 두번째, 세번째 인자는 무시할 때도 많습니다.



📌 배열 생성하기

배열을 생성하는 방법에는 여러가지 방법이 있습니다.

// 일반적인 배열 리터럴
const arr1 = [];

// new Array() 생성자
const arr2 = new Array();

// 이터러블 객체에 스프레드 적용
const arr3 = [...arr1];

// Array.of로 생성
const arr4 = Array.of();

// Array.from() 팩토리 메서드 사용
const arr5 = Array.from([]);

그렇다면 각각의 생성 방법들은 어떤 차이점을 가지고 있을까요? 🤔


🔗 배열 리터럴

const arr = [1,2,3]; // 요소 1,2,3을 가지고 있는 배열

배열 리터럴은 배열 요소를 대괄호 안에 콤마로 구분한 리스트 형태입니다.

const num = 5;
const arr = [num, num + 1, num + 2]; // [5, 6, 7]
const arr1 = [[num], { calc: num * num }]; // 배열과 객체도 사용가능
const arr2 = [1, ,3, ]; // 요소 1,3을 가지고 있는 길이가 3인 희소배열
  • 가장 일반적으로 쓰이는 생성 방법
  • 임의의 표현식이 사용 가능
  • 객체 리터럴이나 다른 배열 리터럴도 사용가능
  • 콤마 사이에 요소의 값이 없으면 희소배열이 생성
  • 마지막에 콤마를 허용함

🔗 Array() 생성자

const arr = new Array(); // []

const arr1 = new Array(1); // [empty]

const num = 5;
const arr2 = new Array(10, num + 5); // [10, 10]

const arr3 = new Array([1,2,3], {x: 3}); // [[1,2,3], {x: 3}];

const arr4 = new Array(,); // SyntaxError: Unexpected token ',';
  • 인자 없이 호출 할 경우
    • 요소 없는 빈 배열 생성 (= 배열 리터럴 []과 동등)
  • 배열 길이를 나타내는 숫자 인자 하나로 호출 할 경우
    • 지정된 길이를 가진 배열 생성
    • 아직 배열에 값을 지정하지 않았고, 배열 인덱스도 정의하지 않았습니다.(empty, 인덱스로 접근시 undefined를 반환)
  • 배열 요소를 두 개 이상 쓰거나, 숫자가 아닌 요소를 넘겨 호출 할 경우
    • 생성자의 인자가 새 배열의 요소가 됩니다. (이렇게 쓸 바에 배열 리터럴이 더 단순하다.)
  • 대괄호 없이 콤마의 사용은 불가능
  • 숫자 하나만 쓸 경우 length의 값으로 설정되기 때문에, Array() 생성자는 숫자 요소가 하나만 있는 배열은 생성 할 수 없습니다.

🔗 Array.of()

ES6의 Array.of()함수는 Array()생성자가 가지고 있던 문제를 해결합니다.

const arr = Array.of(1); // [1];
const arr1 = Array.of(10,11,'a',[1,2],); // [10,11,'a',[1,2]]; 
const arr2 = Array.of(); // [];
const arr3 = Array.of(1, ,3); // SyntaxError: Unexpected token ','
  • 인자의 개수를 따지지 않고 각 인자를 새 배열의 요소로 사용합니다.
  • 마지막 콤마를 허용합니다.
  • 콤마 사이의 요소가 비어있는 희소배열은 만들 수 없습니다.

🔗 분해 연산자

ES6 이후에는 분해 연산자 ...를 써서 배열 리터럴 안에 다른 배열 요소를 넣을 수 있습니다.

// 일차원 배열에서의 스프레드 복사
const arr1 = [1,2,3];
const arr2 = [...arr1]; // [1, 2, 3];
const arr3 = [...arr1, ...arr2]; // [1, 2, 3, 1, 2, 3];

arr2[0] = 4;
arr2; // [4, 2, 3]
arr1; // [1, 2, 3] 복사한 원본 배열은 수정되지 않았음
arr3; // [1, 2, 3, 1, 2, 3]; 요소를 바꾸기전 복사한 배열도 수정되지 않음

// 3차원 배열에서의 스프레드 복사
const arr4 = [1,2,3,[4,5, [6,7]]];
const arr5 = [...arr4]; // [1,2,3,[4,5, [6,7]]]

arr4[2] = 8;
arr4[3][0] = 9;
arr4[3][2][0] = 10;
arr4; // [1,2,8, [9,5, [10, 7]]]; 
arr5; // [1,2,3,[9,5, [10, 7]]]; 1단계는 수정되지 않았지만, 2단계 이상 부턴 같이 변경
  • 분해 연산자는 모든 이터러블 객체(문자열, 배열, Set 등등...)에 동작합니다.
  • 주의점은 1차원 배열에서는 spread deep copy를 허용하지만, 2차원 이상 부터는 shallow copy가 적용되는 점을 주의해야 합니다.

🔗 Array.from()

Array.from()은 ES6에서 도입한 팩토리 메서드 입니다.

※ 팩토리 메서드란?
쉽게 정의하자면, 객체를 생성 반환하는 메서드를 의미합니다.
객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 하는 패턴입니다.

 Array.from(arrayLike[, mapFn[, thisArg]])

const obj = {};
for (let i = 0; i < 5; i++) {
	obj[i] = i;
  	obj.length = i + 1;
}

// 유사배열 객체 배열로 만들기가 가능
const newArr1 = Array.from(obj); // [0, 1, 2, 3, 4]
Array.isArray(newArr1); // true
newArr1.push(5);
newArr1; // [0, 1, 2, 3, 4, 5]

const arr = [1,2,[3,4]];

// 2차원 이상까지도 복사는 가능하지만 얕은 복사됨
const newArr2 = Array.from(arr); // [1,2, [3,4]]
newArr2[2][1] = 5;
arr; // [1,2, [3, 5]]
newArr2; // [1,2, [3, 5]]

// 두번째 인자를 map과 같이 사용가능
const arr1 = [1,2,3];
const newArr3 = Array.from(arr1, (v) => v * v); // [1, 4, 9]
const newArr4 = Array.from({length: 3}, (v, i) => i); // [0,1,2]

첫 번째 인자로 이터러블 객체나 유사 배열 객체를 받으며, 해당 객체의 요소로 새 배열을 만들어 반환합니다.
(분해 연산자를 사용한 [...iterable]과 동등합니다.)

이 메서드가 중요한 이유는 유사 배열 객체를 진정한 배열로 바꾸는 방법이기 때문입니다.

Array.from()은 선택 사항으로 두 번째 인자를 받습니다.
두 번째 인자로 함수를 전달하면, 새 배열을 생성할 때 소스 객체의 각 요소를 이 함수에 전달하고 반환 값을 배열에 저장합니다.

이 동작은 map과 아주 비슷하지만, Array.from()으로 배열로 만든 다음 다시 map을 써서 변환하기 보다는 처음부터 콜백 함수를 써서 변환하는 것이 더 효율적입니다.

Q. 아래 arr2는 어떤 요소와 길이를 가질까요? 🤔

const arr = [1, ,3];
const arr2 = Array.from(arr, (v) => v + 1);


📌 배열 조작하기

🔗 요소 추가/삭제하기

push, pop, unshift, shift

const arr = [];
arr[0] = 'a';

// push와 unshift는 현재 배열의 길이를 가리킵니다.
arr.push('b'); // 2
arr.unshift('c'); // 3

// 각각 배열의 맨 끝과 맨 앞에 요소가 추가됩니다.
arr; // ['c', 'a', 'b'];

// pop과 shift는 삭제할 요소를 가리킵니다.
// 각각 배열의 맨 끝과 맨 앞의 요소를 삭제합니다.
arr.pop(); // 'b'
arr; // ['c', 'a']

arr.shift(); // 'c'
arr; // ['a']
const arr = ['a', 'b', 'c'];
arr[1]; // 'b'
arr.shift();
arr[0]; // 'b'
arr.unshift('d','e','f'); // 인자가 여러개일 경우 순서대로 앞에서부터 추가됨
arr; //  ['d', 'e', 'f', 'b', 'c']
arr[3]; // 'b'

arr.push('a','b'); // 인자가 여러개일 경우 순서대로 뒤에서부터 추가됨
arr; // ['d', 'e', 'f', 'b', 'c', 'a', 'b']

unshift와 shift 메서드의 경우 기존에 있던 요소들의 인덱스를 앞당기며,
unshift의 경우 여러개의 인자를 넣게되면 인자의 순서대로 배열의 앞에서부터 새롭게 추가됩니다.


📍 배열 평탄화하기

concat, flat, flatMap

const arr = [1,2];
const arr2 = arr.concat(arr,arr); // [1, 2, 1, 2, 1, 2]

arr.push([3,4]);
arr; // [1,2,[3,4]]
const arr3 = [].concat(arr); // [1,2,[3,4]]

arr.unshift([-1, 0]);
arr; // [[-1,0], 1,2, [3,4]]
const arr4 = [].concat(...arr); // [-1, 0, 1, 2, 3, 4]

const arr5 = arr.flat(); //  [-1, 0, 1, 2, 3, 4]

const arr6 = [1,[2,3 ,[4,5 ,[6,7]]]]; // 4차원 배열
const arr7 = arr6.flat(3); // [1, 2, 3, 4, 5, 6, 7]

const arr8 = [1,2,3].flatMap((v) => [[v + 1]]); // [[2], [3], [4]]
const arr9 = [1,[2,3]].flatMap((v) => v + 1); // [2, '2,31']
const arr10 = [1,[2,3]].flatMap((v) => v); // [1,2,3]

push와 unshift의 경우 배열을 추가하게 될때 평탄화하지 않습니다.
반면, concat은 배열을 받게되는 경우 평탄화하여 추가합니다.
하지만 2차원 이상의 배열인 경우 평탄화시키지 않으므로 스프레드 문법을 사용하여 평탄화 시켜주어야 합니다.

flat같은 경우 배열의 중첩단계에 맞춰 평탄화가 가능합니다.
인자없이 사용할 경우 기본적으로 한 단계만 평탄화가 이루어집니다.

flatMap은 map처럼 사용이 가능하지만, 각 요소를 결과 배열로 분해합니다.


📍 배열 복사/대체/추출/삽입

slice, splice, fill, copyWithin

// fill(채울요소, 시작인덱스, 끝인덱스)
const arr = new Array(4).fill(1,0,3) // [1, 1, 1, 비어 있음]

// copyWithin(타겟점, 복사를 시작할인덱스, 끝인덱스)
const arr2 = arr.copyWithin(3,0) // [1, 1, 1, 1]

// slice(시작인덱스, 끝인덱스)
const arr3 = arr2.slice(); //  [1, 1, 1, 1]
const arr4 = [1,2,3].slice(0, 2); // [1,2]
const arr5 = [1,2,3].slice(-2, 0); // [1,2]

// splice(제거시작점 인덱스, 삭제할개수, 추가할요소)
const arr4 = [1,2,3,4,5];
arr4.splice(1,3,'a','b','c');
arr4; // [1, 'a', 'b', 'c', 5] 기존에 있던 2,3,4가 삭제됨


🔗 배열 이터레이터 메서드

forEach, map, filter, find, findIndex, every, some, reduce, reduceRight

🌱 Array.prototype.forEach()

// 희소배열의 empthy값은 무시함
const arr = [1, ,3];
arr.forEach((value, index, thisArr) => {
	thisArr[index] = value + 1;
}, this)
arr; // [2, 비어 있음, 4]

// 안에서 요소를 추가해도 기존의 요소만큼 반복호출을 돔
const arr2 = [1,2,3];
arr2.forEach((value, index) => {
	arr2.push(value);
})
arr; // [1, 2, 3, 1, 2, 3]

// 안에서 추가되는 길이와 요소는 인식을해도 반복은 기존의 요소만큼 돔
const arr3 = ['a','b','c'];
arr3.forEach((value, index) => {
	arr3.push(value);
  if(arr3.length >= 4) {
      console.log(arr3[0]); // 'a' ,'b', 'c'
      arr3.shift();
  };
})
arr3 // ['a', 'c', 'c']

🌱 Array.prototype.map()

//map으로 원본 배열 수정하기
const arr = [1, ,3];
arr.map((value, index, thisArr) => {
	thisArr[index] = value + 1;
}, this)
arr; //  [2, 비어 있음, 4]

//map은 새 배열을 반환하며 기존 배열은 수정하지 않음
const arr2 = arr.map((value) => {
	return value + 1;
})
arr2; // [2, 비어 있음, 4]
arr; // [1, 비어 있음, 3]

// 기존배열과 새 배열의 길이는 동일함
const arr3 = [1,2,3,4,5];
const arr4 = arr3.map((value) => {
	if(value % 2 === 0) return value;
})
arr4; // [undefined, 2, undefined, 4, undefined]

🌱 Array.prototype.filter()

filter() 메서드는 주어진 함수의 테스트를 통과(true)하는 모든 요소를 모아 새로운 배열로 반환합니다.

const arr = [1,2,3,4,5];
const arr2 = arr.filter((value, index, thisArr) => {
	return value % 2 === 0;
}, this)
arr2; //  [2, 4]

🌱 Array.prototype.find()와 Array.prototype.findIndex()

판별함수에서 조건에 true한 값을 찾는 즉시 순회를 멈춥니다.

const arr = [1,2,3,4,5];

//find는 조건에 true한 첫 번째 요소를 반환합니다.
const num1 = arr.find((value, index, thisArr) => {
	return value % 2 === 0;
}, this)
num1; // 2

//findIndex는 조건에 true한 첫 번째 인덱스를 반환합니다.
const num2 = arr.findIndex((value, index, thisArr) => {
	return value % 2 === 0;
}, this)
num2; // 1

🌱 Array.prototype.every()와 Array.prototype.some()

const arr = [1,3,5];

//every는 조건에 모든 요소가 true할 때만 true를 반환합니다.
// 한 요소라도 false일때 순회를 멈춥니다.
const answer1 = arr.every((value, index, thisArr) => {
	return value % 2 === 0;
}, this)
answer1; // flase

const answer2 = arr.every((value, index, thisArr) => {
	return value % 2 !== 0;
}, this)
answer1; // true

//some은 조건에 한 요소라도 true일때 true를 반환합니다.
// 한 요소라도 true일때 순회를 멈춥니다.
const answer3 = arr.some((value, index, thisArr) => {
	return value === 3;
}, this)
answer3; //true

🌱 Array.prototype.reduce()와 Array.prototype.reduceRight()

const arr = [1,2,3];
const initialValue = 0;

// reduce는 인자로 4개를 전달하며, 초기값을 설정할 수 있습니다.
const sum1 = arr.reduce((accumulator, value, index, thisArr) => {
  console.log(initialValue, accumulator, value)
  return accumulator += value;
},initialValue);

sum1; // 6 최종순회한 acc의 값을 반환합니다.
/*
0 0 1 처음 순회시 acc에는 설정한 초기값이 들어갑니다.
0 1 2 순회하며 acc와 value의 값도 변합니다.
0 3 3
*/

// reduceRight는 value의 값이 가장 큰 인덱스 요소부터 작은순으로 변합니다.
const sum2 = arr.reduceRight((accumulator, value, index, thisArr) => {
  console.log(initialValue, accumulator, value)
  return accumulator += value;
},initialValue);
sum2; // 6
/*
0 0 3
0 3 2
0 5 1
*/


🔗 배열 검색,정렬,변환하기

indexOf, lastIndexOf, includes, sort, reverse, join, toString

🌱 indexOf()와 lastIndexOf(), includes()

indexOf()와 lastIndexOf()는 첫번째 인자로 찾을 값, 두번째 인자로 시작지점을 지정할 수 있습니다.
찾을 수 없는 경우엔 둘 다 -1을 반환합니다.

const arr = [1,'2',4,2,NaN];

// indexOf는 `===`으로 해당 인덱스를 반환함
arr.indexOf(2); // 3
arr.indexOf('3'); // -1 찾을수 없는 경우
arr.indexOf(2,2); //3 arr[2]부터 검색 시작
arr.indexOf(NaN); // -1 NaN은 자기 자신과 같지 않기때문에 찾을 수 없다고 나옴

// lastIndexOf는 뒤에서부터 검색 시작
arr.lastIndexOf('2',-1); // 1 음수를 사용시 배열길이를 더한값으로 적용됨

arr.includes('1'); //false
arr.includes(NaN); //true

🌱 sort()와 reverse()

sort의 기본 정렬 순서는 오름차순이며, 요소를 문자열로 변환한 다음 UTF-16 코드 단위 값의 시퀀스를 비교합니다.

알파벳이 아닌 다른 순서로 정렬하고 싶다면 인자를 사용해 정렬이 가능합니다.
sort의 자세한 동작원리는 MDN-Array.prototype.sort를 참고해주세요.

const arr = [3,0,21,1, ,-1,'a', ,'A'];
arr.sort(); 
arr; //  [-1, 0, 1, 21, 3, 'A', 'a', 비어 있음 × 2]

arr.sort((a,b) => a - b); //  [-1, 0, 1, 3, 21, 'A', 'a', 비어 있음 × 2]
arr.sort((a,b) => b - a); //  [21, 3, 1, 0, -1, 'A', 'a', 비어 있음 × 2]

arr.reverse(); // [비어 있음 × 2, 'a', 'A', -1, 0, 1, 3, 21]

🌱 join()과 toString()

join과 toString을 배열에서 사용하게 되면 문자열로 변환한 값을 반환합니다.

const arr = [1,2,3];

// 인자없이 사용하면 배열의 콤마까지 합쳐짐
const str = arr.join(); // '1,2,3'
typeof str; // 'string'
arr; // [1,2,3] 원본 배열은 수정되지 않는다.

[1,2,3].join(''); // '123'
[1,2,3].join('-'); //'1-2-3'
[1, [2, [3]]].join(''); //'12,3'
[1, [2, [3]]].toString(); //'1,2,3'


📌 참고 자료

https://developer.mozilla.org/ko/
https://velog.io/@yongjin9660/JavaScript-배열이-배열이-아니라고
https://andjjip.tistory.com/226
https://ablue-1.tistory.com/102
https://developer-talk.tistory.com/86
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/delete
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/length
https://codechacha.com/ko/javascript-array-length/
https://cpro95.tistory.com/340
https://devuna.tistory.com/22
https://www.howdy-mj.me/javascript/array-and-array-like-object-and-iterable
https://velog.io/@thumb_hyeok/자바스크립트의-이터러블#이터러블과-유사-배열-객체
https://kotlinworld.com/366

profile
성장에는 성장통이 있기 마련이다.

0개의 댓글