Работая с объектом — мы работаем с его свойствами. Но, прежде чем читать или записывать значения, на практике часто возникает задача проверить, существует ли нужное свойство у объекта. Как это сделать?

hasOwnProperty

obj.hasOwnProperty('propertyName')

Метод hasOwnProperty принимает на вход имя проверяемого свойства и возвращает булево значение: true — если такое свойство есть в объекте, и false — если нет.

const obj = {
  color: 'red',
  height: 10,
};

obj.hasOwnProperty('color'); // true

const propertyName = 'height';
obj.hasOwnProperty(propertyName); // true

obj.hasOwnProperty('unexistent'); // false

Свойства с именем unexistent в объекте obj нет, поэтому метод hasOwnProperty вернул значение false.

Давайте попробуем решить задачку подсчёта количества продуктов в сумке :) Допустим, наша сумка представлена вот таким массивом:

const bag = [
  'apple', 'banana', 'pear',
  'apricot', 'apple', 'banana',
  'apple', 'orange', 'pear',
];

Напишем функцию fruitsCounter, подсчитывающую количество каждого вида фруктов в сумке. То есть функция на основе массива должна сформировать объект, каждый ключ которого — это определённый фрукт, а значение — количество:

fruitsCounter(bag); // { apple: 3, banana: 2, pear: 2, apricot: 1, orange: 1 }

Алгоритм следующий:

  1. Подготавливаем объект-результат — аккумулятор, в котором будем хранить фрукты и их количество.
  2. Перебираем массив фруктов.
  3. Для каждого фрукта определяем, есть ли соответствующее ему свойство в формируемом объекте-результате: 1. Если свойства нет (то есть фрукт ранее не подсчитывали), то добавляем его в объект и присваиваем значение 1 (единица). 1. Если свойство есть (то есть ранее фрукт уже учитывали), то увеличиваем его количество на единицу.
const fruitsCounter = (fruits) => {
  const statistics = {};

  for (const fruit of fruits) {
    if (statistics.hasOwnProperty(fruit)) {
      statistics[fruit] += 1;
    } else {
      statistics[fruit] = 1;
    }
  }

  return statistics;
};

Новички часто совершают ошибку: вместо проверки существования свойства пытаются взять значение у проверяемого свойства и проверить это значение.

// расчёт на особенность JS, что
// обращение к несуществующему свойству
// вернёт значение undefined
if (statistics[fruit] === undefined)

или

// расчёт на то, что undefined
// в логическом контексте — false
if (statistics[fruit])

Но такой подход может привести к логической ошибке и всегда приводит к семантической (смысловой) ошибке в коде. Чтобы избежать этого, соблюдайте общее правило: выполняйте именно то действие, которое диктуется смыслом выполняемой операции. Там, где надо работать со свойствами — работайте со свойствами, а не значениями.

В примере с фруктами логической ошибки не произойдёт, потому что это очень простой пример. Но будет нарушена семантика кода, ведь мы делаем косвенные, опосредованные проверки, то есть работаем со следствиями, а не причинами. Таким образом, мы, как создатели кода, "скрываем" свои истинные намерения от тех, кто будет читать и анализировать его. Проверка if (statistics.hasOwnProperty(fruit)) явно (насколько это позволяют выразительные средства языка) даёт понять, что проверяется существование свойства. Проверка, наподобие, if (statistics[fruit]) может вызвать непонимание и вопросы "зачем так делается?". Да, опытный программист скорее всего разгадает истинный смысл проверки, но в любом случае такой код окажется тяжелее для чтения и восприятия.

Рассмотрим пример с логической ошибкой, она часто появляется при сравнении объектов. Допустим, у нас есть две карты университетских дисциплин для первого и второго семестров:

const firstSemestrSubjects = {
  chemistry: {
    faculty: 'Chemistry faculty',
    teacher: 'Ivanov',
  },
  law: {
    // какие-то характеристики
  },
  informatics: null,
  microeconomics: {
    // какие-то характеристики
  },
};

const secondSemestrSubjects = {
  microeconomics: null,
  chemistry: {
    faculty: 'Chemistry faculty',
    teacher: 'Ivanov',
  },
  informatics: {
    // какие-то характеристики
  },
};

Задача состоит в том, чтобы определить, какие дисциплины первого семестра были убраны во втором семестре. Опять же, это упрощённая задача, но её достаточно, что схватить суть. Условимся, если дисциплина имеет булево значение null, это означает, что она существует, но на момент создания карты данные по ней не были определены или уточнены (возможно, это произойдёт позже) — реальная жизнь накладывает свои сложности. Алгоритм может быть таким:

  1. Подготавливаем массив-результат — аккумулятор, в котором будем вести список убранных дисциплин.
  2. Получаем все дисциплины первого семестра. Диспциплины — это ключи, для получения массива ключей у объекта есть специальный метод Object.keys().
  3. Перебираем дисциплины первого семестра.
  4. Для каждой дисциплины определяем, есть ли она в карте дисциплин второго семестра: 1. Если свойства нет (то есть дисциплина была убрана во втором семестре), то добавляем её в массив результат.
const getRemovedSubjects = (first, second) => {
  const removedSubjects = [];
  const firstSemestrSubjects = Object.keys(first);

  for (const subject of firstSemestrSubjects) {
    if (!second.hasOwnProperty(subject)) {
      removedSubjects.push(subject);
    }
  }

  return removedSubjects;
};

getRemovedSubjects(firstSemestrSubjects, secondSemestrSubjects); // ['law']

Во втором семестре была убрана дисциплина 'law', функция правильно отработала и вернула массив с единственным элементом: ['law'].

Теперь давайтe поэкспериментируем и заменим прямую проверку свойства на косвенную проверку по значению:

const getRemovedSubjects = (first, second) => {
  const removedSubjects = [];
  const firstSemestrSubjects = Object.keys(first);

  for (const subject of firstSemestrSubjects) {
    if (!second[subject]) {
      removedSubjects.push(subject);
    }
  }

  return removedSubjects;
};

getRemovedSubjects(firstSemestrSubjects, secondSemestrSubjects); // ['law', 'microeconomics']

Как видно, алгоритм отработал неправильно — он посчитал, что, помимо дисциплины law, во втором семестре была убрана микроэкономика microeconomics. Это произошло из-за того, что в логическом контексте false-значением является не только undefined, но и null (в js существует ещё куча других false-значений). Таким образом наша косвенная проверка противоречит допущению реальной жизни, что в ключе объекта может быть записано значение null.

Таких ситуаций можно представить множество, сейчас вам надо понять, что надо следовать семантике, использовать для обработки каждого конкретного случая наиболее подходящие функции, а не искать обходных противоречивых путей.

Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →