[모던 자바스크립트 튜토리얼] 5.5 배열과 메서드

개발견 배도르만·2023년 3월 31일
0
post-thumbnail

배열과 메서드

배열은 다양한 메서드를 제공한다.

요소 추가·제거 메서드

배열의 맨 앞이나 끝에 요소(item)를 추가하거나 제거

  • arr.push(...items) – 맨 끝에 요소 추가
  • arr.pop() – 맨 끝 요소 제거
  • arr.shift() – 맨 앞 요소 제거
  • arr.unshift(...items) – 맨 앞에 요소 추가

기본적인 메서드이다. 더욱 다양한 메서드를 알아보자.

splice

배열에서 요소를 하나만 지우는 메서드

배열 역시 객체형에 속하므로 프로퍼티를 지울 때 쓰는 연산자 delete를 사용한다면?

let arr = ["I", "go", "home"];

delete arr[1]; // "go"를 삭제합니다.

alert( arr[1] ); // undefined

// delete를 써서 요소를 지우고 난 후 배열 --> arr = ["I",  , "home"];
alert( arr.length ); // 3

arr.splice()는 만능 스위스 맥가이버 칼 같은 메서드이다. 이 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능하다.

문법은 다음과 같다.

arr.splice(index[, deleteCount, elem1, ..., elemN])

매개변수 1 : 인덱스(index) - 조작을 가할 첫 번째 요소를 가리킴
매개변수 2 : deleteCount - 제거하고자 하는 요소의 개수를 나타냄
이후 매개변수 : elem1, ..., elemN - 배열에 추가할 요소를 나타냄

요소 삭제 예시

let arr = ["I", "study", "JavaScript"];

arr.splice(1, 1); // 인덱스 1부터 요소 한 개를 제거

alert( arr ); // ["I", "JavaScript"]

인덱스 1이 가리키는 요소부터 시작해 요소 한 개(1)를 지웠다.

요소 세 개(3)를 지우고, 그 자리를 다른 요소 두 개로 교체하기

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삭제된 요소로 구성된 배열을 반환한다.

let arr = ["I", "study", "JavaScript", "right", "now"];

// 처음 두 개의 요소를 삭제함
let removed = arr.splice(0, 2);

alert( removed ); // "I", "study" <-- 삭제된 요소로 구성된 배열

splice 메서드의 deleteCount를 0으로 설정하면 요소를 제거하지 않으면서 새로운 요소를 추가할 수 있다.

let arr = ["I", "study", "JavaScript"];

// 인덱스 2부터
// 0개의 요소를 삭제합니다.
// 그 후, "complex"와 "language"를 추가합니다.
arr.splice(2, 0, "complex", "language");

alert( arr ); // "I", "study", "complex", "language", "JavaScript"

음수 인덱스도 사용할 수 있다.

let arr = [1, 2, 5];

// 인덱스 -1부터 (배열 끝에서부터 첫 번째 요소)
// 0개의 요소를 삭제하고
// 3과 4를 추가합니다.
arr.splice(-1, 0, 3, 4);

alert( arr ); // 1,2,3,4,5

slice

arr.slice는 arr.splice와 유사하지만 훨씬 간단하다.

문법:

arr.slice([start], [end])

이 메서드는 "start" 인덱스부터 ("end"를 제외한) "end"인덱스까지의 요소를 복사한 새로운 배열을 반환한다(둘 다 음수일 수 있음).

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의 복사본을 만들 수 있다. 기존의 배열을 건드리지 않으면서 배열을 조작해 새로운 배열을 만들 때 자주 사용된다.

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 메서드는 제공받은 배열의 요소를 복사해 활용한다. 객체가 인자로 넘어오면 (배열처럼 보이는 유사 배열 객체이더라도) 객체는 분해되지 않고 통으로 복사되어 더해진다.

let arr = [1, 2];

let arrayLike = {
  0: "something",
  length: 1
};

alert( arr.concat(arrayLike) ); // 1,2,[object Object]

그런데 인자로 받은 유사 배열 객체에 특수한 프로퍼티 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

forEach로 반복작업 하기

주어진 함수를 배열 요소 각각에 대해 실행

문법:

arr.forEach(function(item, index, array) {
  // 요소에 무언가를 할 수 있습니다.
});

아래는 요소 모두를 출력하는 코드

// for each element call alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

참고로, 인수로 넘겨준 함수의 반환값은 무시된다.

배열 탐색하기

배열 내에서 무언가를 찾고 싶을 때 사용

indexOf, lastIndexOf와 includes

같은 이름을 가진 문자열 메서드와 문법이 동일하고, 하는 일도 같다. 연산 대상이 문자열이 아닌 배열의 요소라는 점만 다르다.

  • arr.indexOf(item, from)
    인덱스 from부터 시작해 item(요소)을 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환, 발견하지 못했으면 -1을 반환
  • arr.lastIndexOf(item, from)
    위 메서드와 동일한 기능이지만, 검색을 끝에서부터 시작한다는 점이 다름
  • arr.includes(item, from)
    인덱스 from부터 시작해 item이 포함되어있는지의 여부만 판단하여 boolean값 반환
let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

alert( arr.includes(1) ); // true

위 메서드들은 요소를 찾을 때 완전 항등 연산자 === 을 사용한다. false를 검색하면 정확히 false만을 검색하지, 0을 검색하진 않는다.

includesNaN도 제대로 처리한다는 점에서 indexOf/lastIndexOf와 약간의 차이가 있다.

const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (완전 항등 비교 === 는 NaN엔 동작하지 않으므로 0이 출력되지 않습니다.)
alert( arr.includes(NaN) );// true (NaN의 여부를 확인하였습니다.)

find와 findIndex

객체로 이루어진 배열에서 특정 조건에 부합하는 객체를 찾기

문법:

let result = arr.find(function(item, index, array) {
  // true가 반환되면 반복이 멈추고 해당 요소를 반환합니다.
  // 조건에 해당하는 요소가 없으면 undefined를 반환합니다.
});

요소 전체를 대상으로 함수가 순차적으로 호출된다.

item – 함수를 호출할 요소
index – 요소의 인덱스
array – 배열 자기 자신
함수가 참을 반환하면 탐색은 중단되고 해당 요소가 반환된다. 원하는 요소를 찾지 못했으면 undefined가 반환된다.

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

(item => item.id == 1) 이런 패턴이 가장 많이 사용되는 편이다. 다른 인자들(index, array)은 잘 사용되지 않는다.

arr.findIndexfind와 동일한 일을 하나, 조건에 맞는 요소를 반환하는 대신 해당 요소의 인덱스를 반환한다는 점이 다르다. 조건에 맞는 요소가 없으면 -1이 반환된다.

filter

조건을 충족하는 요소가 여러 개라면 arr.filter(fn)를 사용하면 된다.

filterfind와 문법이 유사하지만, 조건에 맞는 요소 전체를 담은 배열을 반환한다는 점에서 차이가 있다.

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

배열을 변형하는 메서드

map

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

sort(fn)

arr.sort()는 배열의 요소를 정렬한다. 호출 시 재정렬된 배열이 반환되는데, 이미 배열 자체를 수정하는 함수이기 때문에 반환값은 잘 사용하지 않는다.

let arr = [ 1, 2, 15 ];

// arr 내부가 재 정렬됩니다.
arr.sort();

alert( arr );  // 1, 15, 2

모든 요소는 문자형으로 변환된 이후에 재정렬된다. 문자열 비교는 사전편집 순으로 진행되기 때문에 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엔 숫자, 문자열, 객체 등이 들어갈 수 있다. 이러한 비동질적인 집합을 정렬해야 한다고 가정해보자. 무언가를 정렬하려면 기준이 필요하다. 이때 정렬 기준을 정의해주는 함수(ordering function, 정렬 함수) 가 필요하다. sort에 정렬 함수를 인수로 넘겨주지 않으면 이 메서드는 사전편집 순으로 요소를 정렬한다.

정렬 함수는 어떤 숫자든 반환할 수 있다.
정렬 함수의 반환 값엔 제약이 없다. 양수를 반환하는 경우 첫 번째 인수가 두 번째 인수보다 '크다’를 나타내고, 음수를 반환하는 경우 첫 번째 인수가 두 번째 인수보다 '작다’를 나타내기만 하면 된다.

이 점을 이용하면 정렬 함수를 더 간결하게 만들 수 있다.

let arr = [ 1, 2, 15 ];

arr.sort(function(a, b) { return a - b; });

alert(arr);  // 1, 2, 15

화살표 함수를 사용하면 정렬 함수를 더 깔끔하게 만들 수 있다.

arr.sort( (a, b) => a - b );

문자열엔 localeCompare를 사용할 수 있다.

독일어로 나타낸 국가가 요소인 배열을 정렬해보자.

let countries = ['Österreich', 'Andorra', 'Vietnam'];

alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (제대로 정렬이 되지 않았습니다.)

alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (제대로 정렬되었네요!)

reverse

arr.reverse는 arr의 요소를 역순으로 정렬시켜주는 메서드이다.

let arr = [1, 2, 3, 4, 5];
arr.reverse();

alert( arr ); // 5,4,3,2,1

반환 값은 재 정렬된 배열이다.

split과 join

문자열을 특정 구분자를 기준으로 배열로 바꿀 수 있다.

쉼표와 공백을 합친 문자열이 구분자로 사용되는 예시

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `${name}에게 보내는 메시지` ); // Bilbo에게 보내는 메시지
}

split 메서드는 두 번째 인수로 숫자를 받을 수 있다. 이 숫자는 배열의 길이를 제한해주며, 길이를 넘어서는 요소를 무시한다. 자주 사용하는 기능은 아니다.

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);

alert(arr); // Bilbo, Gandalf

문자열을 글자 단위로 분리하기
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

reduce와 reduceRight

배열을 기반으로 값 하나를 도출할 때 사용

문법:

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

인수로 넘겨주는 함수는 배열의 모든 요소를 대상으로 차례차례 적용되는데, 적용 결과는 다음 함수 호출 시 사용된다.

accumulator – 이전 함수 호출의 결과. initial은 함수 최초 호출 시 사용되는 초깃값을 나타냄(옵션)
item – 현재 배열 요소
index – 요소의 위치
array – 배열
이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(previousValue)로 사용된다.

첫 번째 인수는 앞서 호출했던 함수들의 결과가 누적되어 저장되는 '누산기(accumulator)'라고 생각하면 된다. 마지막 함수까지 호출되면 이 값은 reduce의 반환 값이 된다.

reduce를 이용해 코드 한 줄로 배열의 모든 요소를 더한 값을 구하는 예제

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

let result = arr.reduce((sum, current) => sum + current, 0);

alert(result); // 15

대개 이렇게 인수를 두 개만 받는다.

계산 흐름:

(초기값이 없으면 reduce는 배열의 첫 번째 요소를 초기값으로 사용하고 두 번째 요소부터 함수를 호출하나, 빈 배열이라면 에러 발생)

arr.reduceRight는 reduce와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행

Array.isArray로 배열 여부 알아내기

자바스크립트에서 배열은 독립된 자료형으로 취급되지 않고 객체형에 속한다.

따라서 typeof로는 일반 객체와 배열을 구분할 수 없다.

Array.isArray(value)는 value가 배열이라면 true를, 배열이 아니라면 false를 반환한다.

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

배열 메서드와 ‘thisArg’

함수를 호출하는 대부분의 배열 메서드(find, filter, map 등. sort는 제외)는 thisArg라는 매개변수를 옵션으로 받을 수 있다.

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg는 선택적으로 사용할 수 있는 마지막 인수입니다.

thisArg는 func의 this가 된다.

let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};

let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

// army.canJoin 호출 시 참을 반환해주는 user를 찾음
let soldiers = users.filter(army.canJoin, army);

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23

thisArgs에 army를 지정하지 않고 단순히 users.filter(army.canJoin)를 사용했다면 army.canJoin은 단독 함수처럼 취급되고, 함수 본문 내 thisundefined가 되어 에러가 발생했을 것이다.

callback함수로 사용된 객체의 메소드는 단독 함수로 취급되며, 다시 바인딩해야 객체의 메소드로 작동하기 때문이다.

users.filter(user => army.canJoin(user))를 사용하면 users.filter(army.canJoin, army)를 대체할 수 있긴 한데 thisArg를 사용하는 방식이 좀 더 이해하기 쉬우므로 더 자주 사용된다.

✍️ 정리

  • 요소를 더하거나 지우기

    • push(...items) – 맨 끝에 요소 추가하기
    • pop() – 맨 끝 요소 추출하기
    • shift() – 첫 요소 추출하기
    • unshift(...items) – 맨 앞에 요소 추가하기
    • splice(pos, deleteCount, ...items) – pos부터 deleteCount개의 요소를 지우고, items 추가하기
    • slice(start, end) – start부터 end 바로 앞까지의 요소를 복사해 새로운 배열을 만듦
    • concat(...items) – 배열의 모든 요소를 복사하고 items를 추가해 새로운 배열을 만든 후 이를 반환함. items가 배열이면 이 배열의 인수를 기존 배열에 더해줌
  • 원하는 요소 찾기

    • indexOf/lastIndexOf(item, pos) – pos부터 원하는 item을 찾음. 찾게 되면 해당 요소의 인덱스를, 아니면 -1을 반환함
    • includes(value) – 배열에 value가 있으면 true를, 그렇지 않으면 false를 반환함
    • find/filter(func) – func의 반환 값을 true로 만드는 첫 번째/전체 요소를 반환함
    • findIndex는 find와 유사함. 다만 요소 대신 인덱스를 반환함
  • 배열 전체 순회하기

    • forEach(func) – 모든 요소에 func을 호출함. 결과는 반환되지 않음
  • 배열 변형하기

    • map(func) – 모든 요소에 func을 호출하고, 반환된 결과를 가지고 새로운 배열을 만듦
    • sort(func) – 배열을 정렬하고 정렬된 배열을 반환함
    • reverse() – 배열을 뒤집어 반환함
    • split/join – 문자열을 배열로, 배열을 문자열로 변환함
    • reduce(func, initial) – 요소를 차례로 돌면서 func을 호출함. 반환값은 다음 함수 호출에 전달함. 최종적으로 하나의 값이 도출됨
  • 기타

    • Array.isArray(arr) – arr이 배열인지 여부를 판단함
  • sort, reverse, splice는 기존 배열을 변형시킨다는 점에 주의하기

profile
네 발 개발 개

0개의 댓글