Прототипы
Прототипы в JavaScript существовали с самого момента его создания.
Идея прототипов следующая.
Представим, что у нас есть несколько видов животных, у каждого из которых есть функция say(), которая воспроизводит голос этого животного.
Этот родительский объект, хранящий функции всех его потомков, называется прототипом. Правило обращения к методам прототипа следующее. Вначале js-движок ищет функцию внутри самого объекта, если не находит - ищет в его прототипе.
Но прототип - тоже объект и у него есть собственный прототип.
Таким образом js-движок в поисках вызванной функции проходит по всей цепочке прототипов
Перейдём от теории к коду.
Вначале напишем код животных, у каждого из которых есть функция say()
Прототипов пока нет
const dog = {
name: 'dog',
voice: 'woof',
say: function() {
console.log(this.name, 'says', this.voice)
}
}
const cat = {
name: 'cat',
voice: 'meow',
say: function() {
console.log(this.name, 'says', this.voice)
}
}
dog.say(); // dog says woof
cat.say(); // cat says meow
Вынесем функцию say() в объект animal:
const animal = {
say: function() {
console.log(this.name, 'says', this.voice)
}
}
const dog = {
name: 'dog',
voice: 'woof'
}
const cat = {
name: 'cat',
voice: 'meow'
}
dog.say();
cat.say();
Ожидаемо получаем ошибку: объекты dog и cat никак не связаны с объектом animal и ничего не знают о хранящихся в нём методах.
Попробуем связать эта объекты и указать. что animal является прототипом для dog и cat
1-й способ
Используем появившуюся в 2015 году функцию Object.setPrototypeOf, которая позволяет связать объект и его прототипconst animal = {
say: function() {
console.log(this.name, 'says', this.voice)
}
}
const dog = {
name: 'dog',
voice: 'woof'
}
Object.setPrototypeOf(dog, animal)
const cat = {
name: 'cat',
voice: 'meow'
}
Object.setPrototypeOf(cat, animal)
dog.say(); // dog says woof
cat.say(); // cat says meow
Всё работает. Проблема в том, что Object.setPrototypeOf очень плохо влияет на производительность и использовать её не рекомендуется
2-й способ
Используем Object.create()const animal = {
say: function() {
console.log(this.name, 'says', this.voice)
}
}
const dog = Object.create(animal);
dog.name = 'dog';
dog.voice = 'woof';
dog.say(); // dog says woof
Строка const dog = Object.create(animal); означает, что мы создаём объект dog при помощи метода Object.create(). Прототипом созданного объекта dog будет объект animal.
3-й способ
Функция-конструктор newСделаем предыдущий код чуть лучше, вынесем функцию создания объекта и прототипа в отдельную функцию createAnimal
const animal = {
say: function() {
console.log(this.name, 'says', this.voice)
}
}
function createAnimal(name, voice) {
const result = Object.create(animal);
result.name = name;
result.voice = voice;
return result;
}
const dog = createAnimal('dog', 'woof');
dog.say(); // dog says woof
Функция createAnimal - это функция-конструктор, при помощи которой можно удобно и быстро создавать новых животных.
Но такая функция в JavaScript уже есть. для её вызова используется ключевое слово new
Сравним функции createAnimal и new Animal
function Animal(name, voice) {
this.name = name;
this.voice = voice;
}
Animal.prototype.say = function() {
console.log(this.name, 'says', this.voice)
}
const dog = new Animal('dog', 'woof');
dog.say(); // dog says woof
Такой код неплохо работает и использовался с самых первых дней создания JavaScript. Дальше пойдёт речь о классах, которые являются синтаксическим сахаром, семантической обёрткой над прототипами и позволяют их проще и удобнее использовать.
Да, если вдруг понадобится создать объект без прототипа, это удобно делать при помощи Object.create(null)
const obj = Object.create(null);
console.log(obj.toString()) // TypeError