Array Additions

배열(Array) 객체에 새롭게 추가된 클래스 또는 스태틱(static), 인스턴스 메서드에 대해 정리합니다.

영상 강의

PART 1

NOTE

영상 강의에서 만든 피보나치 수열 함수는 제터레이터 함수에서 다룬 내용입니다.

PART 2

예제

Array.from()

기존 코드는 유사 배열을 배열화 한 후 배열의 메서드를 사용했습니다.

// DOM 객체 수집(Collection) = NodeList
// lis 변수에 참조된 값은 length 속성을 가진 유사 배열 객체
var lis = document.querySelectorAll('ul.demo li');

// 유틸리티 함수
function makeArray(o) {
  return Array.prototype.slice.call(o);
}

// 유틸리티 함수 makeArray()를 사용하여 lis 유사 배열을 배열로 변경
makeArray(lis).forEach(function (li) {
  console.log(li); // <li> 순환
});

ES6+ 부터는 기본 제공되는 Array.from 클래스 메서드를 사용해 손쉽게 유사 배열을 배열화할 수 있습니다.

// DOM 객체 수집(Collection) = NodeList
// lis 변수에 참조된 값은 length 속성을 가진 유사 배열 객체
var lis = document.querySelectorAll('ul.demo li');

// Array.from() 네이티브 Array 메서드를 사용하여 lis 유사 배열을 배열로 변경
Array.from(lis).forEach(li => console.log(li)); // <li> 순환

NOTE

전개 연산자를 사용해 유사 배열 객체를 배열화 할 수 있습니다.

const lisHTML = [...lis].map(li => li.innerHTML);

Array.of()

new Array() 구문 사용 시 주의할 점은 객체 생성 과정에서 전달된 첫번째 아이템이 숫자일 경우 전달된 개수만큼 아이템이 생성되는 의도치 않는 문제가 발생한 점입니다.

var arr = new Array(3); // [undefined, undefined, undefined]

NOTE

이러한 문제를 마주치지 않기 위해 배열 리터럴 사용을 권장합니다.

var arr = [3];

ES6+ 부터는 Array.of() 구문을 사용해 전달된 첫번째 아이템이 숫자일 경우에도 의도치 않는 문제가 발생하지 않습니다.

const arr = Array.of(3); // [3]

Array.prototype.keys()

기존 코드는 for문 또는 배열의 .forEach() 메서드를 사용해 배열 데이터를 순환 처리하였습니다.

var numbers = [100, 105, 103, 109];

// for문
for ( var i=0, l=numbers.length; i<l; i++ ) {
  console.log(i, numbers[i]);
  // 0, 100
  // 1, 105
  // 2, 103
  // 3, 109
}

// forEach문
numbers.forEach(function(n, i) {
  console.log(i, n);
  // 0, 100
  // 1, 105
  // 2, 103
  // 3, 109
});

ES6+ 부터는 배열에 for .. of문을 사용하여 데이터를 순환 처리할 수 있습니다.

for (let number of numbers) {
  console.log(number);
  // 100
  // 105
  // 103
  // 109
}

만약 데이터의 값이 아닌 인덱스 키를 출력하고자 한다면 배열 객체의 .keys() 메서드를 사용합니다.

for (let index of numbers.keys()) {
  console.log(index);
  // 0
  // 1
  // 2
  // 3
}

Array.prototype.values()

배열 데이터의 값을 순환 처리하고자 할 경우, 배열 객체의 .values() 메서드를 사용할 수 있지만 배열을 순환 처리한 결과와 동일하기에 굳이 이런 방법을 사용할 필요는 없습니다.

for (let value of numbers.values()) {
  console.log(value);
  // 100
  // 105
  // 103
  // 109
}

Array.prototype.entries()

배열 데이터의 각 아이템 인덱스와 값이 모두 필요한 경우라면 .entries() 메서드를 사용할 수 있습니다. 순환된 각 아이템은 인덱스와 값을 포함한 배열을 반환합니다.

for (let item of numbers.entries()) {
  console.log(item);
  // [0, 100]
  // [1, 105]
  // [2, 103]
  // [3, 109]
}

구조 분해 할당을 활용하면 아래 코드처럼 인덱스와 값을 참조하는 변수를 설정할 수 있습니다.

for (let item of numbers.entries()) {
  let [index, number] = item;
  console.log(index, number);
  // 0, 100
  // 1, 105
  // 2, 103
  // 3, 109
}

구조 분해 할당 구문을 직접 for .. of문에 삽입하여 사용할 수도 있습니다.

for (let [index, number] of numbers.entries()) {
  console.log(index, number);
  // 0, 100
  // 1, 105
  // 2, 103
  // 3, 109
}

Array.prototype.find()

기존 코드는 배열의 아이템 중 매칭되는 특정 아이템을 찾기 위해 별도로 헬퍼 함수를 만들어 사용했습니다.

var numbers = [100, 105, 103, 109];

// 배열 아이템을 찾는 유틸리티 함수
function findItemArray(array, cb) {
  for ( var i=0, l=array.length; i<l; i++ ) {
    if ( cb(array[i], i, array) ) { return array[i] }
  }
}

// 유틸리티 함수를 사용해 조건에 부합하는 첫번째 아이템 반환
var item = findItemArray(numbers, function(item, index, array) {
   return item > 100 && item < 105;
});

console.log(item); // 103

ES6+ 부터는 .find() 메서드를 사용해 손쉽게 매칭되는 첫번째 아이템을 찾을 수 있습니다.

numbers.find(number => number > 100 && number < 105); // 103

Array.prototype.findIndex()

기존 코드는 배열의 아이템 중 매칭되는 특정 아이템의 인덱스를 찾기 위해 별도로 헬퍼 함수를 만들어 사용했습니다.

var numbers = [100, 105, 103, 109];

// 배열 아이템 인덱스를 찾는 유틸리티 함수
function findItemIndexArray(array, cb) {
  for ( var i=0, l=array.length; i<l; i++ ) {
    if ( cb(array[i], i, array) ) { return i; }
  }
  return -1;
}

// 유틸리티 함수를 사용해 조건에 부합하는 첫번째 아이템 인덱스를 반환
var item = findItemIndexArray(numbers, function(item) {
   return item > 105;
});

console.log(item); // 3

ES6+ 부터는 .findIndex() 메서드를 사용해 손쉽게 매칭되는 아이템의 인덱스를 찾을 수 있습니다.

numbers.findIndex(number => number > 105); // 3

indexOf vs findIndex

indexOffindIndex은 아이템의 인덱스를 반환한다는 점에서 비슷해보이지만, 매칭되는 값을 비교하는 처리에서 다소 차이가 있습니다. indexOf는 완전하게 동일한 값을 비교하여 인덱스를 반환하는 반면, findIndex는 전달되는 콜백함수 내부에서 비교(예: Object.is()) 조건을 설정하여 특정 조건에 매칭되는 값의 인덱스를 반환 받을 수 있습니다.

// Array.prototype.indexOf
// === 비교
// NaN === NaN // false
[false, 10, NaN, {}].indexOf(NaN); // -1

// Array.prototype.findIndex
// Object.is() 비교
// Object.is(NaN, NaN) // true
[false, 10, NaN, {}].findIndex(x => Object.is(x, NaN)); // 2

Array.prototype.includes()

기존 코드는 특정 아이템이 배열의 아이템인지 알아내기 위해 별도로 헬퍼 함수를 만들어 사용했습니다.

var numbers = [100, 105, 103, 109];

// 배열 아이템이 포함 되었는지 확인하는 유틸리티 함수
function isIncludeItemArray(array, item) {
  return array.indexOf(item) > -1;
}

// 유틸리티 함수를 사용해 아이템이 포함 되었는지 유무 확인
if ( !isIncludeItemArray(numbers, 107) ) {
  numbers.push(107);
}

ES6+ 부터는 .includes() 메서드를 사용해 손쉽게 아이템의 포함 유무를 확인할 수 있습니다.

if (!numbers.includes(107)) {
  numbers.push(107);
}

Array.prototype.fill()

기존 코드는 특정 아이템으로 배열의 아이템을 모두 채우거나 특정 범위만 채우고자 할 경우 별도로 헬퍼 함수를 만들어 사용했습니다.

var numbers = [100, 105, 103, 109];

// 배열 아이템을 모두 동일하게 채우는 유틸리티 함수
function fillItemArray(array, item, start, end) {
  start = start || 0;
  end = end || array.length;
  return array.map(function(t, i){
    if ( i >= start && i < end ) {
      return item;
    } else {
      return t;
    }
  });
}

// 유틸리티 함수를 사용해 배열 아이템을 모두 교체
fillItemArray(numbers, {}); // [{}, {}, {}, {}]

// 유틸리티 함수에 start, end 인자를 전달하면
// 조건에 부합하는 아이템만 교체
fillItemArray(numbers, {}, 1, 3); // [100, {}, {}, 109]

ES6+ 부터는 .fill() 메서드를 사용해 손쉽게 배열의 모든 아이템 또는 특정 범위의 아이템을 채울 수 있습니다.

const numbers = [100, 105, 103, 109];

// Array.prototype.fill 메서드를 사용해 배열 아이템을 모두 교체
numbers.fill({}); // [{}, {}, {}, {}]

// Array.prototype.fill 메서드를 사용해 배열 아이템을 부분 교체
numbers.fill({}, 1, 3); // [100, {}, {}, 109]

Array.prototype.copyWithin()

ES6+ 부터 사용 가능한 .copyWithin() 메서드는 배열의 일부를 얕게 복사한 뒤, 동일한 배열의 다른 위치에 덮어쓰고 그 배열을 반환합니다. 이 때, 크기(배열의 길이)를 수정하지 않고 반환합니다.

var numbers = [100, 105, 103, 109];

// Array.prototype.copyWithin(target, start=0, end=this.length)

// 0부터 (4-1)까지 아이템을 복사한 후, 1 위치부터 붙여넣음
numbers.copyWithin(1); // target: 1, start: 0, end: 4
// [100, 105, 103, 109] → [100, 100, 105, 103]

// 0부터 (4-1)까지 아이템을 복사한 후, -2(끝에서 2번째) 위치부터 붙여넣음
numbers.copyWithin(-2); // target: -2, start: 0, end: 4
// [100, 105, 103, 109] → [100, 105, 100, 105]

// 2부터 (4-1)까지 아이템을 복사한 후, 1 위치부터 붙여넣음
numbers.copyWithin(1, 2); // target: 1, start: 2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]

// 1부터 (2-1)까지 아이템을 복사한 후, 2 위치부터 붙여넣음
numbers.copyWithin(2, 1, 2); // target: 2, start: 1, end: 2
// [100, 105, 103, 109] → [100, 105, 105, 109]

// -2부터 -3까지 아이템을 복사한 후, -3(끝에서 3번째) 위치부터 붙여넣음
numbers.copyWithin(-3, -2); // target: -2, start: -2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]

참고