Объекты в JavaScript

Объекты в JavaScript

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

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

const person = {
    name: 'Alice',
    age: 25,
    hobbies: ['reading', 'dancing'],
    sayHello: function () {
      console.log("Hello, I'm " + this.name);
    },
  };

В этом примере мы создали объект person, который имеет четыре свойства: name, age, hobbies и sayHello. Первые три свойства хранят простые данные (строку, число и массив), а четвертое свойство хранит функцию, которую мы можем вызывать как метод объекта. Для доступа к свойствам и методам объекта мы можем использовать точечную нотацию или квадратные скобки:

console.log(person.name); // "Alice"
console.log(person["age"]); // 25
console.log(person.hobbies[0]); // "reading"
console.log(person.sayHello()); // "Hello, I'm Alice"

Объекты и их свойства

Свойства объекта — это переменные, которые принадлежат объекту. Они могут быть добавлены, изменены или удалены в любое время:

person.height = 170; // Добавляем новое свойство
person.age = 26; // Изменяем существующее свойство
delete person.hobbies; // Удаляем свойство

Индексы свойств объекта

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

const obj = {};
obj.number = 477; 
obj[''] = 'empty'; // Пустая строка
obj['1'] = 'one'; // Строка из цифры
obj['hello world'] = 'greeting'; // Строка с пробелом
console.log(obj) // {1: 'one', number: 477, "": 'empty', hello world: 'greeting'}

// Использование квадратных скобок
console.log(obj['']); // empty
console.log(obj['1']); // one
console.log(obj['hello world']); // greeting

//Использование точечной нотации приводит к синтаксической ошибке
console.log(obj.1); // SyntaxError
console.log(obj.hello world); // SyntaxError

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

Как определить существует нужное свойство в объекте или нет

Чтобы определить, существует ли свойство, используйте оператор in. Он возвращает true, если объект имеет свойство с заданным именем, и false в противном случае. Также проверяется, есть ли свойство в прототипе объекта.

const person = {
  name: 'Igor',
  age: 30,
};

console.log('name' in person); // true
console.log('height' in person); // false
console.log('toString' in person); // true - наследуется от Object.prototype

Как перебрать свойства объекта

Цикл for…in — это способ перебрать все свойства объекта и выполнить некоторое действие для каждого из них:

for (var key in person) {
  console.log(key + ": " + person[key]);
}
// name: Alice
// age: 26
// sayHello: function () {
//      console.log("Hello, I'm " + this.name);
//   }
// height: 170

Упорядочение свойств объекта

Свойства объекта в JavaScript не имеют определенного порядка. Однако, при переборе свойств объекта с помощью цикла for…in или метода Object.keys, они обычно выводятся в следующем порядке: cначала идут свойства с целочисленными индексами в возрастающем порядке, затем идут остальные свойства в порядке их создания.

const obj = {};

obj[2] = 'two';
obj[1] = 'one';
obj['name'] = 'Sveta';
obj[3] = 'tree';
obj['age'] = 23;

for (var key in obj) {
  console.log(key + ':' + obj[key]);
}

// 1:one
// 2:two
// 3:tree
// name:Sveta
// age:23

Удаление свойств

Чтобы удалить свойства объекта или сам объект, применяйте оператор delete. Он удаляет свойство и его значение из объекта ↓

const person = {
  name: "Alice",
  age: 25,
  hobbies: ["reading", "dancing"]
};

delete person.age; // Удаляем свойство age
console.log(person.age); // undefined

delete person.hobbies[0]; // Удаляем элемент массива
console.log(person.hobbies); // [undefined, “dancing”]

delete person; // Удаляем объект 
console.log(person); // ReferenceError: person is not defined

Однако, если свойство имеет атрибут configurable: false, то оно не может быть удалено:

const obj = Object.create({}, {
  x: {
    value: 10,
    writable: true,
    enumerable: true,
    configurable: false
  }
});

console.log(obj.x); // 10

delete obj.x; // не удаляет свойство x
console.log(obj.x); // 10

Определение свойств для типа объекта

Тип объекта — это классификация объектов по их внутренней структуре и поведению. В JavaScript есть встроенные типы объектов, такие как Array, Date, Function, Math и другие, а также пользовательские типы объектов, которые мы можем создавать с помощью функций конструкторов или метода Object.create. Каждый тип объекта имеет свои собственные свойства и методы, которые доступны для всех его экземпляров, например:

const arr = [1, 2, 3]; // Cоздаем массив - экземпляр типа Array
console.log(arr.length); // 3 - свойство length определено для типа Array
console.log(arr.push(4)); // 4 - метод push определен для типа Array

const date = new Date(); // Cоздаем дату - экземпляр типа Date
console.log(date.getFullYear()); // 2023 - метод getFullYear определен для типа Date
console.log(date.getTimezoneOffset()); // -180 - метод getTimezoneOffset определен для типа Date

const func = function () { }; // Cоздаем функцию - экземпляр типа Function
console.log(func.name); // "" - свойство name определено для типа Function
console.log(func.call()); // undefined - метод call определен для типа Function

Чтобы определить свойства объекта, можно применять его прототип. Свойства прототипа общие для каждого экземпляра данного типа. Свойство prototype характерно для каждого типа объекта:

function Animal(name, type) {
  this.name = name; this.type = type;
}
Animal.prototype.makeSound = function () {
  console.log(this.name + " says " + this.type);
};

const dog = new Animal("Rex", "woof");
const cat = new Animal("Tom", "meow");

dog.makeSound(); // Rex says woof
cat.makeSound(); // Tom says meow

Мы определили метод makeSound для Animal через его прототип. Теперь все экземпляры этого типа могут применять этот метод. Это экономит память и избегает дублирования кода.

Создание новых объектов

Существует несколько способов создания новых объектов в JavaScript ↓

  • Использование инициализаторов объекта. Инициализатором называется литерал, который определяет свойства и методы объекта. Это самый простой и удобный метод создания объектов. К примеру:
const car = {
    model: 'Toyota',
    color: 'red',
    drive: function () {
      console.log('Driving...');
    },
  };
  • Применение функции конструктора. Функция конструктора — это специальная функция, которая создает новый объект при вызове с оператором new. Она имеет имя с большой буквы и принимает параметры для инициализации свойств объекта. Внутри функции конструктора мы имеем право использовать слово this для ссылки на инициализируемый объект:
 function Animal(name, type) {
    this.name = name;
    this.type = type;
    this.makeSound = function () {
      console.log(this.name + ' says ' + this.type);
    };
  }
const dog = new Animal('Rex', 'woof');
const cat = new Animal('Tom', 'meow');
  • Использование метода Object.create. Метод Object.create — это современный способ создания объектов, который позволяет наследовать их от других объектов. Он принимает два параметра: прототип объекта, от которого мы хотим наследовать, и дескрипторы свойств, которые мы хотим добавить или переопределить:
const person = {
  name: 'Alice',
  age: 25,
  sayHello: function () {
    console.log("Hello, I'm " + this.name);
  },
};

const student = Object.create(person, {
  grade: {
    value: 10,
    writable: true,
    enumerable: true,
    configurable: true
  },
  study: {
    value: function () {
      console.log(this.name + ' is studying');
    },
    writable: true,
    enumerable: true,
    configurable: true,
  },
});

В этом примере мы создали объект student, который наследует все свойства и методы от объекта person, а также добавили два новых свойства: grade и study. Дескрипторы свойств позволяют задать различные атрибуты свойств, такие как возможность записи, перечисления и конфигурации.

Сравнение объектов

  • Для сравнения объектов мы можем использовать операторы == и ===. Однако, они не сравнивают объекты по их содержанию, а только по их ссылкам. То есть, два объекта равны только тогда, когда они ссылаются на один и тот же объект в памяти:
var person1 = {name: "Alice",age: 25};
var person2 = {name: "Alice",age: 25};
var person3 = person1;

console.log(person1 == person2); // false
console.log(person1 === person2); // false
console.log(person1 == person3); // true
console.log(person1 === person3); // true

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

  • Для сравнения объектов по их содержанию мы можем применять специальные функции, которые рекурсивно проверяют все свойства объектов на равенство, например:
function isEqual(obj1, obj2) {
  if (obj1 === obj2) return true; // если ссылки равны, то объекты равны
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false; // если один из аргументов не объект, то объекты не равны
  
  const keys1 = Object.keys(obj1); // получаем массив ключей первого объекта
  const keys2 = Object.keys(obj2); // получаем массив ключей второго объекта
  
  if (keys1.length !== keys2.length) return false; // если длины массивов ключей не равны, то объекты не равны
  
  for (var key of keys1) {
    // перебираем все ключи первого объекта
    if (!obj2.hasOwnProperty(key)) return false; // если второй объект не имеет такого ключа, то объекты не равны
    if (!isEqual(obj1[key], obj2[key])) return false; // если значения по ключу не равны, то объекты не равны (рекурсивный вызов)
  }
  
  return true; // если все проверки пройдены, то объекты равны
}

var person1 = { name: 'Alice', age: 25, hobbies: ['reading', 'dancing'] };
var person2 = { name: 'Alice', age: 25, hobbies: ['reading', 'dancing'] };
var person3 = person1;

console.log(isEqual(person1, person2)); // true
console.log(isEqual(person1, person3)); // true

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

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

Наследование — это механизм, который позволяет одному объекту получать свойства и методы от другого объекта. Это нужно для того, чтобы использовать код еще раз и при этом максимально избежать дублирования. В JavaScript наследование основано на прототипах, то есть на специальных объектах, которые являются прародителями других объектов. Все объекты обладают свойством [[Prototype]], которое ссылается на его прототип. Когда мы пытаемся получить доступ к свойству или методу объекта, который у него отсутствует, JavaScript ищет его в прототипе. Если он там не найден, то поиск продолжается по цепочке прототипов до самого верхнего уровня — до объекта Object.prototype, который является прототипом всех объектов. Давайте рассмотрим на примере:

const person = {
  name: 'Alice',
  age: 25,
  sayHello: function () {
    console.log("Hello, I'm " + this.name);
  },
};

const student = Object.create(person);
student.grade = 10;
student.study = function () {
  console.log(this.name + ' is studying');
};

student.sayHello();
// Hello, I'm Alice

student.study();
// Alice is studying

console.log(student.age);
// 25

console.log(student.hasOwnProperty('name'));
// false

console.log(student.hasOwnProperty('grade'));
// true

Объект student наследует свойства и методы от объекта person через его прототип. Он также имеет свои собственные свойства grade и study. Метод hasOwnProperty позволяет проверить, является ли свойство собственным или унаследованным.

Определение методов

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

  • Свойства объекта:
const person = {
  name: "Alice",
  age: 25,
  sayHello: function () {
    console.log("Hello, I'm " + this.name);
  }
};

person.sayHello(); // Hello, I'm Alice
  • Свойства прототипа типа объекта:
function Animal(name, type) {
  this.name = name;
  this.type = type;
}

Animal.prototype.makeSound = function () {
  console.log(this.name + " says " + this.type);
};

const dog = new Animal("Rex", "woof");
dog.makeSound(); // Rex says woof

В этих примерах мы видим два способа определения методов: как свойство объекта person и как свойство прототипа типа Animal.

Использование this для ссылки на объект

Ключевое слово this в JavaScript используется для ссылки на текущий объект, в контексте которого выполняется код. Значение this зависит от того, как и где вызывается функция:

  • Если функция вызывается как метод объекта, то this ссылается на этот объект:
const person = {
  name: "Alice",
  age: 25,
  sayHello: function () {
    console.log("Hello, I'm " + this.name);
  }
};

person.sayHello(); // Hello, I'm Alice

Тут мы вызываем функцию sayHello как метод объекта person, поэтому this внутри функции ссылается на объект person.

  • Однако, стрелочная функция внутри объекта не будет работать с this, а будет ссылаться сразу на глобальный объект (window в браузере или global в Node.js):
var lastName = 'Ivanenko';
const person = {
  lastName: 'Dubova',
  age: 25,
  sayHello: () => {
    console.log("Hello, I'm " + this.lastName);
  },
};

person.sayHello(); //Hello, I'm Ivanenko - выведет имя из глобальной области видимости
Важно! В глобальную область видимости попадают переменные, объявленные с помощью var. Переменные, объявленные с помощью let и const будут выдавать Hello, I'm undefined.
  • Если функция вызывается как обычная функция, то this также будет ссылается на глобальный объект:
function sayHello() {
  firstName: "Dima",
  console.log("Hello, I'm " + this.firstName);
}

var firstName = "Pasha"; // Переменная в глобальной области видимости
sayHello(); // Hello, I'm Pasha

function sayBye() {
  console.log("Hello, I'm " + this.lastName);
}
const lastName = "Durov"; // Переменная в блочной области видимости
sayBye(); // Hello, I'm undefined

Мы вызываем функцию sayHello как обычную функцию, поэтому this внутри функции ссылается на глобальный объект, у которого есть свойство name со значением "Bob".

  • Если функция вызывается с оператором new, то this ссылается на новый объект, который создается при вызове функции:
function Animal(name, type) {
  this.name = name;
  this.type = type;
}

const dog = new Animal("Rex", "woof");

console.log(dog.name); // Rex
console.log(dog.type); // woof

В этот раз мы вызываем функцию Animal с оператором new, поэтому this внутри функции ссылается на новый объект dog, который получает свойства name и type.

  • Если функция вызывается с методами call или apply, то this ссылается на объект, который передается в качестве первого аргумента этих методов:
function sayHello() {
  console.log("Hello, I'm " + this.name);
}

const person = {
  name: "Alice",
  age: 25
};

sayHello.call(person); // Hello, I'm Alice
sayHello.apply(person); // Hello, I'm Alice

В этих примерах мы вызываем функцию sayHello с методами call и apply, this внутри функции ссылается на объект person, который передается в качестве первого аргумента этих методов.

Определение геттеров и сеттеров

Геттеры и сеттеры — это специальные методы, которые позволяют получать и устанавливать значения свойств объекта с помощью дополнительной логики. Они определяются с помощью ключевых слов get и set внутри литерала объекта или дескриптора свойства ↓

const person = {
  name: "Alice",
  age: 25,
  get fullName() {
    return this.name + " Smith";
  },
  set fullName(value) {
    var parts = value.split(" ");
    this.name = parts[0];
    this.lastName = parts[1];
  }
};

console.log(person.fullName); // Alice Smith
person.fullName = "Bob Jones";

console.log(person.name); // Bob
console.log(person.lastName); // Jones

Мы задали геттер и сеттер для свойства fullName объекта person. Геттер возвращает полное имя из имени и фамилии. Сеттер разбивает полное имя на части и устанавливает имя и фамилию.

* Как использовать геттеры и секторы в классах читайте здесь.

Софья Пирогова

Софья Пирогова

Главный редактор / Автор статей
Георгий Бабаян

Георгий Бабаян

Основатель и CEO Эльбрус Буткемп