배열은 다양한 메서드를 제공한다.
배열에서 요소의 일부를 지우고 추가할 때 사용하는 만능 메서드이다.
배열 역시 객체형에 속하므로 프로퍼티를 지울 때 쓰는 연산자 delete를 사용해 볼 수 있다.
let arr = ["I", "go", "home"];
delete arr[1]; // "go"를 삭제
alert( arr[1] ); // undefined
// delete를 써서 요소를 지우고 난 후 배열 --> arr = ["I", , "home"];
alert( arr.length ); // 3
delete obj.key는 key를 이용해 해당 키에 상응하는 값을 지우기 때문에 delete 메서드는 제 역할을 다 한 것이다.
arr.splice(start)는 요소를 자유자재로 다룰 수 있게 해준다. 이 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능하다
문법은 다음과 같다.
arr.splice(index[, deleteCount, elem1, ..., elemN])
@PARAM 1 : 조작을 가할 첫 번째 요소를 가리키는 인덱스(index)
@PARAM 2 : deleteCount로, 제거하고자 하는 요소의 개수
@LEFT : elem1, ..., elemN은 배열에 추가할 요소
let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // 인덱스 1부터 요소 한 개를 제거
alert( arr ); // ["I", "JavaScript"]
let arr = ["I", "study", "JavaScript", "right", "now"];
// 처음(0) 세 개(3)의 요소를 지우고, 이 자리를 다른 요소로 대체합니다.
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // now ["Let's", "dance", "right", "now"]
splice는 삭제된 요소로 구성된 배열을 반환한다.
음수 인덱스도 사용할 수 있다.
slice 메서드 뿐만 아니라 배열 관련 메서드엔 음수 인덱스를 사용할 수 있다. 이때 마이너스 부호 앞의 숫자는 배열 끝에서부터 센 요소 위치를 나타낸다.
let arr = [1, 2, 5];
// 인덱스 -1부터 (배열 끝에서부터 첫 번째 요소)
// 0개의 요소를 삭제하고
// 3과 4를 추가합니다.
arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5
arr.slice는 arr.splice와 유사해 보이지만 훨씬 간단하다.
arr.slice([start], [end])
이 메서드는 "start" 인덱스부터 ("end"를 제외한) "end"인덱스까지의 요소를 복사한 새로운 배열을 반환한다.
start와 end는 둘 다 음수일 수 있는데 이땐, 배열 끝에서부터의 요소 개수를 의미한다.
arr.slice는 문자열 메서드인 str.slice와 유사하게 동작하는데 arr.slice는 서브 문자열(substring) 대신 서브 배열(subarray)을 반환한다는 점이 다르다.
let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s (인덱스가 1인 요소부터 인덱스가 3인 요소까지를 복사(인덱스가 3인 요소는 제외))
alert( arr.slice(-2) ); // s,t (인덱스가 -2인 요소부터 제일 끝 요소까지를 복사)
arr.slice()는 인수를 하나도 넘기지 않고 호출하여 arr의 복사본을 만들 수 있다. 이런 방식은 기존의 배열을 건드리지 않으면서 배열을 조작해 새로운 배열을 만들고자 할 때 자주 사용된다.
arr.concat은 기존 배열의 요소를 사용해 새로운 배열을 만들거나 기존 배열에 요소를 추가하고자 할 때 사용할 수 있다.
arr.concat(arg1, arg2...)
인수엔 배열이나 값이 올 수 있는데, 인수 개수엔 제한이 없다.
메서드를 호출하면 arr에 속한 모든 요소와 arg1, arg2 등에 속한 모든 요소를 한데 모은 새로운 배열이 반환된다.
let arr = [1, 2];
// arr의 요소 모두와 [3,4]의 요소 모두를 한데 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4]) ); // 1,2,3,4
// arr의 요소 모두와 [3,4]의 요소 모두, [5,6]의 요소 모두를 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
// arr의 요소 모두와 [3,4]의 요소 모두, 5와 6을 한데 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
concat 메서드는 객체가 인자로 넘어오면 (배열처럼 보이는 유사 배열 객체이더라도) 객체는 분해되지 않고 통으로 복사되어 더해진다.
하지만 인자로 받은 유사 배열 객체에 특수한 프로퍼티 Symbol.isConcatSpreadable
이 있으면 concat은 이 객체를 배열처럼 취급합니다. 따라서 객체 전체가 아닌 객체 프로퍼티의 값이 더해진다.
let arr = [1, 2];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else
arr.forEach는 주어진 함수를 배열 요소 각각에 대해 실행할 수 있게 해준다.
arr.forEach(function(item, index, array) {
// 요소에 무언가를 할 수 있습니다.
});
// for each element call alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
아래는 인덱스 정보까지 더해서 출력해주는 좀 더 정교한 코드입니다.
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
참고로, 인수로 넘겨준 함수의 반환값은 무시된다.
배열 내에서 무언가를 찾고 싶을 때 쓰는 메서드에 대해 알아봅시다.
arr.indexOf와 arr.lastIndexOf, arr.includes는 같은 이름을 가진 문자열 메서드와 문법이 동일하다. 물론 하는 일도 같다. 연산 대상이 문자열이 아닌 배열의 요소라는 점만 다르다.
arr.indexOf(item, from)는 인덱스 from부터 시작해 item(요소)을 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환하고, 발견하지 못했으면 -1을 반환한다.
arr.lastIndexOf(item, from)는 위 메서드와 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다르다.
arr.includes(item, from)는 인덱스 from부터 시작해 item이 있는지를 검색하는데, 해당하는 요소를 발견하면 true를 반환한다.
위 메서드들은 요소를 찾을 때 완전 항등 연산자 === 을 사용한다는 점에 유의하자. false를 검색하면 정확히 false만을 검색하지, 0을 검색하진 않는다.
요소의 위치를 정확히 알고 싶은게 아니고 요소가 배열 내 존재하는지 여부만 확인하고 싶다면 arr.includes를 사용하는 게 좋다.
includes는 NaN도 제대로 처리한다는 점에서 indexOf/lastIndexOf와 약간의 차이가 있다.
객체로 이루어진 배열이 있다고 가정해본다면, 특정 조건에 부합하는 객체를 배열 내에서 arr.find(fn)을 사용해서 찾을 수 있다.
let result = arr.find(function(item, index, array) {
// true가 반환되면 반복이 멈추고 해당 요소를 반환
// 조건에 해당하는 요소가 없으면 undefined를 반환
});
//요소 전체를 대상으로 함수가 순차적으로 호출된다.
함수가 참을 반환하면 탐색은 중단되고 해당 요소가 반환되고, 원하는 요소를 찾지 못했으면 undefined가 반환된다.
id와 name 프로퍼티를 가진 사용자 객체로 구성된 배열을 예로, 배열 내에서 id == 1 조건을 충족하는 사용자 객체를 찾아본다면,
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
let user = users.find(item => item.id == 1);
alert(user.name); // John
find 메서드는 함수의 반환 값을 true로 만드는 단 하나의 요소를 찾는다.
조건을 충족하는 요소가 여러 개라면 arr.filter(fn)를 사용하면 된다.
filter는 find와 문법이 유사하지만, 조건에 맞는 요소 전체를 담은 배열을 반환한다는 점에서 차이가 있다.
let results = arr.filter(function(item, index, array) {
// 조건을 충족하는 요소는 results에 순차적으로 더해짐
// 조건을 충족하는 요소가 하나도 없으면 빈 배열이 반환됨
});
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
// 앞쪽 사용자 두 명을 반환합니다.
let someUsers = users.filter(item => item.id < 3);
alert(someUsers.length); // 2
배열을 특정 조건에 맞춰서 새로운 값으로 구성된 배열을 반환한다.
arr.map은 유용성과 사용 빈도가 아주 높은 메서드 중 하나이다.
map은 배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환해준다.
let result = arr.map(function(item, index, array) {
// 요소 대신 새로운 값을 반환합니다.
});
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6
arr.sort()는 배열의 요소를 정렬해준다. 배열 자체가 변경된다.
메서드를 호출하면 재정렬 된 배열이 반환되는데, 이미 arr 자체가 수정되었기 때문에 반환 값은 잘 사용되지 않는 편입이다.
let arr = [ 1, 2, 15 ];
// arr 내부가 재 정렬됩니다.
arr.sort();
alert( arr ); // 1, 15, 2
기대하던 결과(1, 2, 15)와는 다르다.
요소는 문자열로 취급되어 재정렬 된다.
앞서 배웠듯이 문자열 비교는 사전편집 순으로 진행되기 때문에 2는 15보다 큰 값으로 취급된다.("2" > "15").
기본 정렬 기준 대신 새로운 정렬 기준을 만들려면 arr.sort()에 새로운 함수를 넘겨줘야 한다.
인수로 넘겨주는 함수는 반드시 값 두 개를 비교해야 하고 반환 값도 있어야 한다.
function compare(a, b) {
if (a > b) return 1; // 첫 번째 값이 두 번째 값보다 큰 경우
if (a == b) return 0; // 두 값이 같은 경우
if (a < b) return -1; // 첫 번째 값이 두 번째 값보다 작은 경우
}
let arr = [ 1, 2, 15 ];
arr.sort(compare);
alert(arr); // 1, 2, 15
arr.sort(fn)는 포괄적인 정렬 알고리즘을 이용해 구현되어있다.
대개 최적화된 퀵 소트(quicksort)를 사용하는데, arr.sort(fn)는 주어진 함수를 사용해 정렬 기준을 만들고 이 기준에 따라 요소들을 재배열하므로 개발자는 내부 정렬 동작 원리를 알 필요가 없다. 우리가 해야 할 일은 정렬 함수 fn을 만들고 이를 인수로 넘겨주는 것뿐이다.
정렬 과정에서 어떤 요소끼리 비교가 일어났는지 확인하고 싶다면 아래 코드를 활용하면 된다.
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
alert( a + " <> " + b );
return a - b;
});
정렬 함수는 어떤 숫자든 반환할 수 있다.
양수를 반환하는 경우 첫 번째 인수가 두 번째 인수보다 '크다’를 나타내고, 음수를 반환하는 경우 첫 번째 인수가 두 번째 인수보다 '작다’를 나타내기만 하면 된다.
이 점을 이용하면 정렬 함수를 더 간결하게 만들 수 있습니다.
let arr = [ 1, 2, 15 ];
arr.sort(function(a, b) { return a - b; });
// = arr.sort( (a, b) => a - b );
alert(arr); // 1, 2, 15
문자열엔 localeCompare를 사용하자.
Ö같은 문자가 있는 언어에도 대응하려면 str.localeCompare 메서드를 사용해 문자열을 비교하는 게 좋다.
arr.reverse는 arr의 요소를 역순으로 정렬시켜주는 메서드이다.
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
메일 수신자가 여러 명일 경우, 발신자는 쉼표로 각 수신자를 구분할 것이다. "John, Pete, Mary"와 같이 말이다.
str.split(delim)을 이용하면 구분자(delimiter) delim을 기준으로 문자열을 쪼개준다.
아래 예시에선 쉼표와 공백을 합친 문자열이 구분자로 사용되고 있다.
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `${name}에게 보내는 메시지` ); // Bilbo에게 보내는 메시지
}
split 메서드는 두 번째 인수로 숫자를 받을 수 있다.
이 숫자는 배열의 길이를 제한해주므로 길이를 넘어서는 요소를 무시할 수 있다.
split(s)의 s를 빈 문자열로 지정하면 문자열을 글자 단위로 분리할 수 있다.
let str = "test";
alert( str.split('') ); // t,e,s,t
arr.join(glue)은 split과 반대 역할을 하는 메서드이다.
인수 glue를 접착제처럼 사용해 배열 요소를 모두 합친 후 하나의 문자열을 만들어준다.
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // 배열 요소 모두를 ;를 사용해 하나의 문자열로 합친다.
alert( str ); // Bilbo;Gandalf;Nazgul
forEach, for, for..of를 사용하면 배열 내 요소를 대상으로 반복 작업을 할 수 있다.
각 요소를 돌면서 반복 작업을 수행하고, 작업 결과물을 새로운 배열 형태로 얻으려면 map을 사용하면 된다.
arr.reduce와 arr.reduceRight도 이런 메서드들과 유사한 작업을 해준다.
그런데 사용법이 조금 복잡하다. reduce와 reduceRight는 배열을 기반으로 값 하나를 도출할 때 사용된다.
let value = arr.reduce(function(accumulator, item, index, array) {
// ...
}, [initial]);
인수로 넘겨주는 함수는 배열의 모든 요소를 대상으로 차례차례 적용되는데, 적용 결과는 다음 함수 호출 시 사용된다.
이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(accumulator)로 사용됩니다.
첫 번째 인수는 앞서 호출했던 함수들의 결과가 누적되어 저장되는 '누산기(accumulator)'라고 생각하면 된다. 마지막 함수까지 호출되면 이 값은 reduce의 반환 값이 됩니다.
reduce를 이용해 코드 한 줄로 배열의 모든 요소를 더한 값을 구한다면,
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15
동작 과정은 다음과 같다.
함수 최초 호출 시, reduce의 마지막 인수인 0(초깃값)이 sum에 할당된다. current엔 배열의 첫 번째 요소인 1이 할당되고 함수의 결과는 1이 된다.
두 번째 호출 시, sum = 1 이고 여기에 배열의 두 번째 요소(2)가 더해지므로 결과는 3이 된다.
세 번째 호출 시, sum = 3 이고 여기에 배열의 다음 요소가 더해진다. 이런 과정이 마지막 item까지 이어진다.
초깃값을 생략하는 것도 가능하다.
let arr = [1, 2, 3, 4, 5];
// reduce에서 초깃값을 제거함(0이 없음)
let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15
초깃값이 없으면 reduce는 배열의 첫 번째 요소를 초깃값으로 사용하고 두 번째 요소부터 함수를 호출하기 때문이다.
하지만 이렇게 초깃값 없이 reduce를 사용할 땐 극도의 주의를 기울여야 한다. 배열이 비어있는 상태면 reduce 호출 시 에러가 발생한다.
자바스크립트에서 배열은 독립된 자료형으로 취급되지 않고 객체형에 속한다. 따라서 typeof로는 일반 객체와 배열을 구분할 수가 없다.
alert(typeof {}); // object
alert(typeof []); // object
Array.isArray(value)는 배열만을 구분하도록 사용하는 유용한 메서드이다.
value가 배열이라면 true를, 배열이 아니라면 false를 반환해준다.
alert(Array.isArray({})); // false
alert(Array.isArray([])); // true