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

Объект

Объект — это базовая непримитивная структура данных JavaScript, используемая для хранения пар ключ-значение, произвольного набора свойств и методов. Ключи объекта обычно представляют собой строки, а значения могут быть любого типа. Атрибуты объекта (переменные) называются свойствами, а поведение (функции) называются методами. Значения присваиваются объекту в виде разделенного запятыми списка пар ключ: значение в фигурных скобках { }.

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

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

Доступ к свойствам объекта

Вы можете ссылаться на значения свойств объекта двумя способами — используя синтаксис записи через точку objectName.propertyName или используя синтаксис objectName[‘propertyName’], скобки:

console.log(company.name);     // 'XYZ International'
console.log(company['name']);  // 'XYZ International'

Большую часть времени вы будете использовать запись через точку, но запись в квадратных скобках полезна для нескольких вещей.

  1. Если у вас есть ключ из нескольких слов, вы не можете использовать точечную нотацию, вместо этого вы будете использовать скобочную нотацию:
some_object['multi word key']
some_object['multi-word-key']

2. Вы можете использовать его для переменных

const id = '128F-198G-UBE';
some_object[id] //this will work
some_object.id //this won't work, will get undefined

Изменение значений, добавление и удаление свойств

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

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

company.name = 'ABC Corporation';

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

Добавляем новые свойства к нашему объекту:

company.public = true;

Удалить пару ключ-значение из объекта:

delete company.founders;

сокращенная нотация полезна для объявления объектов на основе некоторых переменных, которые у нас уже есть. Например:

const name = 'Zola';

// option 1
const obj = {
  name: name
}
console.log(obj); // {name: 'Zola'}

// option 2: shorthand notation
const obj = {
  name
}
console.log(obj); // {name: 'Zola'}

У нас есть еще один способ создать объект — с помощью конструктора объекта:

const obj = new Object();
obj.name = 'Zola'
console.log(obj); // {name: 'Zola'}

Конструктор

Что произойдет, если мы захотим иметь два объекта с одинаковыми ключами, но разными значениями? Мы могли бы, конечно, создать объекты, но это было бы немного избыточно. Вместо этого мы можем написать функцию-конструктор и использовать ключевое слово this для ссылки на создаваемый объект. И всякий раз, когда мы используем ключевое слово new, оно создает для нас новый объект.

function Company(name, domain, founded, founders) {
  this.name = name;
  this.domain = domain;
  this.founded = founded;
}

const company1 = new Company('ABC Corp.', 'abccorp.com', 2014);
const company2 = new Company('XYZ Corp.', 'xyzcorp.com', 2001);

console.log(company1); 
// {name: "ABC Corp.", domain: "abccorp.com", founded: 2014}

console.log(company2); 
// {name: "XYZ Corp.", domain: "xyzcorp.com", founded: 2001}

Символ

Символ — это примитивное значение в JavaScript, используемое для уникальных значений. Он создается с помощью функции Symbol(description), которая возвращает уникальный символ. Даже если два символа имеют одинаковое описание, они все равно будут считаться уникальными. Альтернативным синтаксисом для создания символов является использование Symbol.for(key). Это работает так же.

const id = Symbol('id'); 

Два вызова Symbol(‘id’) вернут два разных уникальных идентификатора, и мы можем использовать их как ключи внутри объекта. Поскольку в объекте не может быть дубликатов ключей, в некоторых случаях может пригодиться символ, например, при использовании стороннего API.

Проверить, находится ли ключ в объекте

Чтобы проверить, содержит ли объект ключ, мы можем использовать оператор in или метод hasOwnProperty():

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};
console.log('name' in company); // true
console.log(company.hasOwnProperty('name')); // true

Добавление методов к объектам

Вот как мы можем добавить метод к нашему объекту и вызвать его:

const user = {
  id: 100052,
  name: 'Tomoko',
  user_since: 2004,
  // adding a method say_hello()
  say_hello(){
    console.log('hello');
  }
};
// calling a method using dot notation
user.say_hello(); // hello

Ключевое слово this может использоваться в определениях методов объекта для ссылки на объект, который «владеет» методом. В приведенном ниже примере this ссылается на объект car , поэтому this.model ссылается на свойство car.model а this.make ссылается на свойство car.make.

const car = {
  make: 'Polestar',
  model: 'Polestar 2',
  accelerate() {
    return this.model + ' drives away'
  },
  brake() {
    return this.make + ' pulls up'
  }
};

console.log(car.brake()) // 'Polestar pulls up'

Мы также можем использовать геттеры и сеттеры (функции доступа) с ключевыми словами get и set для извлечения или сохранения значения.

Получить

Функция, вызываемая с пустым списком аргументов для извлечения значения свойства всякий раз, когда выполняется доступ к значению.

Установить

Функция, вызываемая с аргументом, который содержит назначенное значение. Выполняется при каждой попытке изменения указанного свойства.

const user = {
  id: 100052,
  name: 'Tomoko',
  user_since: 2004,
  rating: 4,
  // adding accessor properties
  get get_name(){
    return this.name;
  },
  set set_rating(value) {
    this.rating = value;
  }
};

// we use set and set as standard properties, not methods!
console.log(user.get_name); // Tomoko

console.log(user.rating); // 4
user.set_rating = 5;
console.log(user.rating); // 5

Ссылаясь на приведенный выше пример, важно еще раз подчеркнуть, что get и set используются как свойства доступа, а не методы доступа.

Наследование

Давайте посмотрим, как мы можем заставить объект наследоваться от другого объекта. Мы делаем это с помощью свойства __proto__ и устанавливаем его для объекта, от которого мы хотим наследоваться. Свойство __proto__ — это свойство доступа к Object.prototype, состоящее из функции получения и установки.

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

const company1 = {
  __proto__: company,
  isPublic: true
};

console.log(company1.name); // XYZ International
console.log(company1.isPublic); // true

Итерация по объекту

Существует 3 различных способа перебора объекта.

Когда мы итерируем, есть разные способы обработки свойств в зависимости от того, являются ли они собственным свойством, являются ли они символом или являются ли они свойством, исходящим из __proto__ (цепочка прототипов, которая является унаследованными объектами).

Мы будем использовать 3 разные функции для перебора объекта:

Object.keys() принимает объект и возвращает массив с ключами. Но он не включает все ключи. Во-первых, будут исключены любые ключи, которые не являются собственными ключами, поэтому исключаются любые унаследованные свойства. Затем он исключает неисчислимые ключи, которые являются просто свойствами, исключенными из итерации. По умолчанию все ключи, которые мы добавляем со строковыми именами, бесчисленны, поэтому мы не беспокоимся о них. Это также исключает Символ. Мы не увидим символы с этой базовой версией итерации по объекту, и мы не увидим ни одного из ключей в цепочке прототипов.

Object.values() возвращает массив значений.

Object.entries() одновременно возвращает и ключи, и значения. Мы получим вложенный массив каждой пары ключ-значение.

Обратите внимание, что все эти методы игнорируют прототип и символ.

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

const company1 = {
  __proto__: company,
  isPublic: true,
  industry: 'financial',
  [Symbol('id')]: 0
};

console.log(Object.keys(company1)); // ["isPublic", "industry"]
console.log(Object.values(company1)); // [true, "financial"]
console.log(Object.entries(company1)); // [["isPublic", true], ["industry", "financial"]]

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

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

const company1 = {
  __proto__: company,
  isPublic: true,
  industry: 'financial',
  [Symbol('id')]: 0
};

Object.entries(company1).forEach(function(value) {
  console.log(value);
})

// output:
// (2) ["isPublic", true]
// (2) ["industry", "financial"]

Другой способ перебора объекта — цикл for…in. В приведенном ниже примере мы присваиваем каждому ключу значение ключа в каждой итерации цикла. Он всегда будет видеть собственные свойства, такие как isPublic и industry в нашем примере, но игнорирует свойства Symbol. Однако разница между этим способом итерации и описанным нами первым способом (с Object.keys(), Object.values() и Object .entries()) заключается в том, что он увидит прототип. В нашем примере он увидит свойства компании, от которых мы наследуем. Единственное предостережение заключается в том, что он не будет включать неперечислимые свойства, и это по большей части свойства по умолчанию, которые предоставляются нам браузером (например, toString() ).

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

const company1 = {
  __proto__: company,
  isPublic: true,
  industry: 'financial',
  [Symbol('id')]: 0
};

for (key in company1) {
  console.log(key);
}

/*
output:
isPublic
industry
name
domain
founded
founders
*/

Скопируйте объект

Object.assign() принимает целевой объект и источник и копирует все перечисляемые собственные свойства объекта. Он не будет копировать унаследованные прото-свойства или innumerables.

const obj = {
  hello: 'world',
  foo: 'bar'
};

const my_obj = {
  original: true
};
Object.assign(my_obj, obj);
console.log(my_obj); // Object {original: true, hello: "world", foo: "bar"}

Методы Object.freeze() и Object.seal()

Object.freeze() замораживает объект, что означает, что мы больше не сможем изменить этот объект, он делает объект неизменяемым. Мы также не сможем добавлять свойства. Чтобы проверить, заморожен ли объект, мы можем использовать метод Object.isFrozen().

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

Object.freeze(company);
company.name = 'ABC'; // won't update
company.isPublic = false; // won't add a new property

console.log(Object.isFrozen(company)); // true
console.log(company); 

// output: 
// {name: "XYZ International", domain: "xyzcompany.com", founded: 2015, founders: Array(2)}

У нас также есть метод seal(), но разница в том, что с помощью seal() вы можете изменять существующие свойства, но не добавлять новые свойства. Точно так же есть метод isSealed(), который можно использовать для проверки того, запечатан ли объект.

const company = {
  name: 'XYZ International',
  domain: 'xyzcompany.com',
  founded: 2015,
  founders: ['John', 'Jack']
};

Object.seal(company);
company.name = 'ABC'; // this will work 
company.isPublic = false; // won't add a new property

console.log(company);
console.log(Object.isSealed(company));

// output:
// {name: "ABC", domain: "xyzcompany.com", founded: 2015, founders: Array(2)}
// true

Метод toString()

Метод toString() возвращает строку, представляющую объект. Все объекты, наследуемые от Object.prototype (все, кроме объектов с нулевым прототипом), наследуют метод toString(). Когда вы создаете пользовательский объект, вы можете переопределить toString() для вызова пользовательского метода, чтобы ваш пользовательский объект можно было преобразовать в строковое значение.

function Vegetable(type) {
  this.type= type;
}

const rootveg = new Vegetable('sweet potato');

Vegetable.prototype.toString = function veg_to_string() {
  return `${this.type}`;
};

console.log(rootveg.toString()); // 'sweet potato'

Встроенные объекты

JavaScript предоставляет следующие предопределенные встроенные объекты.

  • Строка — объект, только если он создан с использованием ключевого слова new
  • Число — объект, только если он создан с использованием ключевого слова new
  • Boolean — объект, только если он создан с использованием ключевого слова new
  • Объект — объект, определенный вами.
  • Дата — объект, содержащий компоненты даты и времени.
  • Массив — объект, хранящий проиндексированные элементы данных.
  • RegExp — объект, описывающий шаблон регулярного выражения.
  • Math — объект, предоставляющий математические свойства и методы.
  • Ошибка — объект, предоставляющий сведения об ошибке.

Каждый из перечисленных здесь объектов, кроме Math, имеет метод конструктора, который можно использовать с ключевым словом new для создания объекта определенного типа. Например, чтобы создать новый объект Дата, мы говорим let now = new Date().

Однако не рекомендуется использовать ключевое слово new и метод конструктора для любого типа объекта, кроме Error и Date. JavaScript может разумно распознавать, какой тип объекта должен быть создан по присваиваемому значению, если только это значение не является строкой, числом или логическим значением. Каждое из них рассматривается как примитивные литеральные значения, поэтому оператор typeof возвращает для них string, number, boolean — не возражаю.

Все встроенные объекты наследуют свойства и методы от объекта Object.prototype верхнего уровня. Например, это предоставляет метод toLocaleString() для объектов более низкого уровня, таких как Date.prototype, так что вы можете добавить вызов этого метода к Date object для получения строки даты в локальном формате.