← Назад к вопросам

Реализовать собственный метод Array.prototype.map

2.3 Middle🔥 181 комментариев
#JavaScript Core

Условие

Напишите собственную реализацию метода map для массивов, не используя встроенный Array.prototype.map.

Требования

  1. Функция myMap(arr, callback) должна принимать:

    • arr - исходный массив
    • callback - функция преобразования (element, index, array)
  2. Возвращать новый массив с результатами вызова callback для каждого элемента

  3. Не должна изменять исходный массив

Примеры

const numbers = [1, 2, 3, 4, 5];

myMap(numbers, x => x * 2);
// Результат: [2, 4, 6, 8, 10]

myMap(numbers, (x, i) => x + i);
// Результат: [1, 3, 5, 7, 9]

myMap(["a", "b", "c"], (char, i) => char.repeat(i + 1));
// Результат: ["a", "bb", "ccc"]

Бонус

Добавьте третий параметр thisArg для задания контекста callback.

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Реализация Array.prototype.map

Понимание map

Метод map — один из столпов функционального программирования в JavaScript. Он создаёт новый массив, применяя функцию преобразования к каждому элементу исходного массива. Понимание внутреннего устройства помогает глубже освоить работу с данными.

Базовая реализация

function myMap(arr, callback) {
  const result = [];
  
  for (let i = 0; i < arr.length; i++) {
    result.push(callback(arr[i], i, arr));
  }
  
  return result;
}

С поддержкой thisArg

function myMap(arr, callback, thisArg) {
  const result = [];
  
  for (let i = 0; i < arr.length; i++) {
    // Вызываем callback с заданным контекстом
    result.push(callback.call(thisArg, arr[i], i, arr));
  }
  
  return result;
}

TypeScript версия

function myMap<T, U>(
  arr: T[],
  callback: (element: T, index: number, array: T[]) => U,
  thisArg?: any
): U[] {
  const result: U[] = [];
  
  for (let i = 0; i < arr.length; i++) {
    result.push(callback.call(thisArg, arr[i], i, arr));
  }
  
  return result;
}

Версия с поддержкой разреженных массивов

function myMap(arr, callback, thisArg) {
  // Спецификация ECMAScript требует игнорировать пустые слоты
  const result = new Array(arr.length);
  
  for (let i = 0; i < arr.length; i++) {
    // Проверяем, что элемент существует
    if (i in arr) {
      result[i] = callback.call(thisArg, arr[i], i, arr);
    }
  }
  
  return result;
}

Полная спецификация-совместимая версия

function myMap(arr, callback, thisArg) {
  // Преобразуем в объект (массив может быть array-like)
  if (arr == null) {
    throw new TypeError('arr is null or undefined');
  }
  
  const O = Object(arr);
  const len = parseInt(O.length, 10) || 0;
  
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }
  
  const A = new Array(len);
  
  for (let k = 0; k < len; k++) {
    if (k in O) {
      const mappedValue = callback.call(thisArg, O[k], k, O);
      A[k] = mappedValue;
    }
  }
  
  return A;
}

Примеры использования

// Базовый пример
const numbers = [1, 2, 3, 4, 5];
const doubled = myMap(numbers, x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// С индексом
const indexed = myMap(numbers, (x, i) => x + i);
console.log(indexed); // [1, 3, 5, 7, 9]

// Со строками
const chars = ["a", "b", "c"];
const repeated = myMap(chars, (char, i) => char.repeat(i + 1));
console.log(repeated); // ["a", "bb", "ccc"]

// С объектами
const users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 },
  { name: "Charlie", age: 35 }
];

const names = myMap(users, (user) => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]

// С контекстом (thisArg)
const multiplier = { factor: 10 };
const scaled = myMap([1, 2, 3], function(x) {
  return x * this.factor;
}, multiplier);
console.log(scaled); // [10, 20, 30]

// Цепирование
const result = myMap([1, 2, 3], x => x * 2)
  .reduce((sum, x) => sum + x, 0);
console.log(result); // 12

Версия как метод прототипа

if (!Array.prototype.myMap) {
  Array.prototype.myMap = function(callback, thisArg) {
    if (this == null) {
      throw new TypeError('Array.prototype.myMap called on null or undefined');
    }
    
    const O = Object(this);
    const len = parseInt(O.length) || 0;
    
    if (typeof callback !== 'function') {
      throw new TypeError(`${callback} is not a function`);
    }
    
    const A = new Array(len);
    
    for (let k = 0; k < len; k++) {
      if (k in O) {
        A[k] = callback.call(thisArg, O[k], k, O);
      }
    }
    
    return A;
  };
}

// Использование
const nums = [1, 2, 3];
const mapped = nums.myMap(x => x * 2);
console.log(mapped); // [2, 4, 6]

Сравнение реализаций

// Наивная версия
function naiveMap(arr, cb) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(cb(arr[i], i, arr));
  }
  return result;
}

// С обработкой ошибок
function robustMap(arr, cb, thisArg) {
  if (!Array.isArray(arr)) throw new TypeError('First argument must be an array');
  if (typeof cb !== 'function') throw new TypeError('Callback must be a function');
  
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    result[i] = cb.call(thisArg, arr[i], i, arr);
  }
  return result;
}

// Производительность
const arr = Array.from({ length: 1000 }, (_, i) => i);
console.time('myMap');
for (let i = 0; i < 1000; i++) {
  myMap(arr, x => x * 2);
}
console.timeEnd('myMap');

Пошаговый разбор

// Массив: [1, 2, 3]
// Функция: x => x * 2

// Шаг 1: Создаём пустой результат []
// Шаг 2: i=0, callback(1, 0, [1,2,3]) = 2, result = [2]
// Шаг 3: i=1, callback(2, 1, [1,2,3]) = 4, result = [2, 4]
// Шаг 4: i=2, callback(3, 2, [1,2,3]) = 6, result = [2, 4, 6]
// Шаг 5: Возвращаем [2, 4, 6]

Использование call/apply/bind

function myMap(arr, callback, thisArg) {
  const result = [];
  
  for (let i = 0; i < arr.length; i++) {
    // Все три варианта эквивалентны:
    result.push(callback.call(thisArg, arr[i], i, arr));
    // result.push(callback.apply(thisArg, [arr[i], i, arr]));
    // const bound = callback.bind(thisArg);
    // result.push(bound(arr[i], i, arr));
  }
  
  return result;
}

Различие с другими методами

  • map: преобразует элементы
  • filter: выбирает элементы
  • reduce: агрегирует в одно значение
  • forEach: выполняет побочные эффекты

Ключевые моменты

  • callback.call(thisArg, ...): сохраняем контекст
  • Три параметра callback: element, index, array
  • Новый массив: не изменяем исходный
  • Разреженные массивы: пропускаем пустые слоты
  • Спецификация: следуем ECMAScript стандарту
Реализовать собственный метод Array.prototype.map | PrepBro