본문 바로가기
카테고리 없음

Array.prototype.map()

by 용용이아바이 2024. 4. 23.
728x90

map() 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환한다.

const array1 = [1, 4, 9, 16];

// Pass a function to map
const map1 = array1.map((x) => x * 2);

console.log(map1);
// Expected output: Array [2, 8, 18, 32]

 

    arr.map(callback(currentValue[, index[, array]])[, thisArg])
  1. callback: 새로운 배열 요소를 생성하는 함수. 다음 세 가지 인수를 가집니다.
  2. currentValue: 처리할 현재 요소.
  3. index (Optional): 처리할 현재 요소의 인덱스.
  4. array (Optional): map()을 호출한 배열.
  5. thisArg (Optional): callback을 실행할 때 this로 사용되는 값.
  6. 반환 값:  배열의 각 요소에 대해 실행한 callback의 결과를 모은 새로운 배열.

 

map은 callback 함수를 각각의 요소에 대해 한번씩 순서대로 불러 그 함수의 반환값으로 새로운 배열을 만든다. callback 함수는 (undefined도 포함해서) 배열 값이 들어있는 인덱스에 대해서만 호출된다. 즉, 값이 삭제되거나 아직 값이 할당/정의되지 않은 인덱스에 대해서는 호출되지 않는다.

callback 함수는 호출될 때 대상 요소의 값, 그 요소의 인덱스, 그리고 map을 호출한 원본 배열 3개의 인수를 전달받는다.

thisArg 매개변수가 map에 전달된 경우 callback 함수의 this값으로 사용된다. 그 외의 경우 undefined값이 this 값으로 사용됩니다. callback 함수에서 최종적으로 볼 수 있는 this 값은 함수 내 this를 정하는 일반적인 규칙에 따라 결정된다.

map은 호출한 배열의 값을 변형하지 않습니다. 단, callback 함수에 의해서 변형될 수는 있다.

map이 처리할 요소의 범위는 첫 callback을 호출하기 전에 정해진다. map이 시작한 이후 배열에 추가되는 요소들은 callback을 호출하지 않는다. 배열에 존재하는 요소들의 값이 바뀐 경우 map이 방문하는 시점의 값이 callback에 전달된다. map이 시작되고, 방문하기 전에 삭제된 요소들은 방문하지 않는다.

명세서에 정의된 알고리즘으로 인해 map을 호출한 배열의 중간이 비어있는 경우, 결과 배열 또한 동일한 인덱스를 빈 값으로 유지한다.

배열에 들어있는 숫자들의 제곱근을 구하여 새로운 배열을 만들기

다음 코드는 숫자의 배열을 받아 각 숫자들의 제곱근이 들어있는 새로운 배열을 만든다.

var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// roots는 [1, 2, 3]
// numbers는 그대로 [1, 4, 9]

map을 활용해 배열 속 객체를 재구성하기

다음 코드는 오브젝트의 배열을 받아 각 오브젝트를 다른 형태으로 재구성해 새로운 배열을 만든다.

var kvArray = [
  { key: 1, value: 10 },
  { key: 2, value: 20 },
  { key: 3, value: 30 },
];

var reformattedArray = kvArray.map(function (obj) {
  var rObj = {};
  rObj[obj.key] = obj.value;
  return rObj;
});
// reformattedArray는 [{1:10}, {2:20}, {3:30}]

// kvArray는 그대로
// [{key:1, value:10},
//  {key:2, value:20},
//  {key:3, value: 30}]

인자를 받는 함수를 사용하여 숫자 배열 재구성하기

다음 코드는 인자가 한개인 함수를 이용하여 map이 어떻게 동작하는지 나타낸다. 인자인 배열과 안의 요소들은 map을 통해 순회하면서 원본 배열로 부터 자동으로 할당된다.

var numbers = [1, 4, 9];
var doubles = numbers.map(function (num) {
  return num * 2;
});
// doubles는 이제 [2, 8, 18]
// numbers는 그대로 [1, 4, 9]

map을 포괄적으로 사용하기

아래 예제는 String에 map을 사용해서 각 문자의 ASCII 인코딩 값을 요소로 갖는 배열을 얻는 방법을 보여준다.

var map = Array.prototype.map;
var a = map.call("Hello World", function (x) {
  return x.charCodeAt(0);
});
// a는 이제 [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

map을 포괄적으로 사용하기 (querySelectorAll)

아래 예제는 querySelectorAll을 사용해서 수집된 객체들을 순회 처리하는 법을 보여준다. 이번 경우 체크한 옵션 박스를 콘솔에 프린트한다.

var elems = document.querySelectorAll("select option:checked");
var values = [].map.call(elems, function (obj) {
  return obj.value;
});

더 쉬운 방법은 Array.from()을 사용하는 것이다.

map에 하나의 인자(순회하는 원소)만 받는 콜백을 사용하는 경우가 많다. 그러나 어떤 함수는 대개 하나의 인자로 호출하지만 두 개 이상의 인자를 사용하는 경우도 있다. 이로 인해 어떤 경우 혼란스러울 수도 있다.

// 아래 라인을 보면...
["1", "2", "3"].map(parseInt);
// 결과를 [1, 2, 3] 으로 기대할 수 있다.
// 그러나 실제 결과는 [1, NaN, NaN] 이다.

// parseInt 함수는 보통 하나의 인자만 사용하지만, 두 개를 받을 수 있다.
// 첫 번째 인자는 변환하고자 하는 표현이고 두 번째는 숫자로 변환할 때 사용할 진법이다.
// Array.prototype.map은 콜백에 세 가지 인자를 전달한다.
// 배열의 값, 값의 인덱스, 그리고 배열
// 세 번째 인자는 parseInt가 무시하지만 두 번째 인자는 아니다.
// 따라서 혼란스러운 결과를 도출할 수 있다.

function returnInt(element) {
  return parseInt(element, 10);
}

["1", "2", "3"].map(returnInt); // [1, 2, 3]
// 실제 결과가 예상한 대로 배열의 숫자와 같다.

// 위와 같지만 더 간단한 화살표 표현식
["1", "2", "3"].map((str) => parseInt(str));

// 더 간단하게 해결할 수 있는 방법
["1", "2", "3"].map(Number); // [1, 2, 3]
// 그러나 `parseInt`와 달리 float이나 지수표현도 반환한다.
["1.1", "2.2e2", "3e300"].map(Number); // [1.1, 220, 3e+300]

폴리필

map은 ECMA-262 표준 제5판에 추가됐다. 따라서 어떤 표준 구현체에서는 사용할 수 없을 수도 있다. 다른 모든 코드 이전에 아래 코드를 포함하면 지원하지 않는 플랫폼에서도 map을 사용할 수 있다. 아래 알고리즘은 Object, TypeError, Array가 변형되지 않고, callback.call의 계산 값이 원래의 Function.prototype.call()과 같은 경우 ECMA-262 제5판이 명시한 것과 동일하다.

// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {
  Array.prototype.map = function (callback, thisArg) {
    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. Let O be the result of calling ToObject passing the |this|
    //    value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get internal
    //    method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If IsCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== "function") {
      throw new TypeError(callback + " is not a function");
    }

    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (arguments.length > 1) {
      T = thisArg;
    }

    // 6. Let A be a new array created as if by the expression new Array(len)
    //    where Array is the standard built-in constructor with that name and
    //    len is the value of len.
    A = new Array(len);

    // 7. Let k be 0
    k = 0;

    // 8. Repeat, while k < len
    while (k < len) {
      var kValue, mappedValue;

      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty internal
      //    method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {
        // i. Let kValue be the result of calling the Get internal
        //    method of O with argument Pk.
        kValue = O[k];

        // ii. Let mappedValue be the result of calling the Call internal
        //     method of callback with T as the this value and argument
        //     list containing kValue, k, and O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Call the DefineOwnProperty internal method of A with arguments
        // Pk, Property Descriptor
        // { Value: mappedValue,
        //   Writable: true,
        //   Enumerable: true,
        //   Configurable: true },
        // and false.

        // In browsers that support Object.defineProperty, use the following:
        // Object.defineProperty(A, k, {
        //   value: mappedValue,
        //   writable: true,
        //   enumerable: true,
        //   configurable: true
        // });

        // For best browser support, use the following:
        A[k] = mappedValue;
      }
      // d. Increase k by 1.
      k++;
    }

    // 9. return A
    return A;
  };
}
728x90