JavaScript prototype

В JavaScript есть ряд разительных отличий от других языков программирования. Одним из них является такая вещь как прототип. Часто можно услышать фразы “прототипное наследование”, так вот я постараюсь объяснить что это, так как тема простая, но новичкам с этим не везёт.

Есть ряд “нормальных” ООП языков, в них есть наследования, инкапсуляция, иногда интерфейсы, иногда абстрактные классы, в жс нет ничего из этого, те кто знают как работает ООП в других язык, могут порадоваться что в жс есть хотябы оператор new и instanceof, и нет ничего вышесказанного (в обычном смысле). Те, кто не знают других ООП языков, имейте ввиду, то, что ниже, редко где кроме жс увидишь, это особенность жс.

Тем не менее, в жс можно особым способом сделать даже множественное наследования, а патерны типа “контракт” делаются, но по-другом.

Объект - экземпляр какого-либо класса, класс - тоже объект. Сразу оговорим, что значит тоже объект: вода это жидкость, но жидкость не вода, точно также класс это объект, но не всякий объект класс. Я считаю, что читатель уже знает, что такое переменная, работал хоть чуточку с объектами и конечно понимает что же такое объект в ЖС и что такое свойство объекта (property).

1
2
3
4
5
6
7
8
9
10
// массив принадлежит классу Объект
([]) instanceof Object // true
// массив принадлежит классу Массив
([]) instanceof Array // true
// объект не принадлежит классу Массив
({}) instanceof Array // false
// объект принадлежит классу Объект
({}) instanceof Object
// некоторая вещь, по факту функция, принадлежит классу Функций
(() => 1) instanceof Function // true

Я специально пишу слова Объект с большой буквы, класс курильщика будет написан с маленькой буквы, класс здорового человека с большой. Потом будет ясно почему.

Что такого, что массив принадлежит к классу Массив? А то, что есть ряд “операций”, которые можно проводить именно над массивами, но не над объектами. Есть что-то особенное, что отличает массив от объекта.

1
2
3
4
5
[].length // 0
[].join // что-то напишет :)
({1: '1'}).length // undefined
({1: '1'}).join // undefined
(new Array(2)).fill(0).join("") // "00"

Но и наоборот, у массива есть что-то, что есть у любого объекта, можно сказать, что массив это объект, у которого есть свои фишки.

1
2
3
4
5
6
7
8
9
// свойство length есть у объекта, или оно берётся из хитрых прототипных мест?
// у массива
([]).hasOwnProperty("length") // true
// у объекта
({length: 0}).hasOwnProperty("length") // true
// у совсем другого объекта
({1: 0}).hasOwnProperty("length") // false
// а join которым мы только что пользовались?
([]).hasOwnProperty("join") // false

hasOwnProperty - помогает нам пролить свет на то, что же внутри этого объекта взялось из прототипа, а что его “родное”. Т.е. внутри объекта есть что-то, оно наследуется от Объекта, и возможно не только (смотря о чём говорим!), а есть то, что пренадлежит только этому конкретному объекту.

Поблагодарив сообщество mdn, я аккуратно позаимствую кусок их примера:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name) {
this.name = name;
this.canTalk = true;
this.greet = function() {
if (this.canTalk) {
console.log('Привет, я ' + this.name);
}
};
}
var p = new Person("Allah!")
p instanceof Person // true
p instanceof Object // true
p.greet() // Привет, я Allah!`
Person.prototype // что-то тут есть
p.prototype // undefined

И так, что же мы натворили в примере выше, мы создали Класс, это же целая Личность! И Мы сделали что-то богохульное, с именем Allah. Пожалуй, менее религиозные и более внимательные обратили внимания, что за громкими словами класс скрывается всем известная функция. Но эта функция пишется с большой буквы! А почему? Да хоть как-то их в коде отличить. Ещё раз: между функцией и классом в жс нет разницы! Ну и моё любимое, между функцией и объектом тоже не много, ведь функция (смотри выше) это же Объект, экземпляр его. Вот такая жс приколюшка, получается, мы создаём объекты оператором new от других объектов, а классов как бы и нет, ну то есть, они есть, но они не классы а функции. И что дальше, может на лету будем методы и свойства классов писать?!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name) {
this.name = name;
this.canTalk = true;
this.greet = function() {
if (this.canTalk) {
console.log('Привет, я ' + this.name);
}
};
}
var p = new Person("Allah!")
p.go() // ошибка, варнинг, синтакс эрор, фатал шатал. ну нет тут этого метода, не все личности ходют
// ну так научим
Person.prototype.go = () => [].concat("\n", (new Array(4)).fill("/\\")).join("\n")
p.go() // ходит как лыжник по свежему снегу, ну хоть бы так научили

Повтор. Есть операторы new и instanceof. Есть классы, они же функции. Есть прототип, прототип это объект, в этот объект можно добавить метод или свойство, и это же появится у всех наследников этого класса. Можно определить родное ли свойство с помощью метода hasOwnProperty.

Итог. Самое базовое объяснение что такое прототип дано, прототип это то, что наследуется, что напишешь в прототип, появится и у наследников. Есть ещё множество вещей, которые я не осветил, например дичь и свойство proto, которым рекомендуют не пользоваться. Или как всё же сделать наследование и множественное наследование конечно для тех, кто уважает ес6, в нём это делается короче но под капотом всё тоже самое, основы нужно знать