Псевдо-классовое наследование в ES5 и ES2015. Как организуется, на чём базируется, что изменилось в ES2015.
Класс — это функция-конструктор, создающая объект по заложенному в неё шаблону и классифицирующая его собой, сохраняя в нём указатель на себя.
В версии ES5 "классы" определяли следующим образом:
function Animal(name) {
this.name = name;
this.speed = 0;
}
Animal.prototype.run = function(speed) {
this.speed += speed;
alert( this.name + ' бежит, скорость ' + this.speed );
};
function Rabbit(name) {
this.name = name;
this.speed = 0;
}
// Наследование
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;
Rabbit.prototype.jump = function() {
this.speed++;
alert( this.name + ' прыгает, скорость ' + this.speed );
}
В ES2015 был введен новый синтаксис, с использованием ключевого слова class:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + " " + this.lastName;
}
}
class Student extends Person {
constructor(studentId, firstName, lastName) {
super(firstName, lastName);
this.studentId = studentId;
}
getStudentInfo() {
return this.studentId + "-" + super.getFullName();
}
}
var student = new Student(1, "Bob", "Smith");
student.getStudentInfo();
Функциональное наследование.
Общая схема (кратко):
ü Объявляется конструктор родителя Machine. В нём могут быть приватные (private), публичные (public) и защищённые (protected) свойства:
function Machine(params) {
// локальные переменные и функции доступны только внутри Machine
var privateProperty = …;
// публичные доступны снаружи
this.publicProperty = ...;
// защищённые доступны внутри Machine и для потомков
this._protectedProperty = ...;
}
ü Для наследования конструктор потомка вызывает родителя в своём контексте через apply. После чего может добавить свои переменные и методы:
function CoffeeMachine(params) {
Machine.apply(this, arguments);
this.coffeePublicProperty = ...;
}
ü В CoffeeMachine свойства, полученные от родителя, можно перезаписать своими. Но обычно требуется не заменить, а расширить метод родителя. Для этого он предварительно копируется в переменную:
function CoffeeMachine(params) {
Machine.apply(this, arguments);
var parentProtected = this._protectedProperty;
this._protectedProperty = function(args) {
parentProtected.apply(this, args);
// ...
};
}
Встроенные коллекции в ES2015.
Map
Map – коллекция для хранения записей вида ключ:значение. В отличие от объектов, в которых ключами могут быть только строки, в Map ключом может быть произвольное значение, например:
let map = new Map();
map.set('1', 'str1'); // ключ-строка
map.set(1, 'num1'); // число
map.set(true, 'bool1'); // булевое значение
Как видно из примера выше, для сохранения и чтения значений используются методы get и set. Свойство map.size хранит общее количество записей в map.
При создании Map можно сразу инициализировать списком значений:
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
Аргументом new Map должен быть итерируемый объект (не обязательно именно массив).
В качестве ключейmapможно использовать и объекты:
let user = { name: "Вася" };
// для каждого пользователя будем хранить количество посещений
let visitsCountMap = new Map();
// объект user является ключом в visitsCountMap
visitsCountMap.set(user, 123);
visitsCountMap.get(user); // 123
Методы для удаления записей:
map.delete(key) удаляет запись с ключом key, возвращает true, если такая запись была, иначе false.
map.clear() – удаляет все записи, очищает map.
Для проверки существования ключа:
map.has(key) – возвращает true, если ключ есть, иначе false.
У Map есть стандартный метод forEach, аналогичный массиву:
let recipeMap = new Map([
['огурцов', '500 гр'],
['помидоров', '350 гр'],
['сметаны', '50 гр']
]);
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // огурцов: 500 гр, и т.д.
});
Set
Set – коллекция для хранения множества значений, причём каждое значение может встречаться лишь один раз.
Основные методы:
ü set.add(item) – добавляет в коллекцию item, возвращает set.
ü set.delete(item) – удаляет item из коллекции, возвращает true, если он там был, иначе false.
ü set.has(item) – возвращает true, если item есть в коллекции, иначе false.
ü set.clear() – очищает set.
Перебор Set осуществляется через forEach аналогично Map:
let set = new Set(["апельсины", "яблоки", "бананы"]);
set.forEach((value, valueAgain, set) => {
alert(value); // апельсины, затем яблоки, затем бананы
});
В Set у функции в .forEach три аргумента: значение, ещё раз значение, и затем сам перебираемый объект set. При этом значение повторяется в аргументах два раза. Так сделано для совместимости с Map, где у .forEach-функции также три аргумента. Но в Set первые два всегда совпадают и содержат очередное значение множества.
WeakMap и WeakSet
WeakSet – особый вид Set не препятствующий сборщику мусора удалять свои элементы. То же самое – WeakMap для Map. То есть, если некий объект присутствует только в WeakSet/WeakMap – он удаляется из памяти. Это нужно для тех ситуаций, когда основное место для хранения и использования объектов находится где-то в другом месте кода, а здесь мы хотим хранить для них «вспомогательные» данные, существующие лишь пока жив объект.
Например, у нас есть элементы на странице или, к примеру, пользователи, и мы хотим хранить для них вспомогательную информацию, например обработчики событий или просто данные, но действительные лишь пока объект, к которому они относятся, существует. Если поместить такие данные в WeakMap, а объект сделать ключом, то они будут автоматически удалены из памяти, когда удалится элемент.
// текущие активные пользователи
let activeUsers = [
{name: "Вася"},
{name: "Петя"},
{name: "Маша"}
];
// вспомогательная информация о них,
// которая напрямую не входит в объект юзера,
// и потому хранится отдельно
let weakMap = new WeakMap();
weakMap.set(activeUsers[0], 1);
weakMap.set(activeUsers[1], 2);
weakMap.set(activeUsers[2], 3);
weakMap.get(activeUsers[0]); // 1
activeUsers.splice(0, 1); // Вася более не активный пользователь
// weakMap теперь содержит только 2 элемента
Таким образом, WeakMap избавляет нас от необходимости вручную удалять вспомогательные данные, когда удалён основной объект.
У WeakMap есть ряд ограничений:
ü Только объекты в качестве ключей.
ü Нет свойства size.
ü Нельзя перебрать элементы итератором или forEach.
ü Нет метода clear().
Иными словами, WeakMap работает только на запись (set, delete) и чтение (get, has) элементов по конкретному ключу, а не как полноценная коллекция. Это связано с тем, что содержимое WeakMap может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. В равной степени он не гарантирует и обратное. Нет какого-то конкретного момента, когда такая очистка точно произойдёт – это определяется внутренними алгоритмами сборщика и его сведениями о системе. Поэтому содержимое WeakMap в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации WeakMap, и связано отсутствие методов, осуществляющих доступ ко всем записям.
То же самое относится и к WeakSet: можно добавлять элементы, проверять их наличие, но нельзя получить их список и даже узнать количество.
Эти ограничения могут показаться неудобными, но по сути они не мешают WeakMap/WeakSet выполнять свою основную задачу – быть «вторичным» хранилищем данных для объектов, актуальный список которых (и сами они) хранятся в каком-то другом месте.