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

В этой статье изучим как создавать много однотипных объектов с помощью конструктора в JavaScript. Кроме этого разберём статические свойства и методы, инструкцию return, new.target и многое другое.
Конструкторы
Когда нам нужно создать один объект, то мы обычно используем литеральный синтаксис:
const toyota = {
brand: 'Toyota',
color: 'red',
mileage: 0
}
Но если нам необходимо создать много однотипных объектов, т.е. сделанных по определённому шаблону или заготовке, то это в JavaScript осуществляется с использованием конструкторов.
Конструктор в JavaScript – это обычная функция, название которой принято писать в нотации PascalCase. Это необходимо для того, чтобы понимать, что это не просто функция, а конструктор.
Чтобы понять, чем отличается конструктор от объекта рассмотрим следующий пример:

Допустим, имеется предприятие и на нём можно создавать любые экземпляры автомобилей. Чтобы изготовить автомобиль в производство необходимо передать какие-то определённые вводные данные. Например, бренд, модель, цвет, вид топлива и так далее. Так вот, это то, что делает конструктор в 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
, конечно, независим. Но кроме собственных свойств и методов, они также могут иметь унаследованные.
Например, добавим метод 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
будет наследоваться всеми экземплярами класса Car
. Т.е. также как методы forEach
и map
наследуются массивами.
Обратите внимание что у объектов toyota
и ford
нет собственного метода 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
также создаст пустой объекта, если ему в качестве аргумента передать значение null
или undefined
:
const obj1 = new Object(null);

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

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

Статические свойства и методы
В 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));

Первый статический метод 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
будет возвращать примитивное значение, то оно будет проигнорировано и конструктор вернёт 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
, специальное свойство new.target
,будет содержать саму эту функцию. В противном случае - undefined
.
Пример, в котором проверяем как вызван конструктор. Если без new
, то выбрасываем ошибку:
function Vehicle(speed, color) {
if (!new.target) {
throw 'Vehicle() должна быть вызвана с использованием new';
}
this.speed = speed;
this.color = color;
}
