Выбрать главу

Оператор 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 для первых её пяти элементов – или для меньшего количества, если их меньше пяти.