Прототип JavaScript — это…
Да, я знаю, что это не так просто понять, но как программист JavaScript вы не можете это игнорировать. Так что давайте сделаем это дерьмо вместе.
Вот краткий предварительный просмотр тем.
- Что такое прототип JavaScript?
- [[Прототип]] и прототип
- Прототип
- Атрибут прототипа против свойства прототипа
- Функция конструктора
- Наследование прототипов и цепочка прототипов
- Прототип против класса
- Function.proto === Function.prototype
- Вывод
- Ресурсы
Что такое прототип JavaScript?
Прежде чем говорить о прототипе JavaScript, вы должны сначала понять JavaScript Object.
Вот быстрый просмотр объекта JavaScript.
- Объект является одним из семи типов данных.
- В отличие от других шести примитивов, объект изменчив.
- Объект хранится в виде последовательности пар имя-значение, и каждый элемент в списке называется свойством (функции называются методами).
Для получения дополнительной информации вы можете проверить эту замечательную статью: JavaScript Objects in Detail
Как мы все теперь знаем, объект — это наиболее часто используемый и самый фундаментальный тип данных в JavaScript. А прототип — это тоже объект как свойство любой функции. Таким образом, мы можем сказать, что JavaScript — это язык на основе прототипов, что означает, что свойства и методы объектов могут совместно использоваться через обобщенные объекты, которые можно клонировать и расширять. Это известно как прототипное наследование и отличается от наследования классов. Позже я расскажу о разнице между прототипом и классом.
[[Прототип]] и прототип
У каждого объекта в JavaScript есть внутреннее свойство [[Prototype]]
. Мы можем продемонстрировать это, создав новый пустой объект. Есть два способа создать объект в JavaScript:
- Литерал объекта — с помощью фигурных скобок:
{}
- Конструктор объектов — с помощью ключевого слова
new
.
// I'm used to use the Object literal
let apple = {};
let orange = new Object();
Имейте в виду, что [[Prototype]]
— это внутреннее свойство, что означает, что к нему нельзя получить доступ непосредственно в коде. Мы можем использовать метод Object.getPrototypeOf()
или свойство .__proto__
. Однако .__proto__
— это устаревшая функция, ее нельзя использовать в рабочем коде. Хотя вы можете найти его в таких браузерах, как Chrome и FireFox, он присутствует не во всех современных браузерах.
// output: {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
Object.getPrototypeOf(apple);
site1.__proto__;
Прототип
Что, если мы создадим функцию?
function Human(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
return `Hi, I'm ${name}`;
}
}
Как видите, эта функция немного отличается от обычной функции, так как первая буква заглавная. Она будет работать как обычная функция, пока не будет вызвана экземпляром с ключевым словом new
. В JavaScript первая буква функции-конструктора пишется с заглавной буквы по соглашению.
Напомним, что при создании функции Human
движок JavaScript добавляет свойство prototype к Human
. Это свойство prototype является объектом, который по умолчанию имеет свойство constructor, а свойство constructor указывает на функцию Human
.
Мы можем получить доступ к свойству прототипа, используя Human.prototype
.
Human === Human.prototype.constructor // true
Атрибут прототипа против свойства прототипа
Согласно статье JavaScript Is Sexy, прототип в JavaScript — это две разные взаимосвязанные концепции. Это атрибут прототипа и свойство прототипа. Но не путайте их с атрибутами в HTML. Для получения дополнительной информации ознакомьтесь с разделом JS: атрибут или свойство.
Функция конструктора
Функции-конструкторы используются для создания новых объектов. Например, давайте добавим нового человека.
const leo = new Human('Leo', 26);
console.log(leo); // Human {name: "Leo", age: 26, sayHello: ƒ}
Как мы видим, новый объект был создан с новыми свойствами, установленными, как и ожидалось.
Object.getPrototypeOf(leo); // constructor: ƒ Human(name, age)
leo.sayHello(); // "Hi, I'm Leo"
Что, если мы добавим новый метод в Human
?
Human.prototype.sayGoodbye = function () { return `${this.name} just said goodbye to you.`; }
leo.sayGoodbye(); // "Leo just said goodbye to you."
Как видите, sayGoodbye()
является прототипом Human
, а leo
является экземпляром Human
, метод также доступен для leo
. Именно об этом я и буду говорить, о Наследовании прототипов.
Наследование прототипов и цепочка прототипов
Поскольку люди разные, мы не должны помещать все символы в Human
constructor. Мы можем использовать метод call()
для копирования свойств из одного конструктора в другой конструктор. Давайте создадим конструктор Developer
.
function Developer(name, age, device) { // Chain constructor with call Human.call(this, name, age); this.device = device; }
Developer.prototype.developing = function() { return `Currently, ${this.name} is using ${this.device} to do the coding.`; }
const jack = new Developer('Jack', 24, 'MacBook Pro'); // "Currently, Jack is using MacBook Pro to do the coding."
Это работает довольно хорошо, но что, если вы хотите использовать методы дальше по цепочке прототипов?
jack.sayGoodbye();
// Uncaught TypeError: jack.sayGoodbye is not a function
Свойства и методы прототипа не связываются автоматически, когда вы используете call()
для цепочки конструкторов. Здесь мы можем использовать Object.create()
для связи прототипов.
Developer.prototype = Object.create(Human.prototype);
const mike = new Develoer('Mike', 25, 'Surface Laptop'); mike.sayGoodbye(); // "Mike just said goodbye to you."
Прототип против класса
Класс JavaScript, представленный в ECMAScript 2015, в первую очередь представляет собой синтаксический сахар по сравнению с существующим в JavaScript наследованием на основе прототипов. Синтаксис класса не вводит в JavaScript новую объектно-ориентированную модель наследования.
При наследовании классов экземпляры наследуются от схемы (класса) и создают отношения подклассов. Другими словами, вы не можете использовать класс так же, как экземпляр. Вы не можете вызывать методы экземпляра для самого определения класса. Вы должны сначала создать экземпляр, а затем вызывать методы для этого экземпляра.
При прототипном наследовании экземпляры наследуются от других экземпляров. Использование прототипов делегатов (установка прототипа одного экземпляра для ссылки на примерный объект) буквально означает связывание объектов с другими объектами или OLOO, как это называет Кайл Симпсон. Используя конкатенацию, вы просто копируете свойства объекта-экземпляра в новый экземпляр.
Function.proto === Function.prototype
Это может сбить вас с толку, позвольте мне объяснить, почему это действительно так. Во-первых, движок JavaScript создает Object.prototype
, а затем создает файл Function.prototype
. Оба они связаны с __proto__
. И из-за этого механизма let func = Object.create(Function.prototype)
не имеет свойства прототипа.
Все остальные функции-конструкторы могут найти финальный Function.prototype
через цепочку прототипов. а function Function()
— это действительно обычная функция. Таким образом, мы можем получить Function.__proto__ === Function.prototype
.
Вывод
Function.__proto__ === Function.prototype
Function.__proto__.__proto__ === Object.prototype
Function.prototype.__proto__ === Object.prototype