Оператор instanceof
Иногда удобно знать, произошёл ли объект от конкретного конструктора. Для этого JavaScript даёт нам бинарный оператор instanceof
.
console.log(new RTextCell("A") instanceof RTextCell);
// → true
console.log(new RTextCell("A") instanceof TextCell);
// → true
console.log(new TextCell("A") instanceof RTextCell);
// → false
console.log([1] instanceof Array);
// → true
Оператор проходит и через наследованные типы. RTextCell
является экземпляром TextCell
, поскольку RTextCell.prototype
происходит от TextCell.prototype
. Оператор также можно применять к стандартным конструкторам типа Array
. Практически все объекты – экземпляры Object
.
Итог
Получается, что объекты чуть более сложны, чем я их подавал сначала. У них есть прототипы – это другие объекты, и они ведут себя так, как будто у них есть свойство, которого на самом деле нет, если это свойство есть у прототипа. Прототипом простых объектов является Object.prototype
.
Конструкторы – функции, имена которых обычно начинаются с заглавной буквы – можно использовать с оператором new
для создания объектов. Прототипом нового объекта будет объект, содержащийся в свойстве prototype
конструктора. Это можно использовать, помещая в прототип свойства, общие для всех экземпляров данного типа. Оператор instanceof
, если ему дать объект и конструктор, может сказать, является ли объект экземпляром этого конструктора.
Для объектов можно сделать интерфейс и сказать всем, чтобы они общались с объектом только через этот интерфейс. Остальные детали реализации объекта теперь инкапсулированы, скрыты за интерфейсом.
А после этого никто не запрещал использовать разные объекты при помощи одинаковых интерфейсов. Если разные объекты имеют одинаковые интерфейсы, то и код, работающий с ними, может работать с разными объектами одинаково. Это называется полиморфизмом, и это очень полезная штука.
Определяя несколько типов, различающихся только в мелких деталях, бывает удобно просто наследовать прототип нового типа от прототипа старого типа, чтобы новый конструктор вызывал старый. Это даёт вам тип объекта, сходный со старым, но при этом к нему можно добавлять свойства или переопределять старые.
Упражнения
Векторный тип
Напишите конструктор Vector
, представляющий вектор в двумерном пространстве. Он принимает параметры x
и y
(числа), которые хранятся в одноимённых свойствах.
Дайте прототипу Vector
два метода, plus
и minus
, которые принимают другой вектор в качестве параметра и возвращают новый вектор, который хранит в x
и y
сумму или разность двух векторов (один this
, второй – аргумент).
Добавьте геттер length
в прототип, подсчитывающий длину вектора – расстояние от (0, 0) до (x, y).
// Ваш код
console.log(new Vector(1, 2).plus(new Vector(2, 3)));
// → Vector{x: 3, y: 5}
console.log(new Vector(1, 2).minus(new Vector(2, 3)));
// → Vector{x: -1, y: -1}
console.log(new Vector(3, 4).length);
// → 5
Ещё одна ячейка
Создайте тип ячейки StretchCell(inner, width, height)
, соответствующий интерфейсу ячеек таблицы из этой главы. Он должен оборачивать другую ячейку (как делает UnderlinedCell
), и убеждаться, что результирующая ячейка имеет как минимум заданные ширину и высоту, даже если внутренняя ячейка – меньше.
// Ваш код.
var sc = new StretchCell(new TextCell("abc"), 1, 2);
console.log(sc.minWidth());
// → 3
console.log(sc.minHeight());
// → 2
console.log(sc.draw(3, 2));
// → ["abc", " "]
Интерфейс к последовательностям
Разработайте интерфейс, абстрагирующий проход по набору значений. Объект с таким интерфейсом представляет собой последовательность, а интерфейс должен давать возможность в коде проходить по последовательности, работать со значениями, которые её составляют, и как-то сигнализировать о том, что мы достигли конца последовательности.
Задав интерфейс, попробуйте сделать функцию logFive
, которая принимает объект-последовательность и вызывает console.log
для первых её пяти элементов – или для меньшего количества, если их меньше пяти.