Эти соображения привели к тому, что свойства, не являющиеся методами, многие не включают в интерфейс. Вместо прямого доступа к свойствам-значениям, используются методы типа getSomething
setSomething для чтения и записи значений свойств. Но в таком подходе есть и минус – приходится писать (и читать) много дополнительных методов.К счастью, JavaScript даёт нам технику, использующую лучшее из обоих подходов. Мы можем задать свойства, которые снаружи выглядят обыкновенными, но втайне имеют связанные с ними методы.
var pile = {
elements: ["скорлупа", "кожура", "червяк"],
get height() {
return this.elements.length;
},
set height(value) {
console.log("Игнорируем попытку задать высоту", value);
}
};
console.log(pile.height);
// → 3
pile.height = 100;
// → Игнорируем попытку задать высоту 100
В объявлении объекта записи get
set позволяют задать функцию, которая будет вызвана при чтении или записи свойства. Можно также добавить такое свойство в существующий объект, к примеру, в prototype, используя функцию Object.defineProperty (раньше мы её уже использовали, создавая несчётные свойства).Object.defineProperty(TextCell.prototype, "heightProp", {
get: function() { return this.text.length; }
});
var cell = new TextCell("да\nну");
console.log(cell.heightProp);
// → 2
cell.heightProp = 100;
console.log(cell.heightProp);
// → 2
Так же можно задавать свойство set
defineProperty, для задания метода-сеттера. Когда геттер есть, а сеттера нет, попытка записи в свойство просто игнорируется.Наследование
Но мы ещё не закончили с нашим упражнением по форматированию таблицы. Читать её было бы удобнее, если б числовой столбец был выровнен по правому краю. Нам нужно создать ещё один тип ячеек вроде TextCell
Мы могли бы написать новый конструктор со всеми тремя методами в прототипе. Но прототипы могут сами иметь прототипы, и поэтому мы можем поступить умнее.
function RTextCell(text) {
TextCell.call(this, text);
}
RTextCell.prototype = Object.create(TextCell.prototype);
RTextCell.prototype.draw = function(width, height) {
var result = [];
for (var i = 0; i < height; i++) {
var line = this.text[i] || "";
result.push(repeat(" ", width - line.length) + line);
}
return result;
};
Мы повторно использовали конструктор и методы minHeight
minWidth из обычного TextCell. И RTextCell теперь в общем эквивалентен TextCell, за исключением того, что в методе draw находится другая функция.Такая схема называется наследованием. Мы можем строить в чём-то отличные типы данных на основе существующих, не тратя много сил. Обычно новый конструктор вызывает старый (через метод call
Если мы чуть отредактируем функцию dataTable
RTextCells, мы получим нужную нам таблицу.function dataTable(data) {
var keys = Object.keys(data[0]);
var headers = keys.map(function(name) {
return new UnderlinedCell(new TextCell(name));
});
var body = data.map(function(row) {
return keys.map(function(name) {
var value = row[name];
// Тут поменяли:
if (typeof value == "number")
return new RTextCell(String(value));
else
return new TextCell(String(value));
});
});
return [headers].concat(body);
}
console.log(drawTable(dataTable(MOUNTAINS)));