JS/[web] 코어 JS 튜토리얼

[코어 JS] 5.4 - 배열

web_seul 2021. 10. 23. 19:35
반응형

5.4 배열

배열

키를 사용해 식별가능한 값을 담은 컬렉션은 객체라는 자료구조를 이용해 저장하는데 객체만으로도 다양한 작업이 가능  

객체는 순서와 관련된 메서드가 없어서 불편하고 애초에 순서를 고려하지않는 자료구조이므로 '사이에' 끼워넣는 기능이 불가능, 순서가 있는 컬렉션이 필요할 때,  배열을 사용

 

배열 선언

//빈배열 생성
let arr = new Array();
let arr = [];

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

각 배열의 요소에는 0부터 시작하는 숫자(인덱스)가 있고, 이 숫자는 배열 내 순서를 나타냄, 배열 내 특정 요소를 얻을 때는 대괄호 안에 순서를 나타내는 숫자인 인덱스를 넣어서 확인

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

alert( fruits[0] ); // 사과
alert( fruits[1] ); // 오렌지
alert( fruits[2] ); // 자두

//요소 수정
fruits[2] = '배'; // 배열이 ["사과", "오렌지", "배"]로 바뀜

//요소 추가
fruits[3] = '레몬'; // 배열이 ["사과", "오렌지", "배", "레몬"]으로 바뀜

//배열 내 요소 개수 알아내기
alert( fruits.length ); // 3

//요소 전체 출력
alert( fruits ); // 사과,오렌지,자두

 

배열 요소의 자료형에는 제약이 없음

// 요소에 여러 가지 자료형이 섞여 있습니다.
let arr = [ '사과', { name: '이보라' }, true, function() { alert('안녕하세요.'); } ];

// 인덱스가 1인 요소(객체)의 name 프로퍼티를 출력합니다.
alert( arr[1].name ); // 이보라

// 인덱스가 3인 요소(함수)를 실행합니다.
arr[3](); // 안녕하세요.

 

! trailing 쉼표 _ 배열의 마지막 요소는 객체와 마찬가지로 쉼표로 끝날 수 있음, 모든 줄의 생김새가 유사하므로 요소를 넣고 빼기 쉬움

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

 

pop.push와 shift.unshift

큐(queue) : 배열을 사용해 만들 수 있는 대표적인 자료구조로 배열과 마찬가지로 순서가 있는 컬렉션 저장시 사용, 화면에 순차적으로 띄울 메시지르 비축해 놓을 자료 구조를 만들 때 큐를 사용하듯 큐는 실무에서 상당히 자주 쓰이는 자료구조, 선입선출(First-In-First-Out, FIFO)

- push : 맨 끝에 요소 추가

- shift : 제일 앞 요소를 꺼내서 제거한 후 남아있는 요소들을 앞으로 밀어줌(두번째 요소가 첫번째 요소가 됨)

스택(stack) : '한쪽 끝'에 요소를 더하거나 뺄 수 있게 해주는 자료구조, 가장 나중에 넣은 요소가 먼저 나옴, 후입선출(Last-In-First-Out, LIFO)

- push : 요소를 스택 끝에 넣음

- pop : 스택 끝 요소 추출

이렇게 처음이나 끝에 요소를 더하거나 빼주는 연산을 제공하는 자료구조를 데큐(deque, Double Ended Queue)라 함

 

배열 에 무언가를 해주는 메서드

- pop : 배열 끝 요소 제거, 제거한 나머지 요소 반환

let fruits = ["사과", "오렌지", "배"];
alert( fruits.pop() ); // 배열에서 "배"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.
alert( fruits ); // 사과,오렌지

- push : 배열 끝에 요소 추가, 여러개 한번에 추가 가능

let fruits = ["사과", "오렌지"];
fruits.push("배");
alert( fruits ); // 사과,오렌지,배

fruit.push(...)를 호출하는 것은 fruits[fruits.length] = ...하는 것과 같은 효과 ........????왜???

 

배열 에 무언가를 해주는 메서드

- shift : 배열 앞 요소를 제거하고 나머지 요소 반환

let fruits = ["사과", "오렌지", "배"];
alert( fruits.shift() ); // 배열에서 "사과"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.
alert( fruits ); // 오렌지,배

- unshift : 배열 앞에 요소 추가, 여러개 한번에 추가 가능

let fruits = ["오렌지", "배"];
fruits.unshift('사과');
alert( fruits ); // 사과,오렌지,배

 

배열의 내부 동작 원리

배열은 객체의 특별한 종류로 배열의 arr의 요소를 arr[0]처럼 대괄호를 사용해 접근하는 방식은 객체 문법을 따름, 다만 배열은 키가 숫자라는 차이가 있음, 숫자형 키를 사용함으로써 배열은 객체 기본 기능 외에 순서가 있는 컬렉션을 제어하게 하는 특별한 메서드를 제공, length라는 프로퍼티 제공

배열은 JS의 일곱가지 원시 자료형이 아닌 객체형에 속하므로 객체처럼 동작함

배열은 객체와 마찬가지로 참조를 통해 복사됨

let fruits = ["바나나"]
let arr = fruits; // 참조를 복사함(두 변수가 같은 객체를 참조)
alert( arr === fruits ); // true

arr.push("배"); // 참조를 이용해 배열을 수정합니다.(저장공간 공유)
alert( fruits ); // 바나나,배 - 요소가 두 개가 되었습니다.(저장공간을 공유했으므로 동시 수정됨)

 

배열을 배열답게 만들어주는 것은 특수 내부 표현방식으로 JS엔진은 배열의 요소를 인접한 메모리 공간에 차례로 저장해 연산 속도를 높임, 이 외에도 배열 관련 연산의 속도를 높히는 다양한 최적화 기법이 있음

배열을 '순서가 있는 자료의 컬렉션'으로 다루지않고 일반 객체처럼 다룰 경우에는 이런 기법이 제대로 동작하지 않음

let fruits = []; // 빈 배열을 하나 만듭니다.
fruits[99999] = 5; // 배열의 길이보다 훨씬 큰 숫자를 사용해 프로퍼티를 만듭니다.
fruits.age = 25; // 임의의 이름을 사용해 프로퍼티를 만듭니다.

: 배열은 객체이므로 위처럼 프로퍼티를 추가해도 문제는 없지만 JS엔진이 배열을 일반 객체처럼 다루게 되어 배열을 다룰때 적용되는 최적화 기법이 동작하지 않아 배열 특유의 이점이 사라짐

잘못된 예

- arr.test = 5 와 같이 숫자가 아닌 값을 프로퍼티 키로 사용하는 경우

- arr[0], arr[1000] 만 추가하고 그 사이에 아무 요소가 없는 경우

- arr[1000], arr[999] 같이 역순으로 요소를 채우는 경우

 

배열은 순서가 있는 자료를 저장하는 용도의 특수한 자료구조로 배열 내장 메서드는 이런 용도에 맞게 만들어짐, JS엔진은 이런 특성을 고려하여 배열을 신중하게 조정, 처리하므로 배열을 사용할 땐 이런 목적에 맞게 사용하도록, 임의의 키를 사용해야 한다면 배열보단 일반 객체 { }가 적합한 자료구조일 확률이 높음

 

성능

push와 pop는 빠르지만 shift와 unshift는 느림

 

fruits.shift(); // 배열 맨 앞의 요소를 빼줍니다.

shift는 인덱스0의 요소를 제거할 뿐만 아니라 나머지 요소들을 이동시켜야함

1. 인덱스가 0인 요소 제거

2. 모든 요소들을 왼쪽으로 이동시킴, 인덱스 1->0, 2->1

3. length 프로퍼티 값 갱신

따라서 배열에 요소가 많으면 시간이 오래걸리고 메모리관련 연산이 많아짐

unshift도 동일 (인덱스0추가, 모든 요소들 우측으로 이동)

 

fruits.pop(); // 배열 끝 요소 하나를 제거합니다.

pop와 push는 요소를 옮기지 않고 기존 인덱스를 유지한채로 배열 끝에서 실행하는 메서드 이므로 실행속도가 빠름

 

반복문

for문은 배열을 순회할 때 쓰는 가장 오래된 방법으로 순회시에 인덱스를 사용

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

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

 

for..of 또한 배열을 순회하는 문법이나 현재 요소의 인덱스는 얻을 수 없고 값만 얻을 수 있음, for보다 권장

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

// 배열 요소를 대상으로 반복 작업을 수행합니다.
for (let fruit of fruits) {
  alert( fruit );
}

 

배열은 객체형이므로 for..in도 사용 가능하나 배열에서는 적합하지 않으므로 권장하지 않음

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

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

1. for..in 반복문은 모든 프로퍼티를 대상으로 순회하므로 키가 숫자가 아닌 프로퍼티도 순회 대상에 포함됨

  브라우저나 기타 호스트 환경에서 쓰이는 객체 중 배열과 유사한 형태를 보이는 '유사배열(array-like)' 객체는 배열처럼 length 프로퍼티, 요소당 인덱스가 있으며 배열과 달리 숫자형 키가 아닌 프로퍼티와 메서드가 있을 수 있음, 이런 유사배열객체와 for..in을 함께 사용하면 모든 것을 대상으로 순회하므로 '필요없는' 프로퍼티들이 문제를 발생시킬 수 있음

2. for..in 반복문은 배열이 아닌 객체와 함께 사용할 때 최적화 되어 있으므로 배열에 사용하게되면 10~100배정도 느려짐, for..in 반복문은 속도가 빠른편이라 병목 지점(전체 시스템이 하나의 구성요소에 의해 제한을 받는 현상)에서만 문제가 되긴하나 차이를 알고 적합하게 사용하기를 권장함

 

'length'프로퍼티

배열에 조작을 가하면 length프로퍼티가 자동으로 갱신됨, length프로퍼티는 배열 내 요소의 개수가 아닌 가장 큰 인덱스에 1을 더한 값, 배열요소의 인덱스가 아주 큰 정수라면 length프로퍼티도 같이 커짐

//잘못된 사용
let fruits = [];
fruits[123] = "사과";

alert( fruits.length ); // 124

 

쓰기(???)가 가능한 length 프로퍼티, 값을 수동으로 증가시키면 아무 일이 발생되지않지만 값을 감소시키면 배열이 잘리고 다시 되돌릴 수 없음 -> arr.length = 0을 사용해 배열을 비울 수 있음

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

arr.length = 2; // 요소 2개만 남기고 잘라봅시다.
alert( arr ); // [1, 2]

arr.length = 5; // 본래 길이로 되돌려 봅시다.
alert( arr[3] ); // undefined: 삭제된 기존 요소들이 복구되지 않습니다.

 

new Array()

배열 생성 가능하지만 대괄호[]를 사용하면 더 간편하게 배열생성이 가능하므로 잘 사용하진 않음

let arr = new Array("사과", "배", "기타");

숫자형 인수 하나를 넣어서 new Array를 호출하면 배열이 생성되는데 배열엔 요소가 없는 반면 길이는 인수와 같아지는 오류가 있음

let arr = new Array(2); // 이렇게 하면 배열 [2]가 만들어질까요?
alert( arr[0] ); // undefined가 출력됩니다. 요소가 하나도 없는 배열이 만들어졌네요.
alert( arr.length ); // 길이는 2입니다.

 

다차원 배열

배열이 배열의 요소가 되는 것, 다차원 배열(multidimensional array), 행렬을 저장하는 용도

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

alert( matrix[1][1] ); // 5, 중심에 있는 요소

 

toString

쉼표로 구분한 문자열 반환

let arr = [1, 2, 3];

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

 

배열에는 Symbol.toPrimitive나 valueOf 메서드가 없으므로 아래의 경우 문자열로 형변환이 발생해 []는 빈 문자열, [1]은 문자열 "1", [1,2]는 문자열 "1,2"로 변환

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

=

이항 덧셈 연산자 "+"는 피연산자 중 하나가 문자열인 경우 나머지 피연산자도 문자열로 변환

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

 

요약

배열은 특수한 형태의 객체로, 순서가 있는 자료를 저장하고 관리하는 용도에 최적화된 자료구조

- 선언방법

// 대괄호 (가장 많이 쓰이는 방법임)
let arr = [item1, item2...];

// new Array (잘 쓰이지 않음)
// 길이가 number인 배열이 만들어지나 요소는 비어있음 
let arr = new Array(item1, item2...);

- length프로퍼티는 배열의 길이를 나타냄, 숫자형 인덱스 중 가장 큰 값+1의 값, 배열 메서드는 length프로퍼티를 자동으로 조정함

- length값을 수동으로 줄이면 배열 끝이 잘림

 

다음 연산을 사용하여 데큐처럼 사용

- push(...items) : items를 배열 끝에 더함

- pop() : 배열 끝 요소를 제거하고 나머지 요소를 반환

- shift() : 배열 처음 요소를 제거하고 나머지 요소를 반환

- unshift(...items) : items를 배열 처음에 더함

 

모든 요소를 대상으로 반복 작업 가능

- for(let i=0; i<arr.length; i++) : 가장 빠른 방법이고 오래되 브라우저와도 호환가능

- for(let item of arr) : 배열 요소에만 사용되는 모던한 문법

- for(let i in arr) : 배열에 사용x

 

 

반응형