Ръководство за наследяване на базирани на прототипи в JavaScript

Компютърните езици често предоставят начин за наследяване на един обект
друг обект. Наследственият обект съдържа всички свойства от неговия родителски обект. В допълнение, тя ще посочи и свой собствен набор от уникални свойства.

Следвайте ме в Twitter за съвети от JavaScript и обяви за книги.

JavaScript обекти използват базирани на прототип наследяване. Дизайнът му е логически подобен (но различен в изпълнението) от наследяването на класове в строго обектно ориентирани езици за програмиране.

Може да се опише слабо, като се каже, че когато методите или свойствата са прикачени към прототипа на обекта, те стават достъпни за използване на този обект и неговите потомци. Но този процес често се осъществява зад кулисите.

Когато пишете код, дори няма да е необходимо да докосвате директно свойството на прототипа. Когато изпълнявате метода на разделяне, бихте го нарекли директно от строен буквал като: "здравей" .split ("e") или от променлива: string.split (",");

Когато използвате клас и разширява ключовите думи вътре, JavaScript все пак ще използва наследяване, основано на прототип. Той просто опростява синтаксиса. Може би затова е важно да разберем как работи наследяването въз основа на прототипи. Тя все още е в основата на езиковия дизайн.

Ето защо в много уроци ще видите String.prototype.split написан вместо просто String.split. Това означава, че има метод сплит, който може да се използва с обекти от тип низ, защото е прикачен към свойството прототип на този обект.

Създаване на логическа йерархия на типовете обекти

Котка и куче са наследени от домашен любимец, който е наследен от животни.

Куче и котка споделят подобни черти. Вместо да създавате два различни класа,
можем просто да създадем един клас домашен любимец и да наследим котка и куче от него. Но самият клас Pet също може да бъде наследен от класа Animal.

Преди да започнем

Опитът да разберете прототипите е като да прекосите реката, преминавайки от кодиране до дизайн на компютърен език. Две напълно различни области на знанието.

Технически, само лекото познаване на класа и разширява ключовите думи е достатъчно, за да напишете софтуер. Опитът да разберете прототипа е като да влезете в по-тъмните ъгли на езиковия дизайн. И понякога това може да бъде проницателно.

Този урок сам по себе си няма да е достатъчен. Фокусирах се само върху някои важни неща, които, надявам се, ще ви насочат в правилната посока.

Под капака

Идеята зад обектното наследяване е да се осигури структура за йерархия на
подобни обекти. Можете също така да кажете, че детският обект е „получен“ от неговия родител.

Как се създават прототипните вериги в JavaScript.

Технически това изглежда. Опитайте се да не мислите прекалено много в това. Просто знайте, че в самия връх на йерархията има обектът Object. Ето защо собственият му прототип сочи нула. Няма нищо над него.

Основано на прототип наследяване на обекти

JavaScript поддържа наследяване на обекти чрез нещо, известно като прототипи. Към всеки обект има свойство на обект, наречено прототип.

Работата с класа и разширява ключовите думи е лесна, но всъщност да разберете как работи наследственото базиране на прототип, не е тривиално. Дано този урок ще повдигне поне част от мъглата!

Функции на обектния конструктор

Функциите могат да се използват като конструктори на обекти. Името на конструкторската функция обикновено започва с главна буква, за да се направи разграничението между редовни функции. Конструкторите на обекти се използват за създаване на екземпляр на обект.

Някои от вградените JavaScript обекти вече са създадени, следвайки същите правила. Например Number, Array и String са наследени от Object. Както обсъждахме по-рано, това означава, че всяко свойство, свързано с Object, става автоматично достъпно за всички негови деца.

конструкторите

Невъзможно е да се разбере прототип, без да се разбере анатомията на функциите на конструктора.

И така, какво точно се случва, когато създаваме функция за персонализиран конструктор? Две свойства магически се появяват в определението на нашия клас: конструктор и prototype.constructor.

Те не сочат към един и същ обект. Нека ги разбием:

Да речем, че определяме нов клас Crane (използвайки или функция, или ключова дума).

Персонализиран конструктор, който току-що създадохме, сега е прикачен към свойството прототип на нашия потребителски клас Crane. Това е връзка, която сочи към своя конструктор. Той създава кръгова логика. Но това е само едно парче от пъзела.

Нека сега да разгледаме Crane.constructor:

Самият Crane.constructor посочва вида на обекта, от който е създаден.
Тъй като всички конструктори на обекти са първоначално функции, обектът Crane.constructor сочи към обект от тип Функция, с други думи конструкторът на функции.

Тази динамика между Crane.prototype.constructor и Crane.constructor е това, което дава възможност за наследяване на прототипа на молекулно ниво. Рядко дори се налага да мислите за това, когато пишете JavaScript код. Но това определено е въпрос за интервю.

Нека да разгледаме накратко това отново. Crane.prototype.constructor сочи свой собствен конструктор. Това е почти като да кажете „Аз съм аз.“

Същото точно става, когато дефинирате клас, използвайки ключова дума клас:

Но свойството Crane.constructor сочи към конструктора на функции.

И така се установява връзката.

Сега самият обект Crane може да бъде „прототипът“ на друг обект. И този обект може да бъде прототип на друг обект. И така нататък. Веригата може да продължи вечно.

Странична забележка: В случая на функциите в стил ES5 самата функция е
конструктор. Но ключовата дума от клас ES6 поставя конструктора в обхвата му. Това е просто синтактична разлика.

Наследство, основано на прототип

Винаги трябва да използваме класа и разширява ключовите думи за създаване и наследяване на обекти. Но те са само обвивка за бонбони за онова, което всъщност се случва зад кулисите.

Въпреки че създаването на йерархии за наследяване на обекти с помощта на синтаксис в стил ES5 е отдавна остаряло и рядко се наблюдава сред професионалните разработчици на софтуер, като го разберете, ще получите по-задълбочен поглед върху това как всъщност работи.

Да определим нов обект Bird и да добавим 3 свойства: вид, цвят и яйца. Нека добавим и 3 метода: летене, разходка и полагане на яйца. Нещо, което всички птици могат да направят:

Обърнете внимание, че умишлено се отказах от метода lay_egg. Спомнете си как ние
обсъдени по-рано, че Bird.prototype сочи свой собствен конструктор?

Възможно е алтернативно да прикачите метода за снасяне на яйца директно към Bird.prototype, както е показано в следващия пример:

На пръв поглед може да звучи, че няма разлика между методите за прикачване, използвайки тази ключова дума вътре в Bird, и просто добавянето й директно към свойството Bird.prototype. Защото все още работи нали?

Но това не е напълно вярно. Все още няма да навлизам в подробности, защото не разбирам напълно разликата тук. Но аз планирам да актуализирам този урок, когато събера малко повече информация по темата.

(коментари от прототипи ветерани са добре дошли!)

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

Целият смисъл на наследяване на обекти е да се използва един общ клас, който дефинира всички свойства и методи, които всички деца от този клас автоматично ще наследят. Това прави кода по-кратък и спестява памет.

(Представете си да дефинирате едни и същи свойства и методи на всички обекти на децата поотделно отначало. Това ще отнеме два пъти повече памет.)

Нека създадем няколко различни вида птици. Въпреки че всички те все още могат да летят, да ходят и полагат_eggs (тъй като са наследени от основния клас Bird,), всеки уникален тип птица ще добави свои собствени методи, уникални за този клас. Например само папагали могат да говорят. И само гарваните могат да решават пъзели. Само песен може да пее.

папагал
Нека създадем папагал и да го наследим от Bird:

Папагалът е редовна функция на конструктора точно като Bird.

Разликата е, че призоваваме конструктора на Bird с Bird.call и предаваме този контекст на Папагал, преди да прикачим собствените си методи. Bird.call просто добавя всички свои свойства и методи към Parrot. В допълнение към това ние добавяме и свой собствен метод: говорим.

Сега папагалите могат да летят, да ходят, да снасят яйца и да говорят! Но ние никога не е трябвало да определяме методите за разходка и летене на яйца в самия Parrot.

гарван
По същия начин нека създадем Рейвън и да го наследим от Bird:

Гарваните са уникални по това, че могат да решават пъзели.

пойна птичка
Сега, нека създадем Songbird и да го наследим от Bird:

Певците могат да пеят.

Тестване на птиците

Току-що създадохме куп различни птици с уникални способности. Нека да видим какво
те са способни! Досега само дефинирахме класове и установихме техните
йерархична връзка.

За да работим с обекти, трябва да ги инстанцираме:

Да дадем индикация на врабче с помощта на оригиналния конструктор Bird:

Врабчето може да лети, да ходи и да снася яйца, защото е наследено от Bird, което определя всички тези методи.

Но врабче не може да говори. Защото не е Папагал.

Нека създадем папагал от клас Папагал:

Тъй като Папагалът е наследен от Bird, ние получаваме всички негови методи. Папает има уникалната способност да говори, но не може да пее! Методът на пеене е достъпен само за обекти от тип Songbird. Да наследим скорци от клас Songbird:

И накрая, нека създадем гарван и да решим някои загадки:

Използване на клас и разширява ключови думи

Конструкторите в стил ES5 могат да бъдат малко тромави.

За щастие сега имаме клас и разширява ключови думи, за да постигнем абсолютно същото нещо, което направихме в предишния раздел.

клас замества функция

extends и super () заменят Bird.call от предишните примери.

Забележете, че трябва да използваме super (), което извиква конструктора на родителския клас.

Този синтаксис изглежда много по-управляем!

Сега всичко, което трябва да направим, е инстанциране на обекта:

Overview

Класовото наследяване помага да се установи йерархия на обектите.

Класовете са основните градивни елементи на дизайна и архитектурата на вашето приложение. Те правят работата с код малко по-човешка.

Разбира се, Bird беше просто пример. В сценарий в реалния свят може да бъде всичко, което се основава на типа на приложението, което се опитвате да изградите.

Класът на превозното средство може да бъде родител на Мотоциклет, Кола или Резервоар.

Рибите могат да се използват за наследяване на акула, златна рибка, щука и т.н.

Наследяването ни помага да напишем по-чист код и да пренасочим родителския обект, за да спестим памет при повтарящи се свойства на обекта и дефиниции на метод.