Прототипное программирование.
@frontendproglib
В отличие от большинства других ОО-языков (Java, C#), объектная система в JavaScriptоснована на прототипах, а не классах. Классы, которые вы знаете по таким языкам, как Java, технически не существуют в JavaScript (JS).
Вся иерархия объектов строиться на цепочках - прототипах. Object.prototype - объект, от которого "наследуются" все остальные объекты. Он содержит такие методы, как toString()или valueOf().Прототип у него равен null. Замечу, что Object это просто функция-конструктор для создания объектов:
typeof Object // 'function'
Object.prototype // объект Object {}
Object.prototype.__proto__ // 'null
prototype, который используется в примере, применим только к функциям, а для созданных объектов используется __proto__ (или [[Prototype]]).
Например, Array:
typeof Array // 'function'
Array.prototype.__proto__ === Object.prototype // true
var arr = [1,3]
arr.__proto__ // []
arr.__proto__ === Array.prototype // true
arr.__proto__.__proto__ // объект Object {}
arr.__proto__.__proto__ === Object.prototype // true
Все методы массивов (slice(), splice()) хранятся в объекте Array.prototype, а прототип этого объекта узакывает на Object.prototype.
Получается: arr -> Array.prototype -> Object.prototype -> null
Так же с другими встроенными функциями-конструкторами, например Function, Date, Number и т.д.
Сейчас все усложнилось ещё тем, что в новом стандарте (ES6) разработчики JS ввели class– удобный «синтаксический сахар» для задания конструктора вместе с прототипом. Насколько мне известно, его ввели в том числе специально для разработчиков, которые хотят писать на JS, но которых смущает, что в них нет классов :). На самом деле class в JS это обычная функция:
class Car {}
typeof Car // 'function'
Из плюсов прототипного наследования, наверно, это гибкость. Класс (например в Java) определяет все свойства для всех его экземпляров. Невозможно добавить свойства динамически во время выполнения. В тоже время в JS функция-конструктор определяет начальный набор свойств. Можно добавлять или удалять свойства динамически для отдельных объектов или сразу всем.
Возможно после использования интерфейсов/абстрактных классов в Java это покажется не плюсом, а минусом, но если этим уметь пользоваться, то потом этого не будет доставать в других языках.
// Функция конструктор
function Calculator () {}
Calculator.prototype.max = function (a, b) {
return Math.max(a, b);
}
// Создадим два экземпляра (объекта)
var ins1 = new Calculator();
var ins2 = new Calculator();
// Протестируем метод max:
console.log(ins1.max(1, 5), ins2.max(1, 5)); // -> 5, 5
// Изменим метод прототипа, чтобы можно было выбирать макс. значение
// передавая сколько угодно параметров
Calculator.prototype.max = function () {
var args = Array.prototype.slice.apply(arguments);
return Math.max.apply(null, args);
}
// Протестируем опять:
console.log(ins1.max(1, 5), ins2.max(1, 5)); // -> 5, 5
console.log(ins1.max(9, -5, 13), ins2.max(9, -5, 13)); // -> 13, 13
Мы так же могли поменять реализацию только для одного объекта (сделать как нужно в рамках задачи).
Повторюсь, prototype, который используется в примере, применим только к функциям, а для созданных объектов используется __proto__ (или [[Prototype]]). Метод max будет находится в прототипе созданных объектов (ins1.__proto__.max), а прототипы у них указывают на один и тот же объект:
ins1.__proto__ === ins2.__proto__ // true
Интересный момент:
ins1.__proto__ // объект прототип
ins1.__proto__.constructor // function Calculator () {} - функция, с помощью который создаются объекты
ins1.__proto__.constructor.prototype // объект, который является прототипом
// Проверим?
ins1.__proto__.constructor.prototype === ins1.__proto__ // true
Если интересует момент, почему прототипное наследование в JS похоже на классическое (по синтаксису, например, использование оператора new), то это легко объяснить. Создатель JS, Brendan Eich, хотел чтобы JavaScript стал младшим братом Java и пытался его сделать максимально похожим синтаксически.