Создание объектов с помощью конструктора в JavaScript

Создание объектов с помощью конструктора в JavaScript
Содержание:
  1. Конструкторы
  2. Встроенные конструкторы
  3. Статические свойства и методы
  4. Инструкция return в конструкторе
  5. Защищенные поля
  6. new.target
  7. Комментарии

В этой статье изучим как создавать много однотипных объектов с помощью конструктора в JavaScript. Кроме этого разберём статические свойства и методы, инструкцию return, new.target и многое другое.

Конструкторы

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

const toyota = {
  brand: 'Toyota',
  color: 'red',
  mileage: 0
}

Но если нам необходимо создать много однотипных объектов, т.е. сделанных по определённому шаблону или заготовке, то это в JavaScript осуществляется с использованием конструкторов.

Конструктор в JavaScript – это обычная функция, название которой принято писать в нотации PascalCase. Это необходимо для того, чтобы понимать, что это не просто функция, а конструктор.

Чтобы понять, чем отличается конструктор от объекта рассмотрим следующий пример:

Понятие классов и объектов в JavaScript

Допустим, имеется предприятие и на нём можно создавать любые экземпляры автомобилей. Чтобы изготовить автомобиль в производство необходимо передать какие-то определённые вводные данные. Например, бренд, модель, цвет, вид топлива и так далее. Так вот, это то, что делает конструктор в JavaScript. Т.е. он получает вводные данные и создает в данном случае конкретный экземпляр автомобиля, а затем отдаёт его покупателю. А конкретный экземпляр автомобиля – это уже объект. Он может иметь определённые собственные свойства и методы. Но кроме них, также унаследованные, т.е. характерные для всех автомобилей. Например: ехать, останавливаться, поворачивать и т.д.

Пример создания конструктора Car:

function Car(brand, color) {
  this.brand = brand;
  this.color = color;
  this.mileage = 0;
}

В этом шаблоне для создания объектов у нас используется специальная переменная this. Эта переменная при создании нового экземпляра класса Car будет указывать на создаваемый новый объект.

При специальном вызове такой функции, т.е. с использованием ключевого слова new, она сначала создаст новый объект и присвоит его this. После этого выполнит код функции. С помощью строчки this.brand = brand эта функция добавит к данному объекту свойство brand со значением, которое мы передали параметру brand. После этого таким же образом добавится свойство color со значением, которое мы передали параметру color посредством аргумента в вызове функции Car. Затем свойству mileage присвоится значение 0. После выполнения кода функции, она вернёт полученный объект, т.е. this в качестве результата.

Используя такой конструктор мы можем создать очень много подобных объектов и у каждого из них будут свои собственные свойства. В данном случае такие как brand, color и mileage. Но в то же время все созданные объекты будут экземплярами класса Car. При этом под классом в JavaScript будем понимать шаблон, на основании которого создаются объекты. В данном случае он называется Car.

Создание объекта или, другими словами, экземпляра класса Car как мы уже отметили выше осуществляется очень просто, посредством вызова функции Car с указанием ключевого слова new перед ним:

const toyota = new Car('Toyota', 'red');
const ford = new Car('Ford', 'green');

Здесь мы создали 2 объекта, каждый из которых имеет свои собственные свойства:

Собственные свойства объектов, которые являются экземплярами класса Car

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

Например, добавим метод drive, который будет доступен для всех экземпляров класса Car:

Car.prototype.drive = function(distance) {
  this.mileage += distance;
}

Этот метод будет увеличивать значение свойства mileage:

toyota.drive(50);
console.log(toyota.mileage); // 50
ford.drive(100);
console.log(ford.mileage); // 100
Вызов наследованного свойства drive, увеличивающего значение свойства на заданную величину

В данном примере метод drive будет наследоваться всеми экземплярами класса Car. Т.е. также как методы forEach и map наследуются массивами.

Обратите внимание что у объектов toyota и ford нет собственного метода drive. Он находится на уровне класса Car и доступен для всех экземпляров этого класса.

Метод drive расположен на уровне класса Car, а не в самих объектах

Встроенные конструкторы

В JavaScript очень много различных встроенных конструкторов. Они используются для создания массивов (Array), дат (Date), ошибок (Error), объектов (Object) и т.д.

const myArray = new Array();
const myDate = new Date();
const myError = new Error('message');
const myObject = new Object();

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

Вызов new Object() без аргументов создаст новый пустой объект:

const obj1 = new Object();

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

const obj2 = {};

Т.е. для создания obj1 и obj2 использовался один и тот же конструктор:

obj1.constructor === obj2.constructor // true
Создание пустого объекта с помощью конструктора Object

Кстати, конструктор Object также создаст пустой объекта, если ему в качестве аргумента передать значение null или undefined:

const obj1 = new Object(null);
Конструктор Object также создаст пустой объект, если ему в качестве аргумента передать значение null или undefined

В противном случае будет возвращён объект, который соответствует переданному значению. Если значение уже является объектом, конструктор Object просто вернёт это значение:

Использование конструктора Object для создания различных объектов

На практике использование конструктора Object для создания объектов не используется, т.к. это можно очень просто сделать с помощью литерала.

Массив в JavaScript можно также создать с помощью литерального синтаксиса. Но не все объектные типы в JavaScript имеют литеральный синтаксис. Например, дату в JavaScript нельзя создать с помощью литерального синтаксиса, т.к. его просто нет. Дата может быть создана только путём вызова функции Date как конструктора, т.е. с использованием оператора new.

Использование конструктора Date для создания новой даты

Статические свойства и методы

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

Добавление статических методов к конструктору осуществляется напрямую. Например, создадим конструктор Point и добавим к нему 2 статических метода compare и newRandom:

// конструктор Point
function Point(x, y) {
  this.x = x;
  this.y = y;
}
// статические методы
Point.compare = function(point1, point2) {
  return point1.x === point2.x && point1.y === point2.y;
}
Point.newRandom = function() {
  const x = Math.floor(Math.random() * 101);
  const y = Math.floor(Math.random() * 101);
  return new this(x, y);
}

const point1 = Point.newRandom();
const point2 = Point.newRandom();
console.log(Point.compare(point1, point2));
Добавление статических методов к конструктору Point

Первый статический метод compare принимает на вход 2 объекта класса Point и сравнивает их координаты. Он возвращает true, если координаты совпадают. В противном случае false.

Второй метод newRandom создаёт объект класса Point, значение свойств x и y которого генерируются случайным образом от 0 до 100. Внутри метода мы можем получить сам конструктор Point используя this. Создание нового экземпляра класса Point выполним так: new this(x, y). После этого вернём полученный объект с помощью инструкции return.

Инструкция return в конструкторе

Обычно в конструкторе не используется инструкция return, так как ничего явно возвращать не нужно. Конструктор всегда не явно возвращает this.

Но если в конструкторе в качестве результата вернуть какой-то объект с помощью return, то в этом случае он станет результатом, а не this:

// конструктор Customer
function Customer(email, phone) {
  this.email = email;
  this.phone = phone;
  return {
    name: 'John',
    age: 35
  }
}
// создание объекта класса Customer
const customer1 = new Customer('dave@mail.com', '3-55-55');
console.log(customer1); // { name: 'John', age: 35 }
Использование в конструкторе инструкции return, которая возвращает JavaScript объект

Если return будет возвращать примитивное значение, то оно будет проигнорировано и конструктор вернёт this:

// конструктор Circle
function Circle(radius, color) {
  this.radius = radius;
  this.color = color;
  return 'abc';
}
// создание объекта класса Circle
const circle1 = new Circle(5, 'yellow');
console.log(circle1); // { radius: 5, color: 'yellow' }

Защищенные поля

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

Простой способ создать защищённое свойство - это добавить перед ним нижнее подчеркивание _:

function Person2(name, age) {
  this._name = name;
  this.age = age;
}

Но это просто соглашение между программистами, что эти свойства и методы защищенные и их не следует изменять извне.

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

function Person(name, age) {
  let _name = name;
  this.age = age;
  Object.defineProperty(this, 'name', {
    get() {
      return _name;
    }
  });
}
// создание экземпляров класса Person
const person1 = new Person('Tom', 32);
const person2 = new Person('Bob', 15);
Пример создания защищенных свойств, к которым нельзя будет напрямую обратиться извне

Если например нужно не только читать значение переменной _name, но и иметь возможность присвоить ей значение, то необходимо добавить ещё сеттер:

Object.defineProperty(this, 'name', {
  get() {
    return _name;
  },
  set(name) {
    return _name = name;
  }
});

new.target

Внутри функции мы можем с помощью специального свойства new.target проверить как вызвана функция: с использованием new, то есть как конструктор или без него:

function Vehicle() {
  console.log(new.target);
}
Специальное свойство new.target с помощью которого можно определить как вызвана функция, с исключением this или без него

Когда функция вызвана с использованием оператора new, специальное свойство new.target ,будет содержать саму эту функцию. В противном случае - undefined.

Пример, в котором проверяем как вызван конструктор. Если без new, то выбрасываем ошибку:

function Vehicle(speed, color) {
  if (!new.target) {
    throw 'Vehicle() должна быть вызвана с использованием new';
  }
  this.speed = speed;
  this.color = color;
}
Пример, в котором выбрасываем ошибку, если конструктор в JavaScript вызван без оператора new

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