Как проверить что это массив?

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Легкий
#JavaScript #массивы #База JS

Краткий ответ

Проверить что это массив можно несколькими способами: Array.isArray(value) (рекомендуемый), value instanceof Array, Object.prototype.toString.call(value) === '[object Array]' или проверка свойства constructor. Самый надёжный способ — Array.isArray().

Рекомендуемые методы:

  • Array.isArray() — самый надёжный и быстрый
  • instanceof Array — работает в большинстве случаев
  • toString.call() — универсальный для всех типов

Что значит проверить что это массив

Проверка типа данных — это определение того, является ли переменная массивом. В JavaScript это важно, поскольку массивы имеют особое поведение и методы, отличные от других объектов.

Проблема с typeof

const arr = [1, 2, 3];
const obj = {0: 1, 1: 2, 2: 3, length: 3};
 
console.log(typeof arr); // "object" ❌ Не помогает!
console.log(typeof obj); // "object" ❌ Тот же результат!
 
// typeof не различает массивы и объекты
console.log(typeof null); // "object" ❌ Даже null!

Методы проверки массивов

1. Array.isArray() (рекомендуемый)

const numbers = [1, 2, 3, 4, 5];
const notArray = {0: 1, 1: 2, length: 2};
 
console.log(Array.isArray(numbers)); // true
console.log(Array.isArray(notArray)); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray(undefined)); // false

Преимущества:

  • ✅ Самый надёжный метод
  • ✅ Быстрый и эффективный
  • ✅ Работает с массивами из других фреймов
  • ✅ Стандартный ES5 метод

Недостатки:

  • ❌ Не поддерживается в IE8 и ниже

2. instanceof Array

const fruits = ["apple", "banana", "orange"];
const fakeArray = {0: "apple", length: 1};
 
console.log(fruits instanceof Array); // true
console.log(fakeArray instanceof Array); // false
console.log("string" instanceof Array); // false

Преимущества:

  • ✅ Простой и понятный
  • ✅ Хорошая поддержка браузерами
  • ✅ Работает с наследованием

Недостатки:

  • ❌ Проблемы с iframe и разными контекстами
  • ❌ Может быть обманут изменением prototype

3. Object.prototype.toString.call()

const data = [1, 2, 3];
const notData = "not array";
 
console.log(Object.prototype.toString.call(data)); // "[object Array]"
console.log(Object.prototype.toString.call(notData)); // "[object String]"
 
// Функция-помощник
function isArray(value) {
  return Object.prototype.toString.call(value) === '[object Array]';
}
 
console.log(isArray([1, 2, 3])); // true
console.log(isArray({})); // false

Преимущества:

  • ✅ Работает во всех браузерах
  • ✅ Не зависит от контекста
  • ✅ Универсальный для всех типов

Недостатки:

  • ❌ Более длинная запись
  • ❌ Медленнее других методов

4. Проверка constructor

const items = [1, 2, 3, 4, 5];
 
console.log(items.constructor === Array); // true
 
// Но может быть обманута
const fake = {};
fake.constructor = Array;
console.log(fake.constructor === Array); // true ❌ Ложный результат!

Недостатки:

  • ❌ Может быть изменён
  • ❌ Не работает с null/undefined
  • ❌ Ненадёжный метод

5. Проверка методов массива

function looksLikeArray(value) {
  return value &&
         typeof value === 'object' &&
         typeof value.length === 'number' &&
         typeof value.push === 'function' &&
         typeof value.pop === 'function';
}
 
const realArray = [1, 2, 3];
const arrayLike = {length: 3, push: function() {}, pop: function() {}};
 
console.log(looksLikeArray(realArray)); // true
console.log(looksLikeArray(arrayLike)); // true ❌ Ложный результат!

Недостатки:

  • ❌ Может давать ложные срабатывания
  • ❌ Не проверяет реальную природу объекта

Сравнение производительности

Тест производительности

function performanceTest() {
  const testArray = [1, 2, 3, 4, 5];
  const iterations = 1000000;
  
  // Тест 1: Array.isArray()
  console.time('Array.isArray');
  for (let i = 0; i < iterations; i++) {
    Array.isArray(testArray);
  }
  console.timeEnd('Array.isArray');
  
  // Тест 2: instanceof
  console.time('instanceof');
  for (let i = 0; i < iterations; i++) {
    testArray instanceof Array;
  }
  console.timeEnd('instanceof');
  
  // Тест 3: toString.call
  console.time('toString.call');
  for (let i = 0; i < iterations; i++) {
    Object.prototype.toString.call(testArray) === '[object Array]';
  }
  console.timeEnd('toString.call');
  
  // Тест 4: constructor
  console.time('constructor');
  for (let i = 0; i < iterations; i++) {
    testArray.constructor === Array;
  }
  console.timeEnd('constructor');
}
 
performanceTest();

Результаты (приблизительные)

МетодВремя (мс)НадёжностьПоддержка
Array.isArray()~10ВысокаяES5+
instanceof Array~15СредняяВсе браузеры
toString.call()~25ВысокаяВсе браузеры
constructor~8НизкаяВсе браузеры

Практические примеры

1. Валидация входных данных

class DataProcessor {
  constructor() {
    this.data = [];
  }
  
  // Безопасное добавление данных
  addData(input) {
    if (Array.isArray(input)) {
      this.data.push(...input); // Разворачиваем массив
    } else {
      this.data.push(input); // Добавляем как элемент
    }
  }
  
  // Проверка и обработка
  processInput(value) {
    if (Array.isArray(value)) {
      return value.map(item => this.processItem(item));
    }
    return this.processItem(value);
  }
  
  processItem(item) {
    return typeof item === 'string' ? item.toUpperCase() : item;
  }
}
 
const processor = new DataProcessor();
processor.addData([1, 2, 3]); // Добавит 1, 2, 3
processor.addData("single"); // Добавит "single"
 
console.log(processor.data); // [1, 2, 3, "single"]

2. Универсальная функция итерации

function forEach(collection, callback) {
  // Проверяем что это массив
  if (Array.isArray(collection)) {
    collection.forEach(callback);
    return;
  }
  
  // Проверяем что это объект с length (array-like)
  if (collection && typeof collection.length === 'number') {
    for (let i = 0; i < collection.length; i++) {
      callback(collection[i], i, collection);
    }
    return;
  }
  
  // Обычный объект
  if (typeof collection === 'object' && collection !== null) {
    Object.keys(collection).forEach(key => {
      callback(collection[key], key, collection);
    });
  }
}
 
// Использование
forEach([1, 2, 3], (item, index) => {
  console.log(`Array[${index}]: ${item}`);
});
 
forEach({a: 1, b: 2}, (value, key) => {
  console.log(`Object[${key}]: ${value}`);
});
 
forEach("hello", (char, index) => {
  console.log(`String[${index}]: ${char}`);
});

3. Система типизированных данных

class TypeChecker {
  static getType(value) {
    // Проверка на null и undefined
    if (value === null) return 'null';
    if (value === undefined) return 'undefined';
    
    // Проверка на массив
    if (Array.isArray(value)) return 'array';
    
    // Проверка на дату
    if (value instanceof Date) return 'date';
    
    // Проверка на регулярное выражение
    if (value instanceof RegExp) return 'regexp';
    
    // Базовые типы
    return typeof value;
  }
  
  static validate(value, expectedType) {
    const actualType = this.getType(value);
    
    if (actualType !== expectedType) {
      throw new Error(
        `Expected ${expectedType}, got ${actualType}`
      );
    }
    
    return true;
  }
  
  static isArrayOf(array, type) {
    if (!Array.isArray(array)) {
      return false;
    }
    
    return array.every(item => this.getType(item) === type);
  }
}
 
// Использование
try {
  TypeChecker.validate([1, 2, 3], 'array'); // ✅ Пройдёт
  TypeChecker.validate("string", 'array'); // ❌ Ошибка
} catch (error) {
  console.error(error.message);
}
 
console.log(TypeChecker.isArrayOf([1, 2, 3], 'number')); // true
console.log(TypeChecker.isArrayOf([1, "2", 3], 'number')); // false

Задачи для практики

Задача 1

const value1 = [];
const value2 = {};
const value3 = null;
 
console.log(Array.isArray(value1));
console.log(Array.isArray(value2));
console.log(Array.isArray(value3));
Ответ true, false, false — только пустой массив является массивом.

Задача 2

const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
 
console.log(Array.isArray(arrayLike));
console.log(arrayLike instanceof Array);
console.log(typeof arrayLike);
Ответ false, false, "object" — объект похожий на массив не является массивом.

Задача 3

const arr = [1, 2, 3];
arr.constructor = Object;
 
console.log(Array.isArray(arr));
console.log(arr instanceof Array);
console.log(arr.constructor === Array);
Ответ true, true, false — изменение constructor не влияет на Array.isArray() и instanceof.

Современные возможности

1. Использование с TypeScript

// Типизированная проверка
function processData<T>(data: T | T[]): T[] {
  if (Array.isArray(data)) {
    return data; // TypeScript знает что это T[]
  }
  return [data]; // Оборачиваем в массив
}
 
// Использование
const numbers = processData(5); // number[]
const strings = processData(["a", "b"]); // string[]

2. Проверка с деструктуризацией

function handleInput(input) {
  // Проверяем и сразу деструктурируем
  if (Array.isArray(input)) {
    const [first, second, ...rest] = input;
    console.log('Первый:', first);
    console.log('Второй:', second);
    console.log('Остальные:', rest);
  } else {
    console.log('Не массив:', input);
  }
}
 
handleInput([1, 2, 3, 4, 5]);
handleInput("строка");

3. Проверка с async/await

class AsyncArrayProcessor {
  async processArrays(data) {
    if (!Array.isArray(data)) {
      throw new Error('Ожидался массив');
    }
    
    const results = await Promise.all(
      data.map(async (item) => {
        // Имитация асинхронной обработки
        await new Promise(resolve => setTimeout(resolve, 100));
        return item * 2;
      })
    );
    
    return results;
  }
}
 
const processor = new AsyncArrayProcessor();
 
// Использование
processor.processArrays([1, 2, 3, 4, 5])
  .then(results => console.log('Результаты:', results))
  .catch(error => console.error('Ошибка:', error.message));

Лучшие практики

✅ Рекомендации

// 1. Используйте Array.isArray() как основной метод
function isArray(value) {
  return Array.isArray(value); // ✅ Лучший выбор
}
 
// 2. Создавайте типизированные проверки
function isArrayOf(array, type) {
  return Array.isArray(array) && 
         array.every(item => typeof item === type);
}
 
// 3. Документируйте ожидаемые типы
/**
 * Обрабатывает массив чисел
 * @param {number[]} numbers - Массив чисел
 * @returns {number} Сумма чисел
 */
function sum(numbers) {
  if (!Array.isArray(numbers)) {
    throw new TypeError('Ожидался массив чисел');
  }
  return numbers.reduce((acc, num) => acc + num, 0);
}
 
// 4. Используйте полифилл для старых браузеров
if (!Array.isArray) {
  Array.isArray = function(value) {
    return Object.prototype.toString.call(value) === '[object Array]';
  };
}

❌ Чего избегать

// ❌ Не используйте typeof для массивов
if (typeof value === 'object') {
  // Плохо! Объекты, null тоже object
}
 
// ❌ Не полагайтесь только на constructor
if (value.constructor === Array) {
  // Ненадёжно! Может быть изменён
}
 
// ❌ Не проверяйте только наличие методов
if (value && value.push && value.pop) {
  // Может дать ложные срабатывания
}
 
// ❌ Не используйте instanceof в iframe
if (value instanceof Array) {
  // Может не работать между фреймами
}

Частые ошибки

1. Путаница с array-like объектами

// ❌ Проблема
function processItems(items) {
  // Предполагаем что это массив
  return items.map(item => item * 2); // Ошибка для NodeList!
}
 
const elements = document.querySelectorAll('div'); // NodeList
processItems(elements); // TypeError: items.map is not a function
 
// ✅ Решение
function processItems(items) {
  // Проверяем и конвертируем
  const array = Array.isArray(items) ? items : Array.from(items);
  return array.map(item => item * 2);
}

2. Неправильная обработка null/undefined

// ❌ Проблема
function getLength(value) {
  if (value instanceof Array) {
    return value.length; // Ошибка если value = null
  }
  return 0;
}
 
console.log(getLength(null)); // TypeError!
 
// ✅ Решение
function getLength(value) {
  if (Array.isArray(value)) {
    return value.length; // Безопасно
  }
  return 0;
}

3. Проблемы с вложенными проверками

// ❌ Проблема
function processNestedData(data) {
  if (Array.isArray(data)) {
    return data.map(item => {
      // Забыли проверить что item тоже может быть массивом
      return item.toUpperCase(); // Ошибка если item = []
    });
  }
}
 
// ✅ Решение
function processNestedData(data) {
  if (Array.isArray(data)) {
    return data.map(item => {
      if (Array.isArray(item)) {
        return processNestedData(item); // Рекурсия
      }
      return typeof item === 'string' ? item.toUpperCase() : item;
    });
  }
  return data;
}

Резюме

Проверка массивов — это важная операция в JavaScript с несколькими подходами:

Основные методы:

  • Array.isArray() — самый надёжный и быстрый
  • instanceof Array — работает в большинстве случаев
  • toString.call() — универсальный для всех типов
  • constructor — ненадёжный, избегайте

Выбор метода зависит от:

  • 🚀 ПроизводительностиArray.isArray() самый быстрый
  • 🔒 НадёжностиArray.isArray() самый надёжный
  • 🌐 Поддержки браузеров — все методы кроме Array.isArray() в IE8-
  • 🎯 Контекста — iframe могут создавать проблемы

Рекомендации:

// Для большинства случаев
Array.isArray(value);
 
// Для старых браузеров с полифиллом
if (!Array.isArray) {
  Array.isArray = function(value) {
    return Object.prototype.toString.call(value) === '[object Array]';
  };
}
 
// Для типизированных проверок
function isArrayOf(array, type) {
  return Array.isArray(array) && array.every(item => typeof item === type);
}

Понимание различий между методами — это основа для надёжной работы с типами в JavaScript!


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляйте сайт в закладки и совершенствуйтесь каждый день 💪