[javascript] Array

skdus·2022년 4월 17일
1

JavaScript

목록 보기
3/17
post-thumbnail

💡 Array(배열)

키를 사용해 식별할 수 있는 값을 담은 컬렉션은 객체라는 자료구조를 이용해 저장하는데, 객체만으로도 다양한 작업을 할 수 있다.
그런데 개발을 진행하다 보면 첫 번째 요소, 두 번째 요소, 세 번째 요소 등과 같이 순서가 있는 컬렉션이 필요할 때가 생기곤 한다. 이럴 때 사용하는 것이 바로 배열이다.

// 선언
let arr = new Array(item1, item2...);
let arr = [item1, item2...];

let arr = new Array(2);
alert(arr[0]); //undefined
alert(arr.lenght) //2

보통 두 번째 선언을 이용한다. new Array(number)을 호출하면 길이가 number인 배열이 만들어지는데, 이 때 요소는 비어있기 때문이다.

let fruits = ["사과", "오렌지", "자두"];

// 요소 확인
alert( fruits[1] ); // 오렌지

// 요소 수정/추가
fruits[2] = '배';
fruits[3] = '레몬'; // ["사과", "오렌지", "배", "레몬"]

alert( fruits.length ); // 4

배열 요소의 자료형엔 제약이 없다.

let arr = [ '사과', { name: '이보라' }, true, function() { alert('안녕하세요.'); } ];

// 객체의 프로퍼티 출력
alert( arr[1].name ); // 이보라

// 함수 실행
arr[3](); // 안녕하세요.

🌱 trailing 쉼표
배열의 마지막 요소는 객체처럼 쉼표로 끝날 수 있다. trailing(길게 늘어지는) 쉼표를 사용하면 모든 줄의 생김새가 유사해지기 때문에 요소를 넣거나 빼기가 쉬워진다.
ex) let fruits = ["사과", "오렌지", "자두",];


💡 pop, push, shift, unshift

큐(queue)

배열을 사용해 만들 수 있는 대표적인 자료구조로, 배열과 마찬가지로 순서가 있는 컬렉션을 저장하는 데 사용

  • push - 맨 끝에 요소를 추가
  • shift - 제일 앞 요소를 꺼내 제거한 후 남아있는 요소들을 앞으로 밀어준다. 그럼 두 번째 요소가 첫 번째 요소가 됨!

큐를 사용하면 먼저 집어넣은 요소가 먼저 나오기 때문에 큐는 선입선출(First-In-First-Out, FIFO) 자료구조

스택(stack)

스택은 '한쪽 끝’에 요소를 더하거나 뺄 수 있게 해주는 자료구조

  • push – 요소를 스택 끝에 추가
  • pop – 스택 끝 요소를 추출
    스택을 사용하면 가장 나중에 집어넣은 요소가 먼저 나오기 때문에, 후입선출(Last-In-First-Out, LIFO) 자료구조

처음이나 끝에 요소를 더하거나 빼주는 연산을 제공하는 자료구조를 컴퓨터 과학 분야에선 데큐(deque, Double Ended Queue)라고 부른다.

🌱 배열 끝에 무언가를 해주는 메서드
pop: 배열 끝 요소를 제거하고, 제거한 요소를 반환
push: 배열 끝에 요소를 추가

let fruits = ["사과", "오렌지", "배"];

fruits.pop();
alert( fruits ); // 사과,오렌지

fruits.push("망고");
alert( fruits ); // 사과,오렌지,망고

🌱 배열 앞에 무언가를 해주는 메서드
shift: 배열 앞 요소를 제거하고, 제거한 요소를 반환
unshift: 배열 앞에 요소를 추가

let fruits = ["사과", "오렌지", "배"];

fruits.shift();
alert( fruits ); // 오렌지,배

fruits.unshift('망고');
alert( fruits ); // 망고,오렌지,배

push와 unshift는 요소 여러 개를 한 번에 더해줄 수도 있다.

let fruits = ["사과"];

fruits.push("오렌지", "배");
fruits.unshift("파인애플", "레몬");

// ["파인애플", "레몬", "사과", "오렌지", "배"]
alert( fruits );

💡 배열의 내부 동작 원리

배열은 특별한 종류의 객체이다. 배열 arr의 요소를 arr[0]처럼 대괄호를 사용해 접근하는 방식은 객체 문법에서 왔으며, 키가 숫자라는 점만 다른 것이다.

숫자형 키를 사용함으로써 배열은 객체 기본 기능 이외에도 순서가 있는 컬렉션을 제어하게 해주는 특별한 메서드를 제공한다.

그래도 배열의 본질은 객체이기 때문에 원시 자료형에 해당하지 않고, 객체처럼 동작한다. 예를 들어 배열은 객체와 마찬가지로 참조를 통해 복사된다.

let fruits = ["바나나"]

let arr = fruits; // 참조 복사(두 변수가 같은 객체를 참조)

alert( arr === fruits ); // true

arr.push("배"); // 참조를 이용해 배열을 수정

alert( fruits ); // 바나나,배

🌱 배열을 잘못 사용하는 예

  • arr.test = 5 같이 숫자가 아닌 값을 프로퍼티 키로 사용하는 경우
  • arr[0]과 arr[1000]만 추가하고 그사이에 아무런 요소도 없는 경우
  • arr[1000], arr[999] 같이 요소를 역순으로 채우는 경우

배열은 객체이므로 예시처럼 원하는 프로퍼티를 추가해도 문제가 발생하지 않는다. 하지만, 이러면 자바스크립트 엔진이 배열을 일반 객체처럼 다루게 되어 배열을 다룰 때만 적용되는 최적화 기법이 동작하지 않아 배열 특유의 이점이 사라진다.


💡 성능

push와 pop은 빠르지만 shift와 unshift는 느리다.

shift 연산 동작 과정
1. 인덱스가 0인 요소를 제거
2. 모든 요소를 왼쪽으로 이동. 이때 인덱스 1은 0, 2는 1로 변한다.
3. length 프로퍼티 값을 갱신

unshift를 실행했을 때도 이와 유사한 일이 일어난다.
반면에 push나 pop은 요소 이동을 수반하지 않는다. pop 메서드로 요소를 끝에서 제거하려면 마지막 요소를 제거하고 length 프로퍼티의 값을 줄여주기만 하면 끝!

pop 메서드는 요소를 옮기지 않으므로 각 요소는 기존 인덱스를 그대로 유지한다. 그래서 실행 속도가 빠름.


💡 반복문

for (let i=0; i<arr.length; i++)

가장 빠른 방법이고 오래된 브라우저와도 호환된다.

let arr = ["사과", "오렌지", "배"];

for (let i = 0; i < arr.length; i++) {
  alert( arr[i] );
}

for (let item of arr)

이 반복문을 사용하면, 현재 요소의 인덱스는 얻을 수 없고 값만 얻을 수 있다. 배열 요소를 대상으로 반복 작업을 할 때 사용하자.

let fruits = ["사과", "오렌지", "자두"];

for (let fruit of fruits) {
  alert( fruit );
}

for (let i in arr)

배열은 객체형에 속하므로 for..in을 사용하는 것도 가능하지만, 사용하지 말자

let arr = ["사과", "오렌지", "배"];

for (let key in arr) {
  alert( arr[key] ); // 사과, 오렌지, 배
}

❓ 왜 사용하면 안될까?

  • for..in 반복문은 모든 프로퍼티를 대상으로 순회한다.
    즉, 키가 숫자가 아닌 프로퍼티도 순회 대상에 포함된다. length 프로퍼티나 요소마다 인덱스도 있고, 키가 숫자형이 아닌 프로퍼티와 메서드가 있을수도 있다. 그렇기에 필요없는 프로퍼티들이 문제를 일으킬 수 있다.
  • for..in 반복문은 배열이 아니라 객체와 함께 사용할 때 최적화되어 있어서 배열에 사용하면 객체에 사용하는 것 대비 10~100배 정도 느리다.

💡 ‘length’ 프로퍼티와 다차원 배열

length

배열에 무언가 조작을 가하면 length 프로퍼티가 자동으로 갱신된다. length 프로퍼티는 배열 내 요소의 개수가 아니라 가장 큰 인덱스에 1을 더한 값이다

let fruits = [];
fruits[123] = "사과";

alert( fruits.length ); // 124

위의 예시처럼 사용하지 말자!

length의 값은 쓸 수도 있다. 예를 들어, 값을 수동으로 증가시키면 아무 일도 일어나지 않지만, 값을 감소시키면 배열이 잘린다. 짧아진 배열은 다시 되돌릴 수 없음!

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

arr.length = 2;
alert( arr ); // [1, 2]

arr.length = 5; // 본래 길이로 되돌리기(?)
alert( arr[3] ); // undefined 

이런 특징을 이용하면 arr.length = 0; 을 사용해 아주 간단하게 배열을 비울 수 있다.

다차원 배열 (multidimensional array)

행렬을 저장하는 용도로 사용한다.

let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];
alert( matrix[1][1] ); // 5

💡 toString

배열엔 toString 메서드가 구현되어 있어 이를 호출하면 요소를 쉼표로 구분한 문자열이 반환된다.

let arr = [1, 2, 3];

alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true

alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"

배열엔 Symbol.toPrimitive나 valueOf 메서드가 없다. 그래서 문자열로의 형 변환이 일어나 []는 빈 문자열, [1]은 문자열 "1", [1,2]는 문자열 "1,2"로 변환된다.
이항 덧셈 연산자 "+"는 피연산자 중 하나가 문자열인 경우 나머지 피연산자도 문자열로 변환!!


💡 결론

배열은 순서가 있는 자료를 저장하는 용도로 만들어진 특수한 자료구조이다.
만약 임의의 키를 사용해야 한다면 배열보단 일반 객체 { }가 적합하다.

참고자료

0개의 댓글