Прототип в JavaScript: це химерно, але ось як це працює

Наступних чотирьох рядків достатньо, щоб заплутати більшість розробників JavaScript:

Object instanceof Function//true
Object instanceof Object//true
Function instanceof Object//true
Function instanceof Function//true

Прототип у JavaScript - одна з найбільш приголомшливих концепцій, але уникнути цього не можна. Скільки б ви не ігнорували його, ви зустрінете головоломку-прототип протягом свого життя JavaScript.

Тож давайте прямо зіткнемось.

Починаючи з основ, у JavaScript є такі типи даних:

  1. невизначений
  2. нуль
  3. номер
  4. рядок
  5. логічний
  6. об'єкт

Перші п’ять - примітивні типи даних. Вони зберігають значення свого типу, наприклад, логічне значення, і можуть бути true або false .

Останній “об’єкт” - це посилальний тип, який ми можемо описати як сукупність пар ключ-значення (але це набагато більше).

У JavaScript нові об'єкти створюються за допомогою функції конструктора об'єктів (або об'єктного літералу ), що забезпечує загальні методи, такі як і .{}toString()valueOf()

Функції в JavaScript - це спеціальні об'єкти, які можна " викликати" . Ми робимо їх і за допомогою функції конструктора функції (або літералу функції). Той факт, що ці конструктори є об'єктами, а також функцією, мене завжди бентежив, приблизно так само, як загадка курячого яйця всіх бентежить.

Перш ніж почати з прототипів, я хочу пояснити, що в JavaScript є два прототипи:

  1. прототип : Це спеціальний об'єкт, який призначається як властивість будь-якої функції, яку ви робите в JavaScript. Дозвольте мені пояснити, він уже присутній для будь-якої функції, яку ви робите, але не є обов’язковим для внутрішніх функцій, що надаються JavaScript (і функцією, яку повертає bind). Це prototypeтой самий об’єкт, на який вказує символ[[Prototype]](див. нижче) новоствореного об’єкта з цієї функції (за допомогою newключового слова).
  2. [[Прототип]]: Це якось прихована властивість кожного об’єкта, до якого отримує доступ запущений контекст, якщо якесь властивість, яке читається на об’єкті, недоступне. Ця властивість просто є посиланням наprototypeфункції, з якої створено об’єкт. Доступ до нього можна здійснити за допомогою скрипта за допомогою спеціального викликавача-сеттера (тема для іншого дня) __proto__. Є й інші нові способи доступу до цього прототипу, але для стислості я згадаю[[Prototype]]використання __proto__.
var obj = {}var obj1 = new Object()

Наведені вище два твердження є рівними твердженнями, коли використовуються для створення нового об’єкта, але багато чого трапляється, коли ми виконуємо будь-який із цих тверджень.

Коли я роблю новий предмет, він порожній. Насправді він не є порожнім, оскільки є екземпляромObjectконструктор, і він за своєю суттю отримує посилання на prototypeз Object,на що вказує __proto__новоствореного об’єкта.

Якщо ми подивимося на prototypeз Objectконструктора функції, вона виглядає так само , як і __proto__в obj. самому справі, вони є два покажчика з посиланням на той же об'єкт.

obj.__proto__ === Object.prototype//true

Кожен prototypeфункціїмає властивість, що називається, constructorяка є покажчиком на саму функцію. У разі Objectфункції ,prototype маєconstructorщо вказує на Object.

Object.prototype.constructor === Object//true

На зображенні вище ліва сторона - розгорнутий вигляд Objectконструктора. Напевно, вам цікаво, які всі інші функції над ним. Ну, функції - це об’єкти , тому вони можуть мати над собою властивості, як і інші об’єкти.

Якщо придивитися, сам Object(ліворуч) має символ__proto__що означає, що Objectповинен бути зроблений з іншого конструктора, який має prototype. AsObjectє функційним об'єктом, він повинен бути зроблений за допомогою Functionконструктор.

__proto__з Objectвиглядає так само, як prototypeна Function. Коли я перевіряю рівність обох, вони виявляються однаковими об’єктами.

Object.__proto__ === Function.prototype//true

Якщо ви придивитеся, то побачите Functionсам має a __proto__що означає, що Functionфункція конструктораповинен бути зроблений з якоїсь функції конструктора, яка має a prototype. ЯкFunctionсама по собі є функцією , її, мабуть, було створено за допомогоюFunctionконструктор, тобто сам. Я знаю, що це звучить дивно, але коли ви перевіряєте це, це виявляється правдою.

__proto__з Functionіprototypeз Functionєнасправді два вказівники, що посилаються на один і той же об'єкт.

Function.prototype === Function.__proto__\\true

Як вже згадувалося раніше, constructorбудь-якийprototypeмає вказувати на функцію, яка цим володіє prototype. constructorз prototypeз Functionвказує назад на Functionсебе.

Function.prototype.constructor === Function\\true

Знову ж таки prototypeз Functionмає __proto__Ну, це не дивно ... prototypeце об’єкт, він може його мати. Але зауважте також, що це вказує наprototypeз Object.

Function.prototype.__proto__ == Object.prototype\\true

Тож ми можемо мати головну карту тут:

instanceof Operatora instanceof b

Theinstanceofоператор шукає об'єкт bвказав набудь-який з constructor(S) з прикутий__proto__на a. Прочитайте ще раз! Якщо він знаходить таке посилання, він повертаєтьсяtrueще false.

Тепер ми повертаємось до наших перших чотирьох instanceofзаяви. Я написав відповідні заяви, які роблюinstanceofповернення trueдля наступного:

Object instanceof FunctionObject.__proto__.constructor === Function
Object instanceof ObjectObject.__proto__.__proto__.constructor === Object
Function instanceof FunctionFunction.__proto__.constructor === Function
Function instanceof ObjectFunction.__proto__.__proto__.constructor === Object

Фуу !! Навіть спагеті менш заплутані, але я сподіваюся, зараз все стає ясніше.

Тут у мене є що - то , що я не вказував раніше , що prototypeзObjectне має __proto__.

Насправді він має __proto__але це дорівнює null. Ланцюг повинен був десь закінчуватися, і він закінчується тут.

Object.prototype.__proto__\\null

Наша Object, Function,Object.prototype іFunction.prototypeтакож мають властивості, які є функціями, такими як Object.assign,Object.prototype.hasOwnProperty іFunction.prototype.call. Це внутрішні функції, які не мають, prototypeа також є екземплярами Functionта мають a__proto__який є вказівником на Function.prototype.

Object.create.__proto__ === Function.prototype\\true

Ви можете дослідити інші функції конструктора, такі як ArrayіDate, або візьміть їхні предмети і шукайте prototypeі__proto__. Я впевнений, ви зможете зрозуміти, як все пов’язано.

Додаткові запити:

Там ще одне питання , який прослуховував мене на деякий час: Чому, prototypeз Objectє об'єктом і prototypeв Functionце функціональний об'єкт ?

Осьце гарне пояснення цього, якщо ви думали те саме.

Ще одне запитання, яке дотепер може бути для вас загадкою, полягає в наступному: Як примітивні типи даних отримують такі функції, як toString(),substr() і toFixed()?Це добре пояснено тут .

Використовуючи prototype, ми можемо змусити наслідування працювати з нашими користувацькими об’єктами в JavaScript. Але це тема для іншого дня.

Дякуємо за читання!