Эта глава описывает довольно эксцентричный подход JavaScript к объектам, и то, как они соотносятся с классическими объектно-ориентированными техниками.
Методы
Методы – свойства, содержащие функции. Простой метод:
var rabbit = {};
rabbit.speak = function(line) {
console.log("Кролик говорит '" + line + "'");
};
rabbit.speak("Я живой.");
// → Кролик говорит 'Я живой.'
Обычно метод должен что-то сделать с объектом, через который он был вызван. Когда функцию вызывают в виде метода – как свойство объекта, например object.method()
– специальная переменная в её теле будет указывать на вызвавший её объект.
function speak(line) {
console.log("А " + this.type + " кролик говорит '" + line + "'");
}
var whiteRabbit = {type: "белый", speak: speak};
var fatRabbit = {type: "толстый", speak: speak};
whiteRabbit.speak("Ушки мои и усики, я же наверняка опаздываю!");
// → А белый кролик говорит 'Ушки мои и усики, я же наверняка опаздываю!'
fatRabbit.speak("Мне бы сейчас морковочки.");
// → А толстый кролик говорит 'Мне бы сейчас морковочки.'
Код использует ключевое слово this
для вывода типа говорящего кролика.
Вспомните, что методы apply
и bind
принимают первый аргумент, который можно использовать для эмуляции вызова методов. Этот первый аргумент как раз даёт значение переменной this
.
Есть метод, похожий на apply
, под названием call
. Он тоже вызывает функцию, методом которой является, только принимает аргументы как обычно, а не в виде массива. Как apply
и bind
, в call
можно передать значение this
.
speak.apply(fatRabbit, ["Отрыжка!"]);
// → А толстый кролик говорит 'Отрыжка!'
speak.call({type: "старый"}, "О, господи.");
// → А старый кролик говорит 'О, господи.'
Прототипы
Следите за руками.
var empty = {};
console.log(empty.toString);
// → function toString(){…}
console.log(empty.toString());
// → [object Object]
Я достал свойство пустого объекта. Магия!
Ну, не магия, конечно. Я просто не всё рассказал про то, как работают объекты в JavaScript. В дополнение к набору свойств, почти у всех также есть прототип. Прототип – это ещё один объект, который используется как запасной источник свойств. Когда объект получает запрос на свойство, которого у него нет, это свойство ищется у его прототипа, затем у прототипа прототипа, и т. д.
Ну а кто же прототип пустого объекта? Это великий предок всех объектов, Object.prototype
.
console.log(Object.getPrototypeOf({}) == Object.prototype);
// → true
console.log(Object.getPrototypeOf(Object.prototype));
// → null
Как и следовало ожидать, функция Object.getPrototypeOf
возвращает прототип объекта.
Прототипические отношения в JavaScript выглядят как дерево, в корне которого находится Object.prototype
. Он предоставляет несколько методов, которые появляются у всех объектов. Например, toString
, который преобразует объект в строковый вид.
Прототипом многих объектов служит не непосредственно Object.prototype
, а какой-то другой объект, который предоставляет свои свойства по умолчанию. Функции происходят от Function.prototype
, массивы – от Array.prototype
.
console.log(Object.getPrototypeOf(isNaN) == Function.prototype);
// → true
console.log(Object.getPrototypeOf([]) == Array.prototype);
// → true
У таких прототипов будет свой прототип – часто Object.prototype
, поэтому он всё равно, хоть и не напрямую, предоставляет им методы типа toString
.
Функция Object.getPrototypeOf
возвращает прототип объекта. Можно использовать Object.create
для создания объектов с заданным прототипом.
var protoRabbit = {
speak: function(line) {
console.log("А " + this.type + " кролик говорит '" + line + "'");
}
};
var killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "убийственный";
killerRabbit.speak("ХРЯЯЯСЬ!");
// → А убийственный кролик говорит 'ХРЯЯЯСЬ!'
Прото-кролик работает в качестве контейнера свойств, которые есть у всех кроликов. Конкретный объект-кролик, например убийственный, содержит свойства, применимые только к нему – например, свой тип – и наследует разделяемые с другими свойства от прототипа.
Конструкторы
Более удобный способ создания объектов, наследуемых от некоего прототипа – конструктор. В JavaScript вызов функции с предшествующим ключевым словом new
приводит к тому, что функция работает как конструктор. Конструктор создает новый объект и возвращает его, если только явно не задано возвращение другого объекта вместо созданного. При этом свежесозданный объект доступен изнутри конструктора через переменную this
.