Основы создания объектов и работы с ними в JavaScript

Александр Мальцев
Александр Мальцев
28K
9
Содержание:
  1. Что такое объекты?
  2. Литеральный синтаксис
  3. Свойства
  4. Методы
  5. Краткая запись свойств
  6. Вычисляемые свойства
  7. Копирование и сравнение объектов
  8. Свойства объектов и их конфигурация
  9. Динамические свойства
  10. Преобразование объекта в массив
  11. Задачи
  12. Комментарии

Статья, в которой познакомимся с понятием объекта. Рассмотрим, как осуществляется создание собственных объектов. Разберём массив объектов и приёмы работы с ним.

Что такое объекты?

Объект – это один из типов данных в JavaScript, который предназначен для хранения коллекции различных значений и более сложных сущностей.

Всего в JavaScript существует 8 типов данных: Number, BigInt, String, Boolean, null, undefined, Symbol и Object.

Но все кроме объекта являются примитивными. При присвоении переменной значения примитивного типа, оно хранится в ней непосредственно:

// переменная mark содержит значение примитивного типа в данном случае число
let mark = 4;
// присваиваем переменной mark новое примитивное значение, а именно строку 'Good!'
mark = 'Good!';

Когда мы хотим одной переменной присвоить значение другой, содержащей примитивный тип, копирование осуществляется по значению (по английски – copy by value):

const mark = 4;
// создание newMark и присвоение ей значения переменной mark
let newMark = mark;
// присвоим newMark новое значение
newMark = 5;

console.log(mark); // 4
console.log(newMark); // 5

Здесь мы видим как происходит копирование значений примитивных типов, то есть непосредственно по значению. Если бы переменная mark содержала объект, то процесс копирования осуществлялся бы уже не по значению. Это очень важный момент, но об этом немного позже.

Так что же такое объект? В JavaScript объект – это набор свойств «имя: значение». При этом имена ещё очень часто называют ключами. При этом значение свойства может содержать что угодно. Если в качестве значения используется функция, то такое свойство называют методом.

Объекты – это ссылочный тип. Поэтому, когда вы присваиваете объект переменной, вы на самом деле присваиваете ей не сам этот объект, а ссылку на него, которая указывает на то место в памяти компьютера, где он находится.

Литеральный синтаксис

Литеральный синтаксис объявления объекта – это один из самых простых способов его создания. Обычно он используется, когда необходимо объявить только один объект.

Литерал объекта записывается с помощью фигурных скобок {...}, внутрь которых помещают свойства, отделенные друг от друга посредством запятой:

const person = {
  firstName: 'Александр',
  lastName: 'Мальцев',
  age: 28,
  getFullName: function() {
    return `${this.firstName} ${this.lastName}`
  }
}

В этом примере объект содержит четыре свойства: firstName, lastName, age и getFullName. При этом свойство getFullName является методом, так какие его значение – это функция.

Новый формат записи методов выполняется без использования ключевого слова function и двоеточия:

const person = {
  // ...
  getFullName() {
    return `${this.firstName} ${this.lastName}`
  }
}

Объекты в JavaScript, как и в других языках программирования, легче понять, если провести аналогию с объектами «реального мира». При этом любой объект имеет характеристики (свойства) и может выполнять некоторые задачи (методы).

Например, объект car имеет свойства: color, engine и model. Свойства – это характеристики объекта, они обычно описываются с помощью существительных и прилагательных. Также объект имеет собственные методы: drive, park, start и stop. Методы – это поведение объекта, его функции, они обычно обозначаются посредством глаголов.

Понятие объекта в JavaScript
// объект car
const car = {
  color: 'orange',
  engine: '3.6L V6',
  model: 'AL3',
  drive() {
    console.log('driving');
  },
  park() {
    console.log('parking');
  },
  start() {
    console.log('starting');
  },
  stop() {
    console.log('stopping');
  }
}

Свойства

Свойства состоят из ключа и значения. Отделяется ключ от значения посредством двоеточия. Свойства подобны переменным, но в составе объекта.

Обращение к свойствам выполняется через точку или квадратные скобки:

// через точку
const firstName = person.firstName;
const lastName = person.lastName;
// через квадратные скобки, ключ в этом случае нужно указывать в виде строки
const age = person['age'];
const getFullName = person['getFullName'];

Значения, связанные с соответствующими ключами можно не только получить, но и присвоить им новые значения:

// изменим значения свойств firstName и lastName
person.firstName = 'Иван';
person.lastName = 'Михайлов';

А также добавить новые свойства объекту:

// добавим к объекту person свойство middleName со значением 'Сергеевич'
person.middleName = 'Сергеевич';

Свойствам можно устанавливать какие угодно значения. Например, присвоим свойству children массив объектов:

person.children = [
  {
    name: 'Аня',
    age: 8
  },
  {
    name: 'Ваня',
    age: 14
  }
]

Удаление свойств из объекта осуществляется с помощью оператора delete:

// удалим свойство middleName из объекта person
delete person.middleName;

Проверить наличия ключа в объекте можно посредством оператора in:

'firstName' in person // true
'middleName' in person // false

Обратите внимание, что на самом деле имена свойств в объекте являются строками. Но их можно не заключать в кавычки, как мы это делали выше. Но, это допустимо только в том случае, если они составлены по правилам именования переменных.

Имена свойств, которые составлены не по правилам именования переменных:

const someObj = {
  '': 1, // имя является пустой строкой
  'author of post': 'Алексей', // имя состоит из нескольких слов
}

Обратиться к таким свойствам только с помощью квадратных скобок:

// получим значения свойства, имя которой является пустой строкой
const value = someObj[''];

Если ключ содержится в переменной, то получить значение этого свойства можно только через квадратные скобки:

const key = 'author of post';
const value = someObj[key];

Но именовать свойства не по правилам не рекомендуется. Также не рекомендуется использовать в качестве ключей зарезервированные слова JavaScript, несмотря на то, что это допускается.

Методы

Методы, как мы уже отмечали выше – это свойства, у которых значение является функцией. Обращение к ним осуществляется также как к свойствам, то есть через точку или с использованием квадратных скобок:

const person = {
  firstName: 'Александр',
  lastName: 'Мальцев',
  age: 28,
  getFullName() {
    console.log(`${this.firstName} ${this.lastName}`);
  }
}

// создадим переменную getFullName и присвоим ей метод getFullName
const getFullName = person.getFullName;
// тоже самое только через квадратные скобки
// const getFullName = person['getFullName'];

Но, так как методы – это функции, то их соответственно можно вызвать. Методы мы для этого и создаём, чтобы потом их можно было вызывать. Вызов метода выполняется также как функции, т.е. с использованием круглых скобок:

// получим метод и присвоим его переменной getFullName
const getFullName = person.getFullName;
// вызовем метод
getFullName();
// или сразу
person.getFullName();
person['getFullName']();

Кроме выполнения действий методы всегда возвращают значения. В данном примере вызов getFullName() возвращает значение undefined, т.к. в нём мы явно не прописали инструкцию return. В этом случае не явно возвращается undefined:

console.log(getFullName()); // undefined

Краткая запись свойств

Очень часто в коде мы переменные используем в качестве значений свойств с тем же именем. Когда имя свойства совпадает с названием переменной мы эту запись можем сделать краткой:

// функция, возвращающая объект
const createRect = (width, height) => {
  // возвращаем объект
  return {
    width, // вместо width: width
    height, // вместо height: height
    calcArea() {
      return this.width * this.height;
    }
  }
}
// вызываем функцию и присваиваем возвращённый ей объект переменной rect
const rect = createRect(10, 15);
console.log(rect.calcArea()); // 150

Вычисляемые свойства

В JavaScript имя свойства может быть вычисляемым. То есть для задания имени можно использовать выражение, результат вычисления которого и будет это имя. Указывать вычисляемое свойство необходимо в квадратных скобках []:

const key = 'url';
const app = {
  name: 'Yandex',
  [key]: 'https://yandex.ru/', // имя свойства будет взято из переменной key
};
console.log(app.url); // 'https://yandex.ru/'

Пример вычисляемого свойства, а точнее метода с более сложным выражением:

const key = 'url';
const app = {
  name: 'Yandex',
  [key]: 'https://yandex.ru/',
  ['get' + key.toUpperCase()]() {
    return this[key];
  }
};
console.log(app.getURL()); // 'https://yandex.ru/'

Копирование и сравнение объектов

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

// присвоим переменной student1 объект, а точнее ссылку на него
const student1 = { name: 'Carl' };
// присвоим объект, содержащийся в student1 переменной student2
const student2 = student1;

Теперь student1 и student2 содержат ссылки, указывающие на один и тот же объект.

Изменим значение name, используя student2:

student2.name = 'Nelly';

Получим значение name через student1:

console.log(student1.name); // Nelly

А что если нам необходимо скопировать не саму ссылку, а создать новый объект с такими же свойствами?

const student3 = {};
for (const key in student1) {
  student3[key] = student1[key];
}
// student3 содержит клон объекта student1
student3.name = 'Thyra';
// в student1 значение name осталось прежним
console.log(student1.name); // Nelly

Другой способ скопировать свойства – это воспользоваться методом Object.assign():

// скопируем все свойства из student1 в {}, а затем присвоим его student4
const student4 = Object.assign({}, student1);

Object.assign() позволяет скопировать свойства из множества объектов. Объект, в который нужно скопировать указывается в качестве первого аргумента, а те из которых – после него:

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
console.log(target); // {a: 1, b: 2, c: 3}

Сравнение объектов выполняется по ссылкам:

let objA = {};
let objB = objA;
let objC = {};

console.log( objA === objB ); // true, т.к. переменные содержат одну и ту же ссылку
console.log( objA === objC ); // false, т.к. переменные содержат разные ссылки (оба объекта пусты, но это разные объекты)

Свойства объектов и их конфигурация

До этого времени мы рассматривали свойства как пары «ключ: значение».

Но свойство кроме значения (value) имеет специальные флаги:

  • writable – доступно ли свойство для изменения;
  • enumerable – доступно ли свойство для перебора в циклах;
  • configurable – доступно ли свойство для настройки и удаления.

Когда мы создаем литерал объекта, то все эти специальные флаги у свойства имеют значение true. Получить полное описание свойства можно с помощью метода Object.getOwnPropertyDescriptor:

const flower = {
  name: 'rose',
  color: 'red'
}
const descriptor = Object.getOwnPropertyDescriptor(flower, 'name');
console.log(descriptor);
Получение полного описания свойства в JavaScript с помощью метода Object.getOwnPropertyDescriptor

Как вы наверно уже поняли, этот метод позволяет получить полное описание одного свойства. Для этого в Object.getOwnPropertyDescriptor мы должны в качестве первого аргумента передать объект, содержащий это свойство, а посредством второго – его само.

Получить описание сразу всех свойств объекта можно с помощью статического метода Object.getOwnPropertyDescriptors:

const descriptors = Object.getOwnPropertyDescriptors(flower);
console.log(descriptors);
Получение полного описания всех свойств объекта с помощью метода Object.getOwnPropertyDescriptors в JavaScript

Для того чтобы установить новые значения флагам конкретному свойству объекта необходимо использовать метод Object.defineProperty. Например, сделаем свойство name доступным только для чтения:

Object.defineProperty(flower, 'name', {
  writable: false
});
// попробуем изменить значение свойства name
flower.name = 'lily';
console.log(flower); // { name: 'rose', color: 'red' }
Установка новых значений флагам свойства посредством метода Object.defineProperty в JavaScript

В этом примере свойство name мы сделали не доступным для изменения. Для этого мы флагу writable установили значение false.

Для определения сразу нескольких свойств можно применять метод Object.defineProperties:

Object.defineProperties(flower,
  name: { writable: false, configurable: false },
  color: { enumerable: false }
);

Так как свойству color мы установили флаг enumerable: false, то теперь оно не будет доступно для перебора:

for (let key in flower) {
  console.log(key);
}
Создание не перечисляемого свойства с помощью Object.defineProperties в JavaScript

Так же этого свойства не будет в массиве ключей, который возвращает метод Object.keys:

console.log(Object.keys(flower)); // ['name']

Если свойству установить флаг configurable: false, то оно становится не конфигурируемым. Такое свойство нельзя будет удалить и его флагам нельзя будет установить новые значения. Таким свойствам мы сделали name.

При попытке установить этому свойству новые значения флагам мы получим ошибку:

Ошибка при установлении не конфигурируемому свойству новые значения флагам с помощью Object.defineProperty в JavaScript

Также мы не можем удалить это свойство:

delete flower.name; // false
console.log(flower); // {name: 'rose', color: 'red'}

Методы Object.defineProperty и Object.defineProperties можно использовать для добавления новых свойств объекту:

const city = {};
Object.defineProperties(city, {
  name: {value: 'New York', enumerable: true, writable: true},
  area: {value: 1223.59, enumerable: true, writable: true},
});
console.log(Object.getOwnPropertyDescriptors(city));
Добавление новых свойств в объект с помощью Object.defineProperties на языке JavaScript

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

Динамические свойства

В JavaScript кроме обычных свойств имеются ещё динамические. Динамическое свойство похоже на обычное, но по факту представляет собой комбинацию сеттера и геттера.

Геттер – это метод, который выполняется, когда мы хотим получить значение динамического свойства. Он не имеет параметров и возвращает значение, которое и будет значением этого свойства.

Сеттер – это метод, который выполняется, когда мы хотим присвоить значение динамическому свойству. Он имеет один параметр, который получает это значение. Далее это значение мы можем присвоить некоторому свойству или выполнить какие-либо другие действия.

При литеральном объявлении объекта они обозначаются с помощью get и set:

const lang = {
  history: [],
  // сеттер
  get current() {
    return this.history.length ? this.history[this.history.length - 1] : null;
  },
  // геттер
  set current(value) {
    this.history.push(value);
  }
}
console.log(lang.current);
lang.current = 'ru';
console.log(lang.current); // ru
lang.current = 'en';
console.log(lang.current); // en

В этом примере мы создали динамическое свойство current. Геттер выполняется при чтении значения этого свойства, а сеттер – при записи.

У динамического свойства не обязательно должны быть два метода. Оно может также иметь только геттер или сеттер.

Определять сеттеры и геттеры можно также с помощью Object.defineProperty и Object.defineProperties:

const rect = {
  a: 10,
  b: 15
}
Object.defineProperty(rect, 'sides', {
  get() {
    return `${this.a} x ${this.b}`;
  },
  set(value) {
    [this.a, this.b] = value;
  }
});
console.log(rect.sides); // '10 x 15'
rect.sides = [20, 25];
console.log(rect.sides); // '20 x 25'
Добавление сеттеров и геттеров в объект с помощью Object.defineProperty на языке JavaScript

Динамические свойства не имеет value и флага writable, но вместо этого у них есть методы get и set.

В этом примере создадим геттер вместе с описанием других свойств, который будет возвращать последнее сообщение:

const messages = {}
Object.defineProperties(messages, {
  list: { value: [] },
  add: {
    value: function (item) {
      this.list.push(item)
    }
  },
  // геттер last
  last: {
    get() {
      return this.list.length > 0 ? this.list[this.list.length - 1] : '';
    }
  }
});
messages.add('Green');
messages.add('Red');
console.log(messages.last); // 'Red'

Преобразование объекта в массив

В стандартном конструкторе Object имеются методы keys и value с помощью которых можно очень просто трансформировать объект соответственно в массиве ключей и значений.

const car = {
  brand: 'Ford',
  color: 'blue'
}

const keys = Object.keys(car); // ['brand', 'color']
const values = Object.keys(car); // ['Ford', 'blue']

После этого, так как это массивы мы можем очень просто выполнять любые другие действия над ними, то есть перебирать, искать нужные элементы, фильтровать и так далее.

Пример перебора объекта с помощью forEach:

Object.keys(car).forEach((key) => {
  console.log(`${key}: ${car[key]}`);
});

Если нужно перебрать только значения, то так:

Object.values(car).forEach((value) => {
  console.log(value);
});

Кроме этого в Object имеется также метод entries, массив массивов, в котором первый элемент будет являться именем свойства, а второй значением:

const car = {
  brand: 'Ford',
  color: 'blue'
}
const entries = Object.entries(car); // [['brand', 'Ford'], ['color', 'blue']]
entries.forEach((item) => {
  console.log(`${item[0]}: ${item[1]}`);
});

Задачи

Задача 1. Необходимо создать из имеющихся на странице заголовков <h2> массив объектов следующего вида:

[
  { id: 'h2-1', textContent: 'Первая программа' },
  { id: 'h2-2', textContent: 'Выражения' },
  ...
]

Задание: найти в этом массиве максимальную длину свойства textContent и вывести это значение в консоль.

Массив объектов – это обычный массив, элементами которого являются объекты. Здесь id – это значение атрибута id тега <h2>, а text – это его содержимое.

Для получения всех элементов с тегом <h2> необходимо использовать метод document.querySelectorAll('h2'), а прочитать значения, необходимые для установки id и textContent, можно соответственно с помощью свойств с таким же названием.

После этого необходимо перебрать массив, например, с помощью метода reduce и найти максимальную длину заголовка.

Решение:

const maxLength = headers.reduce((max, item) => {
  return item.textContent.length > max ? item.textContent.length : max;
}, 0);
console.log(maxLength);

Задача 2. Имеется массив объектов. Необходимо удалить из массива объекты, у которых значение свойства x или y меньше нуля.

// массив объектов
const points = [
  {x:  5, y:  7},
  {x: -2, y:  9},
  {x:  0, y:  8},
  {x: -1, y: -3}
]

Решить задачу необходимо 2 способами:

  • посредством изменения оригинального массива;
  • не изменяя оригинальный массив.

Пример решение задачи посредством изменения оригинального массива:

let i = points.length - 1;
while (i gt:= 0) {
  if (points[i].x < 0 || points[i].y < 0) {
    // points.splice(i, 1);
  }
  i--;
}
console.log(points); // [{x: 5, y: 7}, {x: 0, y: 8}]

Пример решения, в котором мы не изменяем оригинальный массив:

const newPoints = points.filter((value) => value.x >= 0 && value.y >= 0);
console.log(newPoints); // [{x: 5, y: 7}, {x: 0, y: 8}]

Задача 3. Имеется список статей в виде массива объектов:

const listArticles = [
  { title: "Статья 7", likes: 15 },
  { title: "Статья 1", likes: 10 },
  { title: "Статья 5", likes: 3 },
  { title: "Статья 3", likes: 20 }
];

Необходимо отсортировать массив объектов по полю likes.

Решение:

listArticles.sort((article1, article2) => {
  return article1.likes < article2.likes ? -1 : article1.likes > article2.likes ? 1 : 0;
});

Задача 4. Имеется следующий массив:

const persons = [
  { name: 'John', age: 25 },
  { name: 'Leonardo', age: 15 },
  { name: 'Kristina', age: 12 }
];

Необходимо создавать новый массив, оставить в нём только те персоны, возраст которых больше или равно 18.

Решение:

const newPersons = persons.filter((value) => value.age >= 18);
console.log(newPersons);

Задача 5. Написать стрелочную функцию, которая будет проверять является ли объект переданным ей в качестве аргумента пустым, то есть не содержит ли он собственные свойства и методы.

Решение:

const isEmpty = (obj) => {
  if (typeof obj === 'object' && obj !== null) {
    return Object.keys(obj).length === 0;
  }
  return null;
}

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

isEmpty({ a: 5 }); // false
isEmpty({}); // true
isEmpty(5); // null
isEmpty(null); // null

Комментарии: 9

  1. Toivo
    Toivo
    2021-10-03 07:11:19
    В JavaScript содержится 8 различных типов данных. Семь из которых являются «примитивными»ю.
    Замените «ю» на.
  1. Александр Мальцев
    Александр Мальцев
    2021-10-04 13:26:51
    Спасибо, поправил.
  • Toivo
    Toivo
    2021-09-08 00:16:38
    // выаодим в консоль значение статического свойства
    1. Александр Мальцев
      Александр Мальцев
      2021-09-09 15:28:26
      Спасибо, поправил.
  • Александр
    Александр
    2019-11-11 23:30:14
    Алекандр, как всегда отличная статья. Два вопроса:

    1) Можно ли на объекте использовать те же методы, что и на массивах, map,filter,reduce или надо преобразовывать объект в массив?
    2) Можно ли удалить объект в объекте. Т.е. если условно стоит задача, находить внутри объекта a у какого из его объектов имеется свойство aaa равное 5 и удалять, но не свойство aaa, а сразу весь подобъект aa?

    const a = {
    aa = {
    aaa: 5,
    aab: 3,
    },

    bb = {
    aaa:8,
    aab: 9,
    }
    }

    1. Александр Мальцев
      Александр Мальцев
      2019-11-12 01:31:08
      Благодарю за отзыв.
      1. Если у объекта и в его цепочки прототипов нет этих методов, то их конечно вызвать нельзя. Чтобы эти методы использовать этот объект конечно нужно привести к массиву.
      2. Удаление свойства осуществляется как обычно даже если оно является объектом:
      delete a.aa
      В примере свойства должны отделяться от значений с помощью двоеточия:
      const a = {
        aa: {
          aaa: 5,
          aab: 3,
        },
        bb: {
          aaa: 8,
          aab: 9,
        }
      }
  • Дмитрий
    Дмитрий
    2016-06-13 22:10:27
    А как удалить все теги из строки, по аналогии с функцией в php — strip_tags
    1. Александр Мальцев
      Александр Мальцев
      2016-06-14 12:17:40
      Попробуйте для этого использовать методы textContent и innerText.
      Можно например для этого создать функцию, которая будет возвращать переданную ей строку, но уже без тегов.
      <script>
      //функция для убирания из строки html тегов
      function strip_tags(html) {
        var tmp = document.createElement("DIV");
        tmp.innerHTML = html;
        return tmp.textContent || tmp.innerText || "";
      }
      // некоторая строка содержащая html код
      var html ='<h2 class="page-header">Понятие объекта</h2>';
      // убрать из строки html-теги
      var output = strip_tags(html);
      // вывести содержимое строки в консоль
      console.log(output);
      </script>
      
  • Иван
    Иван
    2016-04-21 07:58:04
    Очень просто и доходчиво, спасибо автору!