
В программировании мы часто хотим что-то взять и расширить.
Например, у нас есть объект animal со своими свойствами и методами. Мы хотим создать объекты cat и dog с определенными свойствами и методами поверх animal. Мы хотели бы повторно использовать то, что у нас есть в animal, а не копировать/повторно реализовывать его методы, а просто создать новый объект поверх него.
Прототипы — это базовая функция JavaScript, которая позволяет объектам наследовать и совместно использовать свойства между собой.
Чтобы правильно использовать прототипы, нам нужно понять, что они собой представляют на самом деле и как они помогают в наследовании.
Что такое прототип
В JavaScript каждый объект имеет скрытое внутреннее свойство [[prototype]], которое либо ссылается на другой объект, либо имеет значение null.

JavaScript использует прототипное наследование. Это означает, что всякий раз, когда вы пытаетесь получить доступ к свойству объекта.
- Сначала он проверяет свойство в самом объекте.
- Если он не может найти его там, то свойство ищется в прототипе объекта.
- А если и туда не попасть, то ищется прототип прототипа, и так до тех пор, пока либо свойство не будет найдено, либо не будет достигнут конец цепочки, и в этом случае возвращается
undefined.
Поле [[prototype]] является скрытым полем, но мы можем установить его несколькими способами.
obj.__proto__: свойство__proto__устарело и больше не должно использоваться.Object.setPrototypeOf(obj, proto): устанавливает[[Prototype]]изobjнаproto.Object.create(proto, [descriptors]): создает пустой объект с заданнымprotoкак[[Prototype]]и необязательными дескрипторами свойств.
Метод Object.create немного мощнее, так как у него есть необязательный второй аргумент: дескрипторы свойств. Кроме того, изменение прототипа «на лету» с помощью Object.setPrototypeOf или obj.__proto__ — очень медленная операция, поскольку нарушает внутреннюю оптимизацию операций доступа к свойствам объекта.
По этим причинам мы будем использовать метод Object.create в остальной части статьи.
let animal = {
eats: true
};
let cat = Object.create(animal, {
meows: {
value: true
}
});
// we can find both properties in cat now:
console.log(cat.eats); // true
console.log(cat.meows); // true

Как мы видим, когда мы пытаемся прочитать cat.eats, которого нет в cat, JavaScript следует за ссылкой [[Prototype]] и находит ее в animal.
Можно сказать, что animal является прототипом cat или cat прототипически наследуется от animal.
Вот как сильны прототипы. Если в animal много полезных свойств и методов, то они автоматически становятся доступными в cat.
Имейте в виду, что объекты могут иметь только один [[Prototype]]. Они не могут наследовать более чем от одного.
Прототипы не используются в письменной форме
Прототип используется только для чтения свойств. Операции записи/удаления работают непосредственно с объектом.
В приведенном ниже примере мы назначаем собственное свойство dangerous для cat.
let animal = {
eats: true,
dangerous: true
};
let cat = Object.create(animal, {
meows: {
value: true
}
});
console.log(animal.dangerous); // true
// Updating cat object
cat.dangerous = false;
console.log(animal.dangerous); // true
console.log(cat.dangerous); // false

Отныне вызов cat.dangerous находит свойство сразу в объекте и выполняет его, не используя прототип.
Какова ценность «этого»?
Рассмотрим приведенный ниже пример.
let animal = {
eats: true,
doesItEat: function() {
return this.eats;
}
};
let cat = Object.create(animal, {
meows: {
value: true
}
});
cat.eats = false;
console.log(animal.doesItEat()); // true
console.log(cat.doesItEat()); // false
Может возникнуть интересный вопрос: каково значение this в строке return this.eats. Будь то animal или cat?
Ответ прост: this всегда стоит перед точкой. На прототипы это никак не влияет.
Итак, строка cat.eats = false использует cat как this, а не animal.
Подводя итог, можно сказать, что когда наследующие объекты запускают унаследованные методы, они изменяют только свои собственные состояния, а не состояние родителя.
Роль прототипов в цикле for…in
Цикл for..in также перебирает унаследованные свойства.
let animal = {
eats: true
};
let cat = Object.create(animal, {
meows: {
value: true,
enumerable: true
}
});
// for..in loops over both own and inherited keys
for(let prop in cat) console.log(prop); // meows, eats
Если мы хотим исключить унаследованные свойства, мы можем использовать встроенный метод obj.hasOwnProperty(key), который возвращает true, если key является собственным свойством (не унаследованным).
let animal = {
eats: true
};
let cat = Object.create(animal, {
meows: {
value: true,
enumerable: true
}
});
for(let prop in cat) {
let isOwn = cat.hasOwnProperty(prop);
if (isOwn) {
console.log(`Our: ${prop}`); // Our: meows
} else {
console.log(`Inherited: ${prop}`); // Inherited: eats
}
}
"Краткое содержание"
- Все объекты имеют скрытое свойство
[[Prototype]], которое либо ссылается на другой объект, либо имеет значениеnull. - Если мы хотим прочитать свойство или вызвать метод объекта, и если он не существует, то JavaScript пытается найти его в своей цепочке прототипов.
- Операции записи/удаления не задействуют прототипы, они действуют непосредственно на объект.
- Значение
thisвсегда является объектом, вызывающим функцию. - Цикл
for..inперебирает как собственные, так и унаследованные свойства.
Ура, ты дошел до конца. Надеюсь, ваши концепции сегодня немного прояснились. Оставайтесь с нами, чтобы увидеть больше таких сообщений.
Если вам понравилось, посмотрите и другие мои работы.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube,и Discord. Заинтересованы в Взлом роста? Ознакомьтесь с разделом Схема.