자바스크립트 완벽가이드 7장 (배열)

자바스크립트의 배열은 타입이 고정되어 있지 않다. 같은 배열에 있는 원소 값의 타입은 서로 다를 수 있다. 배열의 원소는 객체가 될 수도 있고, 또 다른 배열이 될 수도 있다. 자바스크립트 배열은 동적이다. 배열의 크기가 필요에 따라 커지거나 작아질 수 있다. 배열을 생성하거나, 크기가 변경되어 다시 할당을 할 때도 배열 크기를 선언할 필요가 없다. 자바스크립트 배열은 밀집도가 높지 않고, 각 원소의 인덱스가 연속적이지 않아도 되고, 원소들 사이에 빈자리가 있어도 된다. 자바스크립트 배열에는 length 프로퍼티가 존재한다.
자바스크립트 배열은 자바스크립트 객체의 특별한 형태이고, 배열의 인덱스는 프로퍼티 이름인데 정수인 것이다. 일반적으로 배열은 객체 프로퍼티를 통해 원소에 접근하는 것보다 정수 첨자를 통해 원소에 접근하는 것이 훨씬 빠르도록 최적화 되어 있다.
배열은 Array.prototype의 프로퍼티들을 상속받는다.

7.1 배열 만들기

배열을 만드는 가장 쉬운 방법은 배열 리터럴을 사용하는 것이다. 배열 리터럴은 대괄호([,])안에 배열의 원소를 쉼표(,)로 구분해 나열한 것이다.
만약 배열 리터럴은 객체 리터럴 또는 다른 배열 리터럴을 포함할 수 있다. 만약 배열 리터럴에서 빠진 부분이 있다면 해당 부분의 원소 값은 undefined가 된다.

1
var undefs = [,,];    // 두 원소 모두 값은 undefined (배열 리터럴의 문법은 마지막 원소 다음에 쉼표 추가 가능)

배열을 만드는 또 다른 방법은 Array() 생성자를 이용하는 것이다. 생성자는 3가지 방법으로 호출 가능하다.
인자 없이 호출
빈 배열을 생성하고 생성된 배열은 배열 리터럴 []과 동일하다.

1
var a = new Array();

배열의 길이를 의미하는 숫자를 인자로 주어 호출
배열에 저장될 원소의 크기를 알고, 미리 공간을 할당할 때 사용한다. 배열에는 어떠한 값도 저장되지 않고, 배열의 인덱스 프로퍼티 값(“0”, “1” …)도 존재하지 않는다.

1
var a = new Array(10);

두 개 이상의 원소, 또는 숫자가 아닌 원소 값 하나를 명시적으로 지정
생성자의 인자 값들이 배열의 원소가 된다. 리터럴을 사용하는편이 훨씬 더 간단하다.

1
var a = new Array(5, 4, 3, 2, 1, "test");

7.2 배열의 원소 읽고 쓰기

배열의 각 원소에 접근할 때에는 [] 연산자를 사용한다. 배열은 객체의 특별한 종류이다. 배열의 [] 구문은 객체 프로퍼티 접근 때 쓰는 []와 똑같이 동작한다. 자바스크립트는 사용자가 명시한 숫자 배열 인덱스를 문자열 형태로 바꿔서 프로퍼티 이름으로 사용한다.
배열의 인덱스와 객체 프로퍼티 이름을 올바르게 구별할 줄 알면 좋다. 모든 인덱스 값은 프로퍼티 이름이지만, 프로퍼티 이름은 0과 2의32승-1 사이의 정수여야만 인덱스가 될 수 있다. 모든 배열은 객체이므로, 어떤 이름의 프로퍼티라도 자유롭게 만들 수 있다. 하지만 배열에는, 프로퍼티 가운데 인덱스인 것들을 사용하면 length 프로퍼티의 값이 자동으로 갱신되는 특별한 기능도 갖추어져 있다.(배열이 일반 객체와 다른 점은 속성 이름으로 2의32승보다 작은 양수를 사용할 때, 자동으로 length 프로퍼티의 값을 바꾼다는 것이다.) 배열 첨자로 양의 정수가 담긴 문자열을 사용하면, 일반적으로 프로퍼티가 아닌 배열 인덱스로 쓰인다. (소수점 아래가 없는 부동 소수점 값도 마찬가지) 반면에 음수나, 정수 아닌 수들을 사용하면 숫자는 문자열로 변환되고, 변환된 문자열은 배열 객체의 프로퍼티 이름으로 사용된다.
객체에 존재하지 않는 프로퍼티 이름을 질의하면, 에러가 발생하지 않고 단순히 undefined값이 반환된다. 이러한 성질은 배열에도 적용된다.

1
2
3
a = [true, false];    // 두 개의 원소를 가진 배열을 생성한다.
a[2] // => undefined. 해당 인덱스에 원소가 없어서.
a[-1] // => undefined. '-1'이라는 속성 이름에 해당하는 값이 없어서.

모든 배열은 객체다. 따라서 배열은 객체의 프로토타입으로부터 원소들을 상속 받을 수 있다.

7.3 희소배열

희소배열은 배열에 속한 원소의 위치가 연속적이지 않은 배열을 말한다. 보통, 배열의 length 프로퍼티는 배열에 속한 원소의 개수를 의미한다. 그러나 희소배열의 경우, length 프로퍼티의 값은 원소의 개수보다 항상 크다.

1
2
3
a = new Array(5);      // 원소가 없는 배열이지만 a.length의 값은 5다.
a = [?]; // length 값이 0인 빈 배열을 생성한다.
a[1000] = 0; // 하나의 원소를 할당했지만, length 값은 1001이 된다.

희소배열은 보통배열보다 일반적으로 느리고, 메모리를 많이 사용할 뿐 아니라, 원소를 찾는데 걸리는 시간이 일반 객체의 속성 값을 찾는 시간만큼 오래 걸린다.
배열 리터럴 사용 시 값을 명시하지 않는 방법으로는 희소배열을 만들 수 없다. 해당 원소의 값이 undefined가 되기 때문이다. 이는 배열에 원소가 아예 존재하지 않는 것과는 다르다. in 연산자를 사용하면 두 경우의 차이점을 알 수 있다.

1
2
3
4
var a1 = [,,,];          // 세 개의 원소가 undefined인 배열
var a2 = new Array(3); // 원소가 존재하지 않는 배열
0 in a1 // => true: a1에는 0번 인덱스 위치에 원소가 존재한다.
0 in a2 // => false: a2에는 0번 인덱스 위치에 원소가 존재하지 않는다.

7.4 배열의 길이

1
2
3
4
a = [1,2,3,4,5];    // 다섯 개의 원소를 가진 배열
a.length = 3; // length를 3으로 바꿨기 때문에 결과는 [1,2,3]
a.length = 0; // length 값이 0이기 때문에 모든 element를 삭제, 결과는 []
a.length = 5; // length 값은 5이지만, 원소가 없다. new Array(5)와 같은 결과

7.5 배열에 원소를 추가하거나 삭제하기

배열에 원소를 추가하는 방법

  • 배열의 새 인덱스에 값을 할당한다.
  • push() 메서드를 사용해 배열의 끝에 원소를 추가한다. (a[a.length]에 값을 할당하는것과 같다.)
  • unshift() 메서드를 사용하면 배열의 앞쪽에 원소를 추가할 수 있다.

배열에 원소를 삭제하는 방법

  • delete 연산자로 배열의 원소를 삭제할 수 있다. (배열의 length는 줄어들지 않는다.)
  • pop() 메서드를 사용해 배열의 앞에서 원소를 삭제한다.

배열의 특정 원소를 지우는 것은, 해당 원소에 undefined 값을 할당하는 것과 의미가 비슷하다. 원소가 지워지더라도 생기는 공백을 다른 원소가 대신하지 않으며, 해당 배열은 희소배열이 된다.

7.6 배열 순회하기

ECMAScript 5에는 배열을 순회하는 다양한 메서드가 추가되었다. 사용자가 정의한 함수에 배열의 원소가 인덱스 순서대로 하나씩 넘어오도록 하여 배열을 순회하는 형태다. forEach() 메서드가 대표적이다.

1
2
3
4
5
var data = [1,2,3,4,5];
var sumOfSquares = 0;
data.forEach(function(x)) {
sumOfSquares += x*x;
});

배열을 다룰 때 forEach() 같은 순회 메서드는 간단하고 강력한 함수형 프로그래밍 스타일을 사용할 수 있게 한다.

7.7 다차원 배열

자바스크립트는 진정한 의미에서의 다차원 배열을 지원하지는 않는다. 그러나 배열의 배열을 사용해 다차원 배열을 흉내 낼 수 있다. 배열 내의 배열에 있는 원소에 접근하기 위해서는 단순히 [] 연산자를 두 번 사용하면 된다.

7.8 배열 메서드

7.8.1 join()

Array.join() 메서드는 배열의 모든 원소를 문자열로 변환하고, 변환한 문자들을 이어 붙인 결과를 반환한다. 결과로 반환되는 문자열에서 배열의 원소들을 구별하기 위해 구분자 문자열을 사용한다. 별도로 구분자 문자열을 지정하지 않으면 쉼표(,)가 기본 값으로 사용된다.

1
2
3
4
5
6
var a = [1, 2, 3];
a.join(); // => '1,2,3'
a.join(" "); // => '1 2 3'
a.join(""); // => '123'
var b = new Array(10); // 길이가 10인 빈 배열
b.join('-') // => '---------': 아홉 개의 하이픈 문자열

Array.join() 메서드는 String.split() 메서드와는 반대로 작동한다. String.split() 메서드는 문자열을 조각들로 분리하고, 이 조각들을 원소로 하는 배열을 생성한다.

7.8.2 reverse()

Array.reverse() 메서드는 배열의 원소 순서를 반대로 뒤집어 반환한다. 순서가 뒤바뀐 새로운 배열을 생성하는 것이 아니라, 이미 존재하는 배열 안에서 원소들의 순서를 뒤바꾼다.

7.8.3 sort()

Array.sort() 메서드는 배열 안의 원소들을 정렬하여 반환한다. sort() 메서드는 별도의 인자전달 없이 호출하면, 배열 안의 원소들을 알파벳순으로 정렬한다. 배열에 undefined 원소들이 존재하면, 이 원소들은 배열의 끝부분으로 정렬된다.
알파벳순이 아니라 다른 순서로 배열을 정려하려면, sort() 메서드의 전달 인자를 통해 비교 함수를 직접 명시해주어야 한다. 비교 함수는 전달인자를 두 개 받아서, 정렬된 배열에서 어떤 것이 먼저 나타나야 하는지 판단한다. 만약 첫 번 째 인자가 두 번째보다 먼저 나타나야 한다면, 비교 함수는 0보다 작은 숫자를 반환해야 한다. 만약 두 값이 동등하다면 0을 반환해야 한다.

1
2
3
4
5
6
var a = [33, 4, 1111, 222];
a.sort(); // 알파벳순: 1111, 222, 33, 4
a.sort(function(a, b) { // 번호순: 4, 33, 222, 1111
return a-b; // 0보다 작은 값, 0, 또는 0보다 큰 값을 반환한다.
});
a.sort(function(a,b) {return b-a}); // 내림차순 정렬

7.8.4 concat()

Array.concat() 메서드는 기존 배열의 모든 원소에 concat() 메서드의 전달인자들을 추가한 새로운 배열을 반환한다. 전달인자로 배열을 전달하면, 이 배열안의 원소들을 꺼내어 반환하는 배열에 추가한다. 하지만 중첩 배열일 경우에는 중첩된 배열의 원소까지는 꺼내지 않는다.

1
2
3
4
5
var a = [1,2,3];
a.concat(4, 5); // [1,2,3,4,5]
a.concat([4, 5]); // [1,2,3,4,5]
a.concat([4, 5], [6, 7]); // [1,2,3,4,5,6,7]
a.concat(4, [5, [6, 7]]); // [1,2,3,4,5, [6,7]]

7.8.5 slice()

Array.slice() 메서드는 부분 배열을 반환한다. 부분 배열은 배열에서 잘라낸 원소들을 담은 새 배열이다. slice() 메서드는 전달인자를 두 개 받는데, 각 인자는 반환될 부분의 처음을 명시한다. 반환되는 배열은 첫 번째 전달인자가 지정하는 위치부터 두 번째 전달인자가 지정하는 위치 이전까지의 모든 원소를 포함한다. 만약 전달인자가 하나라면 그 위치에서 배열 끝까지의 모든 원소를 포함하는 부분 배열을 반환한다. 만약 전달인자가 음수라면, 배열의 마지막 원소에서부터의 상대적인 위치를 가리키는 것이다.

1
2
3
4
5
var a = [1,2,3,4,5];
a.slice(0, 3); // [1,2,3]
a.slice(3); // [4,5]
a.slice(1, -1); // [2,3,4]
a.slice(-3, -2); // [3]

7.8.6 splice()

Array.splice() 메서드는 배열의 원소를 삽입하거나 원소를 제거하려 할 때 범용적으로 사용하는 메서드다. splice() 메서드는 slice()나 concat() 메서드와는 달리 호출 대상 배열을 바로 수정한다.
splice()의 첫 번째 전달인자는 배열상에서 삽입 혹은 삭제 작업을 시작할 위치를 지정하고, 두 번째 전달인자는 배열에서 삭제할 원소의 개수를 지정한다. 두 번째 전달인자를 지정하지 않으면 첫 번째 전달인자로 지정한 배열의 시작 위치에서 마지막 원소까지 전부 삭제한다. splice()는 삭제한 배열을 반환하며, 만약 삭제된 원소가 하나도 없다면 빈 배열을 반환한다.

1
2
3
4
var a = [1,2,3,4,5,6,7,8];
a.splice(4); // [5,6,7,8]을 반환, a는 이제 [1,2,3,4]
a.splice(1, 2); // [2, 3]을 반환, a는 이제 [1,4]
a.splice(1, 1); // [4]를 반환, a는 이제 [1]

세 번째 전달인자부터는 배열에 새롭게 삽입할 원소들을 지정하는데 사용한다. 삽입 작업은 첫 번째 전달인자로 지정된 시작 위치부터 수행한다.

1
2
3
var a = [1,2,3,4,5];
a.splice(2,0, 'a', 'b'); // []를 반환, a는 이제 [1,2,'a','b',3,4,5]
apsplice(2,2, [1,2], 3); // ['a', 'b']를 반환, a는 이제 [1,2,[1,2],3,3,4,5]

concat() 메서드와 달리 splice() 메서드는 전달인자로 배열이 전달되면, 그 배열의 원소들을 꺼내어 삽입하지 않고 배열 그 자체를 삽입한다.

7.8.7 push()와 pop()

push()와 pop() 메서드를 사용하면 배열을 마치 스택처럼 조작할 수 있다. (FILO (선입후출) 스택 구현 가능)
push() 메서드는 하나 이상의 원소들을 배열의 끝 부분에 이어 붙이고, 배열의 새로운 length 값을 반환한다
pop() 메서드는 배열의 마지막 원소를 제거하고 배열의 length 값을 감소시킨 후, 배열에서 제거한 원소를 반환한다.

7.8.8 unshift()와 shift()

push(), pop()과 매우 유사하게 동작하는데, 배열의 끝이 아니라 배열의 맨 앞에서 원소를 추가하고 제거한다.
unshift() 메서드는 하나 혹은 그 이상의 원소들을 배열의 맨 앞에 추가하고, 추가된 원소만큼 공간을 만들기 위해 기존 배열 원소들을 인덱스가 높은 방향으로 옮긴 후, 배열의 새로운 length 값을 반환한다.
shift() 메서드는 배열의 첫 번째 원소를 제거한 후, 배열에서 제거한 원소를 반환한다.

7.8.9 toString()

배열의 toString() 메서드는 배열의 모든 원소를 문자열로 변환하고 이 문자열들을 쉼표(,)로 분리한 목록을 반환한다. 별도의 전달인자를 지정하지 않고 join() 메서드를 호출하면 toString()과 동일한 결과를 얻을 수 있다.

1
2
3
[1,2,3].toString()             // '1,2,3'
["a", "b", "c"].toString() // 'a,b,c'
[1, [2, 'c']].toString() // '1,2,c'

7.9 ECMAScript 5 배열 메서드

ECMAScript 5는 배열을 순회, 매핑, 필터링, 테스팅, 감소, 검색하기위한 아홉 가지 새로운 메서드를 정의한다.
대부분의 메서드들은 첫 번째 전달인자로 함수를 받는다. 이 함수는 배열의 각 원소마다 한 번씩 실행하거나 일부 원소들에 한해 실행된다. 만약 배열이 희소배열이라면, 빈 원소의 경우 함수를 호출하지 않는다. 대부분, 첫 번째 전달인자로 지정한 함수는 세 개의 전달인자를 갖고 호출되는데, 배열 원소의 값과 인덱스, 마지막으로 배열 그 자체다. 첫 인자로 함수를 받는 대부분의 ECMAScript 5의 배열 메서드들은 생략 가능한 두 번째 인자를 받는다. 두 번째 전달인자를 지정하면, 첫 번째 전달인자인 함수는 마치 두 번째 인자의 메서드인 것처럼 호출된다. 두 번째 인자는 첫 번째 전달인자인 함수 안에서 this 키워드의 값으로 사용된다. ECMAScript 5 배열 메서드는 호출 대상 배열을 수정하지 않는다. 메서드의 전달인자로 쓰인 함수 안에서는 배열을 수정할 수 있다.

7.9.1 forEach()

forEach() 메서드는 배열을 순회하는 메서드이다. 첫 번째 인자로 넘긴 함수를 각각의 원소를 대사응로 호출한다. forEach()는 첫 인자로 전달된 함수를 호출할 때 세가지 인자를 넘긴다. 각 인자는 배열의 원소 값과, 원소의 인덱스 값, 그리고 배열 그 자체다.
forEach() 메서드는 배열의 모든 원소가 순회되기 전에는 종료도지 않는다. 루프에서 사용하는 break문은 사용할 수 없다. 루프를 중간에 종료시키려면, 예외를 발생시켜야 하고, forEach()는 try 블록 안에서 호출되어야 한다.

7.9.2 map()

map() 메서드는 배열의 각 원소를 메서드의 첫 번째 전달인자로 지정한 함수에 전달하고, 해당 함수의 반환 값을 배열에 담아 반환한다.
map() 메서드에 전달한 함수는 forEach()에 전달한 함수와 동일한 형태로 호출되지만 map() 메서드에 인자로 전달된 함수는 반드시 값을 반환해야 한다. map() 메서드는 기존의 배열을 수정하지 않고, 새배열을 반환한다.

1
2
a = [1, 2, 3];
b = a.map(function(x) { return x*x; }); // b는 [1, 4, 9]

7.9.3 filter()

filter() 메서드는 배열의 일부분을 반환한다. 이 메서드에 전달하는 함수는 조건자 함수(항상 true 또는 false 값을 반환하는 함수)여야 한다. filter()의 조건 함수는 forEach()와 map() 메서드와 동일한 형태로 호출된다. 반환값이 true이거나 true로 변환되는 값이면 조건자 함수에 전달된 값은 filter가 반환할 배열에 추가된다.

1
2
3
a = [5, 4, 3, 2, 1];
smallvalues = a.filter(function(x) { return x > 3 }); // [2, 1]
everyother = a.filter(function(x, i) { return i%2==0 }); // [5, 3, 1]

7.9.4 every()와 some()

every()some() 메서드는 배열 조건자 함수다. 두 메서드는 인자로 주어진 조건자 함수를 배열에 적용하여, 결과로 true나 false를 반환한다.
every() 메서드는 전달인자로 넘긴 함수가 배열의 모든 원소에 대하여 true를 반환하는 경우, every() 메서드는 true를 반환한다.
some() 메서드는 전달인자로 넘긴 함수가 배열의 일부 원소에 대해 true를 반환하는 경우, some() 메서드는 true를 반환한다.
every()와 some() 메서드는 반환 값이 결정되면 배열의 원소 순회를 중단한다.

7.9.5 reduce()와 reduceRight()

reduce()reduceRight() 메서드는 인자로 주어진 함수를 사용하여 배열의 원소들을 하나의 값으로 결합한다.
reduce() 메서드는 두 개의 인자를 갖는다. 첫 번째 인자는 배열 원소의 감소 작업을 하는 함수다. 이 감소 함수는 배열 원소 중 두 값을 하나로 결합하면서 크기를 줄이고, 마지막 남은 값을 반환한다. 두 번째 인자는 감소 함수에 전달할 시작 값이다.
reduce()에 사용되는 함수는 forEach()와 map()과는 조금 다르다. reduce()에서 사용하는 함수의 첫 번째 인자는 함수를 사용해 계산된 값의 누적된 결과다. 그 초기 값은 reduce()의 두 번째 인자로 전달한 값이다. 이후의 호출에서는 전 단계 함수 호출에서 반환된 값을 함수의 첫 번째 인자로 사용한다.
reduceRight() 메서드는 reduce()와 동작은 같지만, 배열의 끝부터 시작해 반대 방향으로 처리한다. 감소 함수의 피연사자들 중 오른쪽 피연산자의 우선순위가 높다면, reduceRight()를 사용해야 한다. reduce()와 reduceRight() 메서드는 감소 함수 호출 시 사용할 this 값을 선택인자로 지정할 수 없다. 선택 초기 값 인자만 지정할 수 있다. 만약 감소 함수를 특정 object의 메서드로 호출하고 싶다면, Function.bind() 메서드를 사용해야 한다.

7.9.6 indexOf()와 lastIndexOf()

indexOf()lastIndexOf() 메서드는 배열의 원소 중에서 특정한 값을 찾는다. 값이 존재하면 해당 값의 인덱스를 반환하고, 존재하지 않을 경우에는 -1을 반환한다. indexOf()는 배열의 처음부터 검색하고, lastIndexOf()는 배열의 끝에서부터 검색한다. indexOf()와 lastIndexOf()는 함수를 인자로 받지 않고, 첫 번째 인자에서 배열에서 찾고자 하는 값, 두 번째 인자에서 검색을 시작할 배열 인덱스를 지정할 수 있다.(생략가능)

7.10 배열 타입

ECMAScript 5에서는 Array.isArray()라는 함수를 통해 특정 객체가 배열인지 판단할 수 있다.

1
2
Array.isArray([?])      // => true
Array.isArray({?}) // => false

7.11 유사 배열 객체

length 프로퍼티와 양의 정수 이름의 프로퍼티가 있는 객체는 일종의 배열로 취급할 수 있다. 이를 유사 배열 객체라고 한다.
자바스크립트 배열 메서드는 배열뿐 아니라 유사 배열 객체에도 적용이 가능 하도록 범용 메서드로 구현되었다. 유사 배열은 Array.prototype을 상속받지 않기 때문에, 배열 메서드를 해당 객체의 메서드로 호출할 수는 없지만 Function.call 메서드를 통해서 간접적으로 호출할 수 있다.

1
2
var a = {"0":"a", "1":"b", "2":"c", length:3};     // 유사 배열 객체
Array.prototype.join(a, "+") // => 'a+b+c'

7.12 문자열을 배열처럼 사용하기

문자열은 읽기 전용 배열처럼 동작한다. 문자열의 각 문자는 chatAt() 메서드로 접근할 수도 있지만 대괄호 []를 사용해 접근할 수도 있다. 문자열을 인덱스로 접근함으로써 얻을 수 있는 가장 큰 장점은 charAt() 메서드 호출을 단순하게 []로 대체 함으로써 코드가 전보다 간결해지고, 가독성이 높아지는 것이다.
문자열은 변하지 않는 값이라서, 읽기 전용 배열로만 다룰 수 있다. push(), sort(), reverse(), splice()와 같은 배열 메서드는 배열을 직접 수정하므로 문자열에서는 작동하지 않는다.

출처 : “JavaScript: The Definitive Guide, by David Flanagan (O’Reilly). Copyright 2011 David Flanagan, 978-0-596-80552-4”

Author

KimJongMin

Posted on

2017-01-28

Updated on

2021-03-22

Licensed under

댓글