[모던 JavaScript 튜토리얼] 24. 배열 메서드

6 분 소요

본문 바로가기


splice

let arr = [1, 2, 3];

delete arr[0];

console.log(arr);        // [ <1 empty item>, 2, 3 ]
console.log(arr.length); // 3

위 코드는, 배열 요소 하나를 객체 프로퍼티 제거 연산자 delete 를 통해 0번 째 요소를 지우는 동작을 한다. delete에 해당하는 을 지우기 때문에, 값이 할당되지 않은 상태를 의미하는 undefined 로 설정된다. 즉, 제대로 지워졌다고 볼 수 있다. 하지만, 우리는 값을 지운 만큼 공간도 지워지길 바라며, 이에 따른 length 프로퍼티의 변화도 원한다.


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

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

arr.splice(1, 1);

console.log(arr); // [ "I", "JavaScript" ]

arr.plice 는 파라미터로 주어진 index 부터 deleteCount 개의 요소를 제거한다. undefined 로 남아 있는 것이 아니라, 공간 자체가 비워지고, 동시에 length 프로퍼티도 조정된다. 즉, 요소들이 빈 공간을 채워 앞으로 당겨진다.


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

arr.splice(0, 3, "Let's", "dance");

console.log(arr); // [ "Let's", "dance", "right", "now" ]

deleteCount 개의 요소를 지우고, 그 자리에 다른 요소들 elem1, ..., elemN 을 새로 넣어 줄 수도 있다.


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

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

console.log(arr); // [ "I", "study", "complex", "language", "JavaScript" ]

deleteCount0으로 설정해 요소를 제거하지 않으면서 요소들을 사이에 추가할 수 있다.


slice

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

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

console.log(arr.slice(1, 3)); // [ "e", "s" ]
console.log(arr.slice(-2));   // [ "s", "t" ]

arr.slicestart 부터 end 까지의 요소들로 새로운 배열을 만들어 리턴한다.


let arr1 = ["t", "e", "s", "t"];
let arr2 = arr1.slice();
// let arr2 = Object.assign([], arr1);
arr2[0] = "d";

console.log(arr1); // [ "t", "e", "s", "t" ]
console.log(arr2); // [ "d", "e", "s", "t"]

위와 같이 인자를 전달하지 않은 채 호출해 복사본을 만들 수 있다. 배열도 객체이므로, Object.assign 을 통해서도 복사할 수 있다.


concat

arr.concat(arg1, arg2…)

let arr = [1, 2];

console.log(arr.concat([3, 4]));         // 1,2,3,4
console.log(arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6
console.log(arr.concat([3, 4], 5, 6));   // 1,2,3,4,5,6

arr.concat 은 기존 배열의 요소를 사용해 새로운 배열을 만들거나, 기존 배열에 요소를 추가할 때 사용한다. 위 예에서 볼 수 있듯, 각 인자로 요소가 아닌 배열이 주어질 수도 있다.


let arr = [1, 2];
let arrayLike = {
    0: "something",
    length: 1,
};

console.log(arr.concat(arrayLike)); // [ 1, 2, { '0': 'something', length: 1 } ]

객체가 인자로 넘어오면, 객체는 분해되지 않고 통으로 복사되어 한 요소를 차지하게 된다.


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

console.log(arr.concat(arrayLike)); // [ 1, 2, 'something', 'else' ]

Symbol.isConcatSpreadable 프로퍼티가 true 로 설정되면, 객체를 배열처럼 취급해, 객체 프로퍼티의 값이 더해진다. 이 때, 키 값은 숫자여야 한다.


forEach

arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])

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

// Bilbo is at index 0 in Bilbo,Gandalf,Nazgul
// Gandalf is at index 1 in Bilbo,Gandalf,Nazgul
// Nazgul is at index 2 in Bilbo,Gandalf,Nazgul
arr.forEach((item, index, array) => {
    console.log(`${item} is at index ${index} in ${array}`);
});

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


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

console.log(arr.forEach((value, index, array) => index)); // undefined

반환 값이 undefined 임을 유의하자.


배열 탐색하기

arr.indexOf와 arr.lastIndexOf, arr.includes 의 쓰임새는 [문자열 편]과 동일하다.

  • arr.indexOf(item, from)from 부터 시작해 인덱스를 증가시키며 item 을 찾는다.
  • arr.lastIndexOf(item, from)from 부터 인덱스를 감소시키며 item 을 찾는다.
  • arr.includes(item, from)from 부터 시작해 item 이 있는 지 찾는다.


let arr = [1, 0, false];

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

console.log(arr.includes(1));    // true

비교를 할 때 동등 연산자(===)를 사용함을 유의하자.


const arr = [NaN];

console.log(arr.indexOf(NaN));  // -1
console.log(arr.includes(NaN)); // true

includesNaN 도 제대로 처리한다는 점에서 indexOf, lastIndexOf 와 차이점이 있다.


find와 findIndex

객체로 이루어진 배열의 경우, arr.find(fn) 을 통해 조건에 맞는 객체를 찾을 수 있다.


arr.find((value, index, array) ⇒ { })

let users = [
    { id: 1, name: "John" },
    { id: 2, name: "Pete" },
    { id: 3, name: "Mary" },
];

let user = users.find((item) => item.id == 1);

console.log(user.name); // John

조건에 부합하는 요소를 찾으면, 해당 객체를 리턴하고, 찾지 못하면 undefined 를 리턴한다.


filter

let users = [
    { id: 1, name: "John" },
    { id: 2, name: "Pete" },
    { id: 3, name: "Mary" },
];

let someUsers = users.filter((item) => item.id < 3);

console.log(someUsers); // [ { id: 1, name: 'John' }, { id: 2, name: 'Pete' } ]

위에서 본 find 는 조건을 만족하는 객체 하나를 리턴하는 반면, filter 는 만족하는 여러 개의 객체를 새로운 배열에 담아 리턴한다.


배열을 변형하는 메서드

map

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map((value) => value.length);

console.log(lengths); // [ 5, 7, 6 ]

arr.map 은 배열의 전체 요소를 대상으로 함수를 호출한 뒤, 함수 호출 결과로 새로운 배열을 만들어 리턴한다.


sort

arr.sort() 는 배열의 요소를 정렬해준다. 메서드를 호출한 배열 자체가 변경된다.

let arr = [1, 2, 15];

arr.sort();

console.log(arr); // [ 1, 15, 2 ]

모든 요소는 문자형으로 변환된 후 정렬된다.


let arr = [1, 2, 15];

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

console.log(arr); // [ 1, 2, 15 ]

위와 같이, 새로운 정렬 기준을 함수로 작성해 인수로 넘길 수 있다.


reverse

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

console.log(arr); // [ 5, 4, 3, 2, 1 ]

arr.reverse 는 arr의 요소를 역순으로 바꾼다.


split과 join

let nums = "1 2 3 4 5";
let arr = nums.split(" ").map(Number);

console.log(arr);

str.split(delim) 은, 구분자 delim 을 기준으로 문자열을 쪼개 각각을 요소로 하는 배열을 리턴한다.


let arr = ["Bilbo", "Gandalf", "Nazgul"];
let str = arr.join();

console.log(str); // Bilbo,Gandalf,Nazgul

str.join(glue) 은 split 과 반대로, 배열 모든 요소 사이에 glue 를 넣어 문자열로 만들어 리턴한다. 파라미터로 glue 를 전달하지 않으면, 쉼표가 디폴드로 적용된다.


reduce와 reduceRight

forEach, for, for...of 를 사용하면 배열 내 요소들을 대상으로 반복 작업을 할 수 있다.

arr.reducearr.reduceRight 도 마찬가지로 반복 작업을 위해 사용되지만, 특정한 하나의 값을 리턴한다는 차이점이 있다. reduceRight 은 reduce 와 하는 일이 같고, 뒤에서부터 순히한다는 차이점이 있다.


arr.reduce((prev, value, index, array) ⇒ { }, initValue)

initValue를 설정하지 않으면, 초기 accumulator 는 첫 번째 인덱스, 즉 array[0]으로 설정된다. 인덱스가 홀수면 더하고, 짝수면 빼는 연산을 적용해 최종 값을 출력하는 예제를 보자.


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

let result = arr.reduce((prev, val, idx) => prev + (idx % 2 == 0 ? -val : val));

console.log("result : " + result); // -1

initValue 를 설정하지 않았기에, 첫 번째 요소의 인덱스가 짝수임에도 불구하고 prev 가 1로 사용되고, 다음 번 요소부터 리듀서 함수를 적용시킨다. 따라서, 잘못된 정답 -1이 나오게 된다.


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

let result = arr.reduce((prev, val, idx) => prev + (idx % 2 == 0 ? -val : val), 0);

console.log("result : " + result); // -1

initValue 를 0으로 설정함으로써, 첫 번째 인덱스부터 순회를 시작하게 된다.


배열 여부 알아내기

let obj = { name: "Kim", age: 28 };
let arr = [1, 2, 3];

console.log(typeof obj); // object
console.log(typeof arr); // object

배열도 객체기에, typeof 로는 object 가 출력된다.


let obj = { name: "Kim", age: 28 };
let arr = [1, 2, 3];

console.log(Array.isArray(obj)); // false
console.log(Array.isArray(arr)); // true

배열인지 확인을 위해서는 Array.isArray() 를 사용하자.


thisArg

배열 메서드 대부분의 마지막 파라미터로 thisArg 가 전달된다. 이는 파라미터로 전달된 콜백 함수 내부에 this 가 사용시, 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 }];

let soldiers = users.filter(army.canJoin, army);

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

객체 army 의 내부 메서드 canJoin 을 콜백 함수로 설정해 filter 메서드를 실행했다. 여기서 army 라는 객체를 전달하지 않으면, canJoin 내부에서의 this 는 global object 로 잘못 인식되어 에러가 발생하게 된다.

댓글남기기