배열은 여러 개의 값을 순차적으로 나열한 자료구조다. []안에 값을 넣음으로써 배열을 선언할 수 잇다. 이때 배열이 가지고 있는 값을 요소라고 한다. 또한 요소들은 자신의 위치를 나타내는 0 이상의 정수인 index를 갖는다. index는 0부터 시작한다.
const arr = ['a', 'b', 'c', 'd'];
arr = ['a', 'b', 'c', 'd']
arr[0] = 'a'
arr[1] = 'b'
arr[2] = 'c'
arr[3] = 'd'
Array의 property중 length는 요소의 갯수, 즉 배열의 길이를 나타낸다.
arr.length
4
length를 이용하여 for문을 작동시킬수도있다.
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 'a', 'b', 'c', 'd'
}
Array는 타입이 존재하지 않는다. 기본값은 객체 타입이다. 그러나 일반객체와는 확연이 틀린 특징들이 있다.
대표적으로 값의 순서와 length 프로퍼티를 예로 들 수 있다. 배열은 값의 순서가 존재하기에 for문에 length 프로퍼티를 통해 작동시킬수있지만 객체는 length 프로퍼티를 가지고있지 않을뿐더러 값의 순서역시 존재하지않기때문에 큰 차이점이라고 볼 수 있다.
일반적인 자료구조에서 말하는 배열은 동일한 크기의 메모리공간이 빈틈없이 연속적으로 나열된 자료구조를 말한다. 배열의 요소들이 하나의 데이터로 통일되어 있으며 서로 연속적으로 인접해있는데 이러한 배열을 밀집배열(dense array)이라고한다.
밀집 배열의 경우 위의 특성을 이용하여 단 한번의 연산식을 통하여 임의의 요소에 접근할 수 있다.
임의 접근, 시간복잡도 O(1) : 검색 대상 요소의 메모리 주소 = 배열의 시작 메모리 주소 + 인덱스 X 요소의 바이트 수
예를들어 배열의 시작 메모리주소가 1000이고 이곳에 밀집배열 [1, 2, 3] 이 있다고 가정할때 이들의 메모리 주소는 아래와 같다.
배열요소 1 : 1000(시작 메모리 주소) + 0(요소의 인덱스) X 8 (8bit = 1byte, 정수는 1바이트이다.) = 1000
배열요소 2 : 1000 + 1 X 8 = 1008
배열요소 3 : 1000 + 2 X 8 = 1016
하지만 정렬되지 않은 배열의 경우 특정한 요소를 검색해야 하는 경우 다른 연산식을 통해 접근해야한다.
선형검색, 시간복잡도O(n) : 예제 27-08 (495p)
function search(array, target) {
const length = array. length;
for (let i = 0; i < length; i++) {
if (array[i] === target) return i;
}
return -1;
}
console.log(search([1,2,3,4,5,6], 3));
2
console.log(search([1,2,3,4,5,6], 0));
-1
배열을 받아와 전부 순회하며 target의 위치를 찾으면 해당 위치의 인덱스 값을 반환한다. 없을경우 -1을 반환하는 형식이다. 이와같이 연속적으로 이어져있지 않은 배열인데 이를 희소 배열이라한다.
이처럼 자바스크립트의 배열은 일반적인 의미의 배열이 아닌 배열의 동작을 흉내 낸 특수한 객체이다.
위에서 말한것처럼 배열은 배열이라는 타입이 아닌 객체 타입을 갖는다했는데, 아래 예제를 통해 더 자세히 알 수 있다.
배열을 오브젝트의 프로퍼티 getOwnPropertyDescriptors로 찍어보면 위와같이 우리가 인덱스 값이라고 생각했던것은 사실 배열의 프로퍼티 key이고, length프로퍼티를 갖는 특수한 객체라는 사실을 알 수 있다.
그렇다고 자바스크립트의 배열이 꼭 안좋다는것은 아닌데, 배열의 특정 인덱스를 찝어서 찾아낼 경우 일반 배열보다 훨신 빠르다. 이는 해당 인덱스를 호출하면 되기때문이다. 그리고 삽입과 삭제의 과정에서 또한 일반 배열보다 빠르다.
length프로퍼티는 요소의 개수, 즉 배열의 길이를 나타내는 0 이상의 정수를 값으로 갖는다 하였는데, length 프로퍼티 값을 length보다 작은 임의의 숫자로 할당시킬경우 배열의 길이가 줄어들게된다. 하지만 length보다 큰 값을 넣을경우 프로퍼티 값은 늘어나지만 실제 배열의 길이가 늘어나진않는다.
위 사진과 같이 length프로퍼티의 value값이 변한거지 실제로 배열이 늘어난것은 아니라는걸 확인할 수 있다.
희소배열은 연속적으로 이어져 있지 않은 배열이라 하였는데 위 사진의 arr처럼 요소가 부분부분 비어있는 배열 역시 희소 배열이라 할 수 있다. 그렇기에 희소 배열은 최대한 사용하지않는것이 좋다.
배열은 다양한 방식으로 생성할 수 있는데 가장 일반적인방식은 배열 리터럴을 사용하는것이다.
변수 안에 배열을 할당함으로써 배열을 선언한다.
const arr = [1,2,3];
console.log(arr) // 1, 2, 3
console.log(arr.length) // 3
배열의 length값은 요소의 개수를 의미하나 요소가 없을경우 length의 값은 0으로 할당된다.
const zeroarr = [];
console.log(zeroarr.length); //0
배열의 요소를 생략하면 희소 배열이 생성된다.
const skiparr = [1, , 3];
console.log(skiparr); // 1, empty, 3
배열 또한 생성자 함수를 통해 생성할 수 있다. 단, 생성자 함수는 전달된 인수의 개수에 따라 다르게 동작하므로 주의가 필요하다.
const arr = new Array(3);
console.log(arr); // empty * 3
console.log(arr.length); // 3
생성자 함수는 전달된 인수가 없을경우 빈 배열을 생성한다.
Array.of는 ES6에서 도입된 메서드로 전달된 인수를 요소로 갖는 배열을 생성한다.
const arr = Array.of(1,3,5,6);
console.log(arr); // 1,3,5,6
Array.from 역시 ES6에서 도입된 메서드이다. 이 메서드는 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환한다.
배열의 요소를 참조할 때 에는 대괄호 표기법 [] 을 사용한다. 그리고 배열을 참조 할 때 대괄호 안에는 인덱스값이 와야한다. 또한 존재하지 않는 요소에 접근하면 undefined가 출력된다. 이는 희소 배열일 경우에도 마찬가지이다.
const arr = [1,2,3];
arr[0]; // 1
arr[2]; // 3
arr[3]; // undefined
배열에도 요소를 추가 할 수 있다. 다만 현재 length의 값보다 큰 값을 넣을경우 빈 공간은 empty가 들어가게됨으로써 희소 배열이 된다.
let arr = [1,2,3];
arr[4] = 5;
console.log(arr) // [1,2,3,empty,5]
갱신 할 때에도 마찬가지로 인덱스값에 갱신하고싶은 값을 넣어야한다. 단, 인덱스는 0 이상의 정수인데 다른값, 혹은 음수를 넣을경우 프로퍼티로 생성되어버린다. 그리고 이때 추가한 프로퍼티는 length의 길이에 영향을 주지 않는다.
앞서 배열은 객체타입이라하였는데 그렇기에 delete연산자를 사용할 수 있다. 단, 이 경우 배열의 요소가 삭제되는것이지 length에 영향을 주지 않는다. 즉 delete연산자를 이용해 제거하면 해당 배열은 희소배열이 된다.
const arr = [1,2,3]
delete arr[1]; // true
console.log(arr); // [1, empty, 3]
console.log(arr.length); // 3
배열 메서드는 결과물을 반환하는 패턴이 두가지로 존재하기에 주의가 필요하다.
이렇게 두가지로 나눌 수 있다.
// push메서드의 경우 1번케이스, 원본 배열을 직접 변경하는 메서드이다.
const arr = [1];
arr.push(2);
console.log(arr); // [1,2]
// concat메서드의 경우 2번케이스, 원본 배열을 직접 변경하지않고 새로운 배열을 생성하여 반환하는 메서드이다.
const result = arr.concat(3);
console.log(arr); // [1,2]
console.log(result); // [1,2,3]
Array.isArray는 Array생성자 함수의 정적 메서드이다. 전달된 인수가 배열이면 true, 아니면 false를 참조한다.
Array.isArray([]); // true
Array.isArray([1, 2]); // true
Array.isArray(new Array()); // true
Array.isArray(); // false
Array.isArray({}); // false
Array.isArray(null); // false
Array.isArray(undefined); // false
indexOf메서드는 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환한다. 요소가 없을경우 -1로 반환한다.
const arr = [1,2,3];
arr.indexOf(2); // 1
arr.indexOf(3); // 2
arr.indexOf(4); // -1
push메서드는 인수로 전달받은값을 원본배열의 맨 마지막요소로 추가하고 length프로퍼티 값을 반환한다. 이때 주의해야할것은 위에서 말했던것처럼 push는 원본을 건들기 때문에 조심해야한다.
const arr = [1,2,3];
let result = arr.push(4,5,6);
console.log(arr); // [1,2,3,4,5,6]
console.log(result); // 6. length값을 반환받은것이다.
pop 메서드는 원본배열에서 마지막요소를 제거하고 제거한 요소를 반환한다. pop 역시 원본을 건들기 때문에 조심해야한다.
const arr = [1,2,3];
let result = arr.pop();
console.log(arr); // [1,2]
console.log(arr); // 3. 제거한 요소를 반환받았다.
unshift 메서드는 인수로 전달받은 모든 값을 원본 배열의 선두에 요소로 추가하고 length프로퍼티값을 반환한다. unshift역시 원본 배열을 직접 변경한다.
const arr = [1,2,3];
let result = arr.unshift(0);
console.log(result); // 4. lnegth프로퍼티값을 반환받았다.
console.log(arr); // [0,1,2,3]