Прототип JavaScript — это…

Да, я знаю, что это не так просто понять, но как программист JavaScript вы не можете это игнорировать. Так что давайте сделаем это дерьмо вместе.

Вот краткий предварительный просмотр тем.

  • Что такое прототип JavaScript?
  • [[Прототип]] и прототип
  • Прототип
  • Атрибут прототипа против свойства прототипа
  • Функция конструктора
  • Наследование прототипов и цепочка прототипов
  • Прототип против класса
  • Function.proto === Function.prototype
  • Вывод
  • Ресурсы

Что такое прототип JavaScript?

Прежде чем говорить о прототипе JavaScript, вы должны сначала понять JavaScript Object.

Вот быстрый просмотр объекта JavaScript.

  1. Объект является одним из семи типов данных.
  2. В отличие от других шести примитивов, объект изменчив.
  3. Объект хранится в виде последовательности пар имя-значение, и каждый элемент в списке называется свойством (функции называются методами).

Для получения дополнительной информации вы можете проверить эту замечательную статью: 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. Именно об этом я и буду говорить, о Наследовании прототипов.

Наследование прототипов и цепочка прототипов

Поскольку люди разные, мы не должны помещать все символы в Humanconstructor. Мы можем использовать метод 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

Ресурсы

  1. Прототип JavaScript на простом языке
  2. Объект.прототип | МДН
  3. Вы не знаете JS: это и прототипы объектов
  4. Понимание прототипов и наследования в JavaScript
  5. JS: атрибут против свойства
  6. Понимание объектов в JavaScript
  7. Javascript: прототип против класса
  8. Трудности JavaScript-прототипа
  9. Дезинелео | Дизайн и разработка