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
.
Говорят, что объект, созданный при помощи new
, является экземпляром конструктора.
Вот простой конструктор кроликов. Имена конструкторов принято начинать с заглавной буквы, чтобы отличать их от других функций.
function Rabbit(type) {
this.type = type;
}
var killerRabbit = new Rabbit("убийственный");
var blackRabbit = new Rabbit("чёрный");
console.log(blackRabbit.type);
// → чёрный
Конструкторы (а вообще-то, и все функции) автоматически получают свойство под именем prototype
, которое по умолчанию содержит простой пустой объект, происходящий от Object.prototype
. Каждый экземпляр, созданный этим конструктором, будет иметь этот объект в качестве прототипа. Поэтому, чтобы добавить кроликам, созданным конструктором Rabbit
, метод speak
, мы просто можем сделать так:
Rabbit.prototype.speak = function(line) {
console.log("А " + this.type + " кролик говорит '" + line + "'");
};
blackRabbit.speak("Всем капец...");
// → А чёрный кролик говорит 'Всем капец...'
Важно отметить разницу между тем, как прототип связан с конструктором (через свойство prototype
) и тем, как у объектов есть прототип (который можно получить через Object.getPrototypeOf
). На самом деле прототип конструктора – Function.prototype
, поскольку конструкторы – это функции. Его свойство prototype
будет прототипом экземпляров, созданных им, но не его прототипом.
Перегрузка унаследованных свойств
Когда вы добавляете свойство объекту, есть оно в прототипе или нет, оно добавляется непосредственно к самому объекту. Теперь это его свойство. Если в прототипе есть одноимённое свойство, оно больше не влияет на объект. Сам прототип не меняется.
Читать дальше