Прототип в 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 новите обекти се правят с помощта на функцията Object constructor (или обектния литерал ), която предоставя общи методи като и .{}toString()valueOf()

Функциите в JavaScript са специални обекти, които могат да бъдат „ извикани“ . Ние ги правим и с помощта на функцията конструктор на функции (или литерал на функция). Фактът, че тези конструктори са обекти, както и функция, винаги ме е обърквал, по същия начин, както гатанката с пилешко яйце обърква всички.

Преди да започна с прототипи, искам да поясня, че в JavaScript има два прототипа:

  1. прототип : Това е специален обект, който е присвоен като свойство на всяка функция, която правите в JavaScript. Нека да бъда ясен тук, той вече присъства за всяка функция, която правите, но не е задължителен за вътрешни функции, предоставени от JavaScript (и функцията, върната от bind). Това prototypeе същият обект, към който е посочен[[Prototype]](вижте по-долу) на новосъздадения обект от тази функция (с помощта на newключова дума).
  2. [[Прототип]]: Това е някак скрито свойство за всеки обект, до който се осъществява достъп от текущия контекст, ако някакво свойство, което се чете в обекта, не е налично. Това свойство просто е препратка къмprototypeна функцията, от която е направен обектът. Той може да бъде достъпен в скрипт с помощта на специален getter-setter (тема за друг ден) __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(отляво) има a__proto__което означава, че Objectтрябва да е направен от друг конструктор, който има prototype. AsObjectе функционален обект, трябва да е направен с помощта на Functionконструктор.

__proto__на Objectизглежда същото като prototypeна Function. Когато проверя равенството и на двете, те се оказват едни и същи обекти.

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

Ако се вгледате внимателно, ще видите Functionсебе си има __proto__което означава, че Functionфункция конструктортрябва да са направени от някаква конструкторска функция, която има prototype. КатоFunctionсамо по себе си е функция , трябва да е направено с помощта наFunctionконструктор, тоест себе си. Знам, че звучи странно, но когато го проверите, се оказва вярно.

The __proto__на Functionиprototypeна Functionсавсъщност два указателя, отнасящи се до един и същ обект.

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

Както бе споменато по-рано, constructorна всекиprototypeтрябва да сочи към функцията, която притежава това prototype.The 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. Но това е тема за друг ден.

Благодаря за четенето!