Study JavaScript 0506 - 1hr 배열

변승훈·2022년 5월 12일
0

Study JavaScript

목록 보기
17/43

배열

배열 선언

다음과 같은 두 가지 방법으로 빈 배열을 만들 수 있다.

    let arr = new Array();
    let Arr = [];

보통은 두 번째 방법으로 배열을 선언하며 대괄호 안에 초기 요소를 넣어주는 것이 가능하다.

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

각 배열 요소엔 0부터 시작하는(zero-based) index(숫자)가 매겨져 있다. 이 숫자들은 배열 내의 순서를 나타낸다.
배열 내 특정 요소를 얻고 싶다면 대괄호 안에 index를 넣어주면 된다.

    let fruits = ["사과", "오렌지", "자두"];
    console.log(fruits[0]);  // 사과
    console.log(fruits[1]);  // 오렌지
    console.log(fruits[2]);  // 자두

위의 index를 이용하여 요소를 수정할 수도 있다.
let fruits = ["사과", "오렌지", "자두"];

    fruits[2] = '배'; 
    console.log(fruits)  ["사과", "오렌지", "배"]

새로운 요소를 배열에 추가하는 것도 가능하다.

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

    fruits[3] = '포도'; 
    console.log(fruits);    ["사과", "오렌지", "배", "포도"]

length를 사용하여 배열에 담긴 요소가 몇 개인지 알아낼 수 있다.

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

    console.log(fruits.length);

console.log()를 사용해 요소 전체를 출력하는 것도 가능하다.

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

    console.log(fruits);  사과, 오렌지, 자두

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

	// 요소에 여러 가지 자료형이 섞여 있다.
    let arr = ['사과', {name: 'Hun', true, function() { console.log('Hi'); }}];
    
	// index가 1인 요소(객체)의 name 프로퍼티를 출력
    console.log(arr[1].name);

	// index가 3인 요소(함수)를 실행
    arr[3]();  Hi

pop·push와 shift·unshift

javascript에서는 배열을 사용하면 큐와 스택을 둘 다 만들 수 있다.

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

그렇다면 큐와 스택이 무엇인지 지금부터 알아보자.

  1. 큐(queue): 배열을 사용해 만들 수 있는 대표적인 자료구조로, 배열과 마찬가지로 순서가 있는 컬렉션을 저장하는 데 사용한다.
    큐에서 사용하는 주요 연산은 아래와 같다.
  • push: 맨 끝에 요소를 추가한다.
  • shift: 제일 앞 요소를 꺼내 제거한 후 남아있는 요소들을 앞으로 밀어준다. 이렇게 하면 두 번째 요소가 첫 번째 요소가 된다.

선입선출(First-In-First-Out, FIFO): 큐를 사용하면 먼저 집어넣은 요소가 먼저 나온다.

  1. 배열: 큐 이외에 스택(stack)이라 불리는 자료 구조를 구현할 때에도 쓰인다.
    스택에서 사용하는 연산은 아래와 같다.
  • push: 요소를 스택 끝에 집어넣는다.
  • pop: 스택 끝의 요소를 추출한다.
    스택은 이처럼 '한쪽 끝'에 요소를 더하거나 뺄 수 있게 해주는 자료구조이다.

후입선출(Last-In-First-Out, LIFO): 스택을 사용하면 가장 나중에 집어넣은 요소가 먼저 나온다.

배열엔 두 연산을 가능하게 해주는 내장 메소드 pushpop이 있다.
화면에 순차적으로 띄울 메시지를 비축해 놓을 자료 구조를 만들 때 큐를 사용하는 것 처럼 큐는 실무에서 자주 쓰이는 자료구조이다.

기본 원리에 대해 알아 보았으니 이제부터 메소드를 알아보자!

아래는 배열의 끝에 무엇인가를 해주는 메소드이다.
pop(): 배열 끝 요소를 제거하고, 제거한 요소를 반환한다. 배열의 원본이 바뀌게 된다.

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

    console.log( fruits.pop() );  // 배열에서 "배"를 제거하고 제거된 요소를 띄운다.

    console.log( fruits );  // 사과, 오렌지 / 배열의 원본이 바뀌게 된다.

push(): 배열 끝에 요소를 추가한다. 배열의 원본이 바뀌게 된다.

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

    fruits.push("배");

    console.log( fruits );  사과,오렌지,// fruits.push(...)를 호출하는 것은 
	// fruits[fruits.length] = ...하는 것과 같은 효과를 보인다.

다음은 배열의 앞에 무언가를 해주는 메소드이다.
shift(): 배열 앞 요소를 제거하고, 제거한 요소를 반환한다. 배열의 원본이 가뀌게 된다.

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

    console.log( fruits.shift() );  // 배열에서 "사과"를 제거하고 제거된 요소를 띄운다.

    console.log( fruits );  // 오렌지,배

unshift(): 배열 앞에 요소를 추가한다. 배열의 원본이 바뀌게 된다.

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

    fruits.unshift('사과');

    console.log( fruits );  // 사과,오렌지,배

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

    let fruits = ["사과"];

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

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

배열의 내부 동작 원리

배열은 특별한 종류의 객체이다. 배열의 요소를 대괄호를 사용해 접근하는 방식은 객체문법에서 왔다.
다만 배열은 키가 숫자라는 점만 다르다.

숫자형 키를 사용함으로써 배열은 객체 기본 기능 이외에도 순서가 있는 컬렉션을 제어하게 해주는 특별한 메소드를 제공한다.
length라는 프로퍼티를 제공하는 것 처럼 말이다. 아무튼 본질은 객체이다.

배열은 javascript의 7개의 원시 자료형에 해당하지 않고, 원시 자료형이 아닌 객체형에 속하기 때문에 객체처럼 동작한다.

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

배열을 배열답게 만들어주는 것은 특수 내부 표현방식이다. javscript 엔진은 아래쪽 그림에서처럼 배열의 요소를 인접한 메모리 공간에 차례로 저장해 연산 속도를 높인다. 이 방법 이외에도 배열 관련 연산을 더 빠르게 해주는 최적화 기법은 다양하다.

그런데 개발자가 배열을 '순서가 있는 자료의 컬렉션’처럼 다루지 않고 일반 객체처럼 다루면 이런 기법들이 제대로 동작하지 않는다.

javscript 엔진이 배열을 일반 객체처럼 다루게 되면 배열을 다룰 때만 적용되는 최적화 기법이 동작하지 않아 배열 특유의 이점이 사라진다.
잘못된 예시는 다음과 같다.

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

★배열은 순서가 있는 자료를 저장하는 용도로 만들어진 특수한 자료구조이다. 배열 내장 메서드들은 이런 용도에 맞게 만들어졌다. 자바스크립트 엔진은 이런 특성을 고려하여 배열을 신중하게 조정하고, 처리하므로 배열을 사용할 땐 이런 목적에 맞게 사용해야 한다.

임의의 키를 사용해야 한다면 배열보단 일반 객체 {}가 적합한 자료구조일 확률이 높다.

성능

pushpop은 빠르지만 shiftunshift는 느리다!
예시를 통하여 실행 흐름을 알아보자!

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

shift 메서드를 호출한 것과 동일한 효과를 보려면 인덱스가 0인 요소를 제거하는 것만으론 충분하지 않다. 제거 대상이 아닌 나머지 요소들의 인덱스를 수정해 줘야 한다.

shift 연산은 아래 3가지 동작을 모두 수행해야 이뤄진다.

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

그런데 배열에 요소가 많다면 요소가 이동하는데 시간이 오래 걸리고 메모리 관련 연산도 많아진다. unshift도 이와 유사한 일이 벌어진다.
하지만 pushpop은 요소 이동을 수반하지 않고 배열 끝의 마지막 요소를 대상으로만 동작하면서 length의 프로퍼티의 값을 줄져주기만 하면 되므로 실행 속도가 빠르다.

반복문

  • for(시작조건, 종료조건, 증감조건): 인덱스를 조건으로 사용한다.
    let arr = ["사과", "오렌지", "배"];

    for (let i = 0; i < arr.length; i++) {
      console.log( arr[i] );
    }
  • for..of: 현재요소의 인덱스는 얻을 수 없고 값만 얻을 수 있다.
    let fruits = ["사과", "오렌지", "자두"];

    // 배열 요소를 대상으로 반복 작업을 수행
    for (let fruit of fruits) {
      console.log( fruit );
    }
  • for..in: 모든 프로퍼티를 대상으로 순회하여 필요없는 프로퍼티들이 문제를 일으킬 수 있다. 또한 느리기 때문에 되도록 쓰지 말자.
    let arr = ["사과", "오렌지", "배"];

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

'length' 프로퍼티

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

따라서 배열에 요소가 하나 있고, 이 요소의 index가 큰 숫자면 배열의 length 프로퍼티도 커진다.

length 프로퍼티의 독특한 특징 중 하나는 쓰기가 가능하다는 점이다.
length의 값을 수동으로 증가시키면 아무 일도 일어나지 않는다. 그런데 값을 감소시키면 배열이 잘린다. 짧아진 배열은 다시 되돌릴 수 없다.

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

    arr.length = 2; // 요소 2개만 남기고 잘라보자
    console.log( arr );  [1, 2]

    arr.length = 5; // 본래 길이로 되돌려 보자
    console.log( arr[3] );  // undefined: 삭제된 기존 요소들이 복구되지 않는다!

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

new Array()

new Array() 문법을 사용해 배열을 만들 수 있다.

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

대괄호 []를 사용하면 더 짧은 문법으로 배열을 만들 수 있기 때문에 new Array()는 잘 사용되지 않는 편이다.
new Array()엔 다루기 까다로운 기능도 있어서 더욱더 그렇다.
숫자형 인수 하나를 넣어서 new Array를 호출하면 배열이 만들어지는데, 이 배열엔 요소가 없는 반면 길이는 인수와 같아진다.

    let arr = new Array(2);  // 이렇게 하면 배열 [2]가 만들어질까?

    console.log( arr[0] ); // undefined, 요소가 하나도 없는 배열이 만들어졌다.

    console.log( arr.length ); // 길이는 2.

위 예시에서 확인해 본 것처럼 new Array(number)를 이용해 만든 배열의 요소는 모두 undefined 이다.

이런 뜻밖의 상황을 마주치지 않기 위해 new Array의 기능을 잘 알지 않는 한 대부분의 개발자가 대괄호를 써서 배열을 만든다.

다차원 배열

배열 역시 배열의 요소가 될 수 있다. 이런 배열을 다차원배열이라 한다.
다차원 배열은 행렬을 저장하는 용도로 쓰인다.

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

    console.log( matrix[1][1] );  5, 중심에 있는 요소

toString

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

    let arr = [1, 2, 3];

    console.log( arr );  1,2,3
    console.log( String(arr) === '1,2,3' );  true

아래의 예시를 보자

   console.log( [] + 1 );  "1"
   console.log( [1] + 1 );  "11"
   console.log( [1,2] + 1 );  "1,21"

배열엔 Symbol.toPrimitive나 valueOf 메서드가 없다.
따라서 위 예시에선 문자열로의 형 변환이 일어나 []는 빈 문자열, [1]은 문자열 "1", [1,2]는 문자열 "1,2"로 변환된다.

이항 덧셈 연산자 "+"는 피연산자 중 하나가 문자열인 경우 나머지 피연산자도 문자열로 변환한다. 따라서 위 예시는 아래 예시와 동일하게 동작한다.

    console.log( "" + 1 );  "1"
    console.log( "1" + 1 );  "11"
    console.log( "1,2" + 1 );  "1,21"
profile
잘 할 수 있는 개발자가 되기 위하여

0개의 댓글