[javascript] Array methods

skdus·2022년 4월 17일
1

JavaScript

목록 보기
4/17
post-thumbnail

💡 요소 추가·제거 메서드

🌱 이전 게시글에서 다룬 메서드
arr.push(...items) - 맨 끝에 요소 추가
arr.pop() - 맨 끝 요소 제거
arr.shift() - 맨 앞 요소 제거
arr.unshift(...items) - 맨 앞에 요소 추가

splice()

배열에서 요소 하나만 지우고 싶을 때,
1) 객체의 프로퍼티를 지울 때 쓰는 연산자 delete 사용한다.

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

delete arr[1];
alert( arr[1] ); // undefined

//arr = ["I",  , "go", "home"];
alert( arr.length ); // 4

원하는 값을 지웠는데도, 배열의 요소가 여전히 4개인 것을 확인할 수 있다. 이것은 delete obj.key는 key를 이용해 해당 키에 상응하는 값을 지우기 때문이기에, 당연한 결과이다.

그럼 삭제된 요소가 만든 빈 공간을 나머지 요소들이 자동으로 채우려면 어떻게 해야할까?

2) arr.splice(start)을 사용한다.
이 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능하다.

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

첫 번째 매개변수는 조작을 가할 첫 번째 요소를 가리키는 인덱스(index)이다. 두 번째 매개변수는 deleteCount로, 제거하고자 하는 요소의 개수를 나타내며, elem1, ..., elemN은 배열에 추가할 요소를 나타낸다.

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

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

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


arr.splice(0, 1, "Let's", "dance"); // 요소 제거후 대체

alert( arr ) // ["Let's", "dance", "JavaScript"]

splice는 삭제된 요소로 구성된 배열을 반환할 수도 있으며,
splice 메서드의 deleteCount를 0으로 설정하면 요소를 제거하지 않으면서 새로운 요소를 추가할 수 있다.

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


let removed = arr.splice(0, 2);

alert( removed ); // "I", "study"


arr.splice(2, 0, "complex", "language");

alert( arr ); // "JavaScript", "right", "complex", "language", "now"

🌱 음수 인덱스도 사용 가능
slice 메서드 뿐만 아니라 배열 관련 메서드엔 음수 인덱스를 사용할 수 있다. 이때 마이너스 부호 앞의 숫자는 배열 끝에서부터 센 요소 위치를 나타낸다.

let arr = [1, 2, 5];

arr.splice(-1, 0, 3, 4);

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

slice()

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

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

arr.slice는 문자열 메서드인 str.slice와 유사하게 동작하는데 arr.slice는 서브 문자열(substring) 대신 서브 배열(subarray)을 반환한다는 점이 다르다.

let arr = ["t", "e", "s", "t"];

alert( arr.slice(1, 3) ); // e,s
alert( arr.slice(-2) ); // s,t

arr.slice()는 인수를 하나도 넘기지 않고 호출하여 arr의 복사본을 만들 수 있기 때문에, 기존의 배열을 건드리지 않으면서 배열을 조작해 새로운 배열을 만들고자 할 때 자주 사용한다.

concat()

arr.concat은 기존 배열의 요소를 사용해 새로운 배열을 만들거나 기존 배열에 요소를 추가하고자 할 때 사용할 수 있다.

arr.concat(arg1, arg2...)

인수엔 배열이나 값이 올 수 있는데, 인수 개수엔 제한이 없다.

메서드를 호출하면 arr에 속한 모든 요소와 arg1, arg2 등에 속한 모든 요소를 한데 모은 새로운 배열이 반환된다.

인수 argN가 배열일 경우 배열의 모든 요소가 복사된다.
그렇지 않은경우(단순 값인 경우)는 인수가 그대로 복사된다.

let arr = [1, 2];

alert( arr.concat([3, 4]) ); // 1,2,3,4가 포함된 새로운 배열 반환

alert( arr.concat([3, 4], [5, 6]) ); // 1,2,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",
  length: 1
};

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


let arrayLike2 = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

alert( arr.concat(arrayLike2) ); // 1,2,something,else

💡 배열 순회 메서드

forEach(func)

arr.forEach는 주어진 함수를 배열 요소 각각에 대해 실행할 수 있게 해준다.

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

// 요소 모두를 얼럿창을 통해 출력해줌
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

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


💡 배열 탐색 메서드

배열 내에서 무언가를 찾고 싶을 때 쓰는 메서드

indexOf/lastIndexOf와 includes

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

  • arr.indexOf(item, from): 인덱스 from부터 시작해 item(요소)을 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환하고, 발견하지 못했으면 -1을 반환한다.
  • arr.lastIndexOf(item, from): 위 메서드와 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다르다.
  • arr.includes(item, from): 인덱스 from부터 시작해 item이 있는지를 검색하는데, 해당하는 요소를 발견하면 true를 반환한다.
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


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

위 메서드들은 요소를 찾을 때 완전 항등 연산자 === 을 사용한다는 점에 유의해야한다. 보시는 바와 같이 false를 검색하면 정확히 false만을 검색하지, 0을 검색하진 않는다.

요소의 위치를 정확히 알고 싶은게 아니고 요소가 배열 내 존재하는지 여부만 확인하고 싶다면 arr.includes를 사용하는 게 좋다.

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

find/findIndex()

특정 조건에 부합하는 객체를 배열 내에서 찾을 때는 arr.find(fn) 사용

let result = arr.find(function(item, index, array) {
  // ...
});

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

  • item – 함수를 호출할 요소
  • index – 요소의 인덱스
  • array – 배열 자기 자신

함수가 참을 반환(true)하면 탐색은 중단되고 해당 요소가 반환되고, 원하는 요소를 찾지 못했으면 undefined가 반환된다.

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 안의 함수가 인자를 하나만 가지고 있다는 점에 주목하자(item => item.id == 1). 이런 패턴이 가장 많이 사용되는 편이라고 한다. 다른 인자들(index, array)은 잘 사용되지 않음!!

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

filter(func)

find 메서드는 함수의 반환 값을 true로 만드는 단 하나의 요소를 찾는다.

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

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

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(func)

map은 배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환해준다.

let result = arr.map(function(item, index, array) {
  // 요소 대신 새로운 값을 반환
});

//길이 출력 예시
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);

alert(lengths); // 5,7,6

sort(func)

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 compareNumeric(a, b) {
  if (a > b) return 1; // 자리 변경
  if (a == b) return 0;
  if (a < b) return -1;
}

let arr = [ 1, 2, 15 ];

arr.sort(compareNumeric);

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

arr엔 숫자, 문자열, 객체 등이 들어갈 수 있으며, 알 수 없는 무언가로 구성된 집합이 된다.
이제 이 비 동질적인 집합을 정렬해야 한다고 가정해보자. 무언가를 정렬하려면 기준이 필요하다!!
이때 정렬 기준을 정의해주는 함수(ordering function, 정렬 함수) 가 필요합니다. 그렇기에 sort에 정렬 함수를 인수로 넘겨주지 않으면 이 메서드는 사전편집 순으로 요소를 정렬한다.

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

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를 사용하자.
strings에서의 비교 알고리즘은 유니코드를 기준으로 글자를 비교한다. Ö같은 문자가 있는 언어에도 대응하려면 str.localeCompare 메서드를 사용해 문자열을 비교하는 게 좋다.

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

alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (제대로 정렬 X)

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()

split()

긴 문자열 형태의 리스트를 배열 형태로 전환해 처리하고 싶을 때, 입력받은 문자열을 배열로 바꿔준다.
str.split(delim)는 구분자(delimiter) delim을 기준으로 문자열을 쪼개준다.

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

join()

arr.join(glue)은 split과 반대 역할을 하는 메서드이다. 인수 glue를 접착제처럼 사용해 배열 요소를 모두 합친 후 하나의 문자열을 만들어준다.

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';'); // 배열 요소 모두를 ;를 사용해 하나의 문자열로 합침.

alert( str ); // Bilbo;Gandalf;Nazgul

reduce와 reduceRight

reduce와 reduceRight는 배열을 기반으로 값 하나를 도출할 때 사용한다.

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

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

  • accumulator – 이전 함수 호출의 결과. initial은 함수 최초 호출 시 사용되는 초깃값을 나타냄(옵션)
  • item – 현재 배열 요소
  • index – 요소의 위치
  • array – 배열

이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(previousValue)로 사용된다.

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

// 배열의 모든 요소를 더해보자

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

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

alert(result); // 15

reduce에 전달한 함수는 오직 인수 두 개만 받고 있다. 대개 이렇게 인수를 두 개만 받음!

함수 최초 호출 시, reduce의 마지막 인수인 0(초깃값)이 sum에 할당되고, current엔 배열의 첫 번째 요소인 1이 할당된다. 따라서 함수의 결과는 1.
두 번째 호출 시, sum = 1 이고 여기에 배열의 두 번째 요소(2)가 더해지므로 결과는 3.
세 번째 호출 시, sum = 3 이고 여기에 배열의 다음 요소가 더해지면서, 이러한 과정이 반복된다.

🌱 초깃값을 생략하는 것도 가능하다.
초깃값이 없으면 reduce는 배열의 첫 번째 요소를 초깃값으로 사용하고 두 번째 요소부터 함수를 호출한다.
But, 초기값 없이 사용할 때, 배열이 비어있는 상태면 reduce 호출 시 에러가 발생한다. 이런 예외상황 때문에 항상 초깃값을 명시해 줄 것을 권장!!!

arr.reduceRight는 reduce와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행한다는 점이 다른 메서드입니다.


💡 느낀 점

ㅇㅗㅏ,,,, 진짜 너무 많은데,,,
이 메서드만으로 배열과 관련된 작업 99%를 해결할 수 있다고 하니,,,힘내봅니다,,


참고자료

0개의 댓글