Классы и наследование в ES5 и ES6

Классы и наследование в ES5

Т.к. в ES5 классов - нет, объекты создаются с помощью простых функций. Эти функции называются конструкторами. Основное отличие конструкторов от простых функций:

  • Название обычно пишется с большой буквы
  • Перед вызовом функции используется ключевое слово new, которое указывает на то что функция является функцией конструктором. Когда мы попадаем в эту функцию - создаётся пустой объект. Далее благодаря ключевому слову this получаем ссылку на этот пустой объект. С помощью this присваиваем ему требуемые свойства и в конце фугкция возвращает этот объект. Возвращённый объект присваиваем выбранной переменной.
function Task(title){
	this._title = title;
	this._done = false;
	Task.count += 1;
}

В ES5 свойства создаются при помощи метода defineProperty у объекта Object. Метод Object.defineProperty() определяет новое или изменяет существующее свойство непосредственно на объекте, возвращая этот объект. Подробнее: MDN, learn.javascript.ru.

Object.defineProperty(Task, 'title', {
	get: function () { 
		return this._title;
	},
	set: function (value) {
		this._title = value;
	}
});

В ES5 для создания метода класса - он указывается на прототипе prototype функции конструктора. Это делается для того чтобы не создавать метод на экземпляре объекта, т.к. если мы в конструкторе укажем this.complete - то каждый экземпляр класса Task будет иметь отдельный метод. Т.е. он реально будет занимать место в памяти. Если же мы укажем метод на прототипе объекта то все объекты будут ссылаться на один и тот же метод.

Task.prototype.complete = function () {
	this._done = true;
};

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

Task.getDefaultTitle = function () {
	return 'Задача';
}

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

Task.count = 0;

Наследование: В ES5 для того чтобы правильно инициализтировать объект - необходимо вызвать конструктор родительского класса. Это делается с помощью метода call на функции конструкторе, в которую мы отправляем создаваемый объект, ссылка на который хранится в слове this и параметры необходимые для правильной инициализации. У каждой функции есть метод call, который позволяет вызвать функцию и при этом указать значение this (контекста) для этой функции. После того как конструктор  ролительского класса вернёт нам объект мы присвоим ему свойства parent.

function SubTask(title, parent) {
	Task.call(this, title);
	this._parent = parent;
}

Для правильного определения наследования необходимо использовать прототип.

Метод Object.create() создаёт новый объект с указанными объектом прототипа и свойствами. Подробнеее: MDN, Прототип объекта[learn.javascript.ru].

// Мы указываем что прототипом класса SubTask является прототип класса Task.
SubTask.prototype = Object.create(Task.prototype);
// Указываем что у прототипа класса SubTask конструктором является функция SubTask.
SubTask.prototype.constructor = SubTask;

Итоговый класс

function Task(title){
	this._title = title;
	this._done = false;
	Task.count += 1;
}

Object.defineProperty(Task, 'title', {
	get: function () {
		return this._title;
	},
	set: function (value) {
		this._title = value;
	}
});

Task.prototype.complete = function () {
	this._done = true;
};

Task.getDefaultTitle = function () {
	return 'Задача';
}

function SubTask(title, parent) {
	Task.call(this, title);
	this._parent = parent;
}

SubTask.prototype = Object.create(Task.prototype);
SubTask.prototype.constructor = SubTask;

var task = new Task('Изучить JavaScript');
var subtask = new SubTask('Изучить ES6', task);

console.log(task);
console.log(subtask);