moștenirea clasei este o modalitate prin care o clasă poate extinde o altă clasă.

astfel încât să putem crea noi funcționalități pe partea de sus a existente.

„extinde” cuvinte cheie

Să spunem că avem clasa Animal:

Iată cum ne poate reprezenta animal obiect și Animal clasa grafic:

…Și am dori să creăm un alt class Rabbit.,

Ca iepurii sunt animale, Rabbit clasa ar trebui să se bazeze pe Animal, au acces la animale metode, astfel încât iepurii pot face ceea ce „generic” animalele pot face.

sintaxa pentru a extinde o altă clasă este: class Child extends Parent.

Să creăm class Rabbit care moștenește de la Animal:

Obiect de Rabbit clasa au acces atât la Rabbit metode, cum ar fi rabbit.hide(), și, de asemenea, să Animal metode, cum ar fi rabbit.run().,

intern,extends cuvinte cheie funcționează folosind mecanica prototip vechi bun. Acesta stabilește Rabbit.prototype.]la Animal.prototype. Deci, dacă o metodă nu este găsită în Rabbit.prototype, JavaScript, ia din Animal.prototype.,

De exemplu, pentru a găsi rabbit.run metoda, motorul controale (de jos în sus pe imagine):

Cum putem aminti la capitolul Nativ prototipuri, JavaScript se folosește prototypal moștenire pentru obiecte built-in. E. g. Date.prototype.] este Object.prototype. De aceea, datele au acces la metode de obiecte generice.,

Orice expresie este permis după extends

Clasa de sintaxă permite să se precizeze nu doar o clasa, dar orice expresie după extends.

De exemplu, un apel de funcție care generează clasa părinte:

function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello

Aici class User moștenește de la rezultatul de f("Hello").,

care pot fi utile pentru modele avansate de programare atunci când folosim funcții pentru a genera clase în funcție de multe condiții și pot moșteni de la ei.

suprascrierea unei metode

acum să mergem mai departe și să suprascriem o metodă. În mod implicit, toate metodele care nu sunt specificate în class Rabbit sunt luate direct „ca este” de class Animal.,

Dar dacă vom specifica metoda noastră în Rabbit, cum ar fi stop() atunci acesta va fi folosit în loc:

class Rabbit extends Animal { stop() { // ...now this will be used for rabbit.stop() // instead of stop() from class Animal }}

de Obicei nu vrem să înlocuiască un părinte metodă, ci mai degrabă de a construi pe partea de sus a acesteia pentru a optimiza sau a extinde funcționalitatea. Facem ceva în metoda noastră, dar apelăm metoda părinte înainte/după aceasta sau în proces.

clasele oferă "super" cuvinte cheie pentru asta.

  • super.method(...) pentru a apela o metodă părinte.,
  • super(...) pentru a apela un constructor părinte (numai în interiorul constructorului nostru).

De exemplu, să ne iepure ascundere automată atunci când s-a oprit:

Rabbit are stop metoda care solicită părinte super.stop() în acest proces.

constructor imperativ

cu Constructori devine un pic complicat.

până acum, Rabbit nu avea propriul constructor.,

în Conformitate cu specificația, dacă o clasă extinde o altă clasă și nu are nici un constructor, apoi următoarele „gol” constructor este generat:

class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); }}

după Cum putem vedea, practic numește părinte constructor trece toate argumentele. Asta se întâmplă dacă nu scriem un constructor al nostru.

acum să adăugăm un constructor personalizat la Rabbit. Se va specifica earLength în plus față de name:

Hopa!, Avem o eroare. Acum nu putem crea iepuri. Ce a mers prost?

răspunsul scurt este:

  • Constructorii din clasele moștenitoare trebuie să sune super(...) și (!) faceți-o înainte de a utiliza this.

…dar de ce? Ce se întâmplă aici? Într-adevăr, cerința pare ciudată.desigur ,există o explicație. Să intrăm în detalii, astfel încât să înțelegeți cu adevărat ce se întâmplă.

în JavaScript, există o distincție între o funcție constructor a unei clase moștenitoare (așa-numitul „constructor derivat”) și alte funcții., Un constructor derivat are o proprietate internă specială ]:"derived". E o etichetă internă specială.

această etichetă afectează comportamentul său cu new.

  • atunci Când o funcție regulate este executat cu new, se creează un obiect gol și atribuie-l la this.
  • dar când un constructor derivat rulează, nu face acest lucru. Se așteaptă ca constructorul părinte să facă acest lucru.,

Deci, un derivat constructor trebuie să numim super în scopul de a executa părinte (de bază) constructor, în caz contrar obiect pentru this nu va fi creat. Și vom primi o eroare.

Pentru Rabbit constructor pentru a lucra, trebuie să spună super() înainte de a utiliza this, ca aici:

Imperative clasa domenii: un complicat notă

Avansat notă

Acest act presupune că au o anumită experiență cu clase, poate și în alte limbaje de programare.,

Acesta oferă o perspectivă mai bună în limba și explică, de asemenea, comportamentul care ar putea fi o sursă de bug-uri (dar nu foarte des).dacă vi se pare greu de înțeles, continuați, continuați să citiți, apoi reveniți la ea ceva timp mai târziu.

putem suprascrie nu numai metodele, ci și câmpurile de clasă.

deși, există un comportament dificil atunci când accesăm un câmp suprascris în constructorul părinte, destul de diferit de cele mai multe alte limbaje de programare.,

luați în Considerare acest exemplu:

Aici, de clasă Rabbit extends Animal și suprascrie name teren cu propria sa valoare.

nu e nici propriul constructor în Rabbit, deci Animal constructor este numit.

ceea Ce este interesant este că în ambele cazuri: new Animal() și new Rabbit(), alert în linia (*) arată animal.,

cu alte cuvinte, constructorul părinte folosește întotdeauna propria valoare de câmp, nu cea suprascrisă.

ce este ciudat despre asta?

dacă nu este clar încă, vă rugăm să comparați cu metode.

Aici e acelasi cod, dar în loc de this.name domeniul numim this.showName() metoda:

vă Rugăm să notă: acum, de ieșire este diferită.

și asta ne așteptăm în mod natural. Când constructorul părinte este apelat în clasa derivată, acesta folosește metoda suprascrisă.

…dar pentru câmpurile de clasă nu este așa. După cum sa spus, constructorul părinte utilizează întotdeauna câmpul părinte.,

De ce există diferența?ei bine, motivul este în ordinea de inițializare a câmpului. Câmpul clasă este inițializat:

  • înainte de constructor pentru clasa de bază (care nu extinde nimic),
  • imediat după super() pentru clasa derivată.

în cazul nostru, Rabbit este clasa derivată. Nu există constructor() în ea. Așa cum am spus anterior, este același lucru ca și cum ar exista un constructor gol cu doar super(...args).,

Astfel, new Rabbit() apeluri super(), astfel executarea părinte constructor, și (pe regula pentru clasele derivate) numai după aceea clasa sa câmpuri sunt inițializate. La data de părinte constructor executie, nu există Rabbit clasa domenii, de aceea Animal câmpuri sunt utilizate.

această diferență subtilă între câmpuri și metode este specifică JavaScript

Din fericire, acest comportament se dezvăluie numai dacă un câmp suprascris este utilizat în constructorul părinte., Atunci poate fi dificil de înțeles ce se întâmplă, așa că explicăm aici.

dacă devine o problemă, se poate rezolva folosind metode sau getters/setters în loc de câmpuri.

Super: internals,]

informații avansate

dacă citiți tutorialul pentru prima dată – această secțiune poate fi omisă.este vorba despre mecanismele interne din spatele moștenirii și super.

să ne adâncim puțin sub capotasuper. Vom vedea câteva lucruri interesante pe parcurs.,

Mai întâi să spunem, din tot ceea ce am învățat până acum, este imposibil ca super să funcționeze deloc!da ,într-adevăr, să ne întrebăm, cum ar trebui să funcționeze din punct de vedere tehnic? Când se execută o metodă obiect, acesta devine obiectul curent ca this. Dacă apelăm super.method()atunci, motorul trebuie să obțină method din prototipul obiectului curent. Dar cum?

sarcina poate părea simplă, dar nu este., Motorul știe obiectul curent this, deci s-ar putea obține părinte method ca this.__proto__.method. Din păcate, o astfel de soluție” naivă ” nu va funcționa.

să demonstrăm problema. Fără clase, folosind obiecte simple de dragul simplității.

puteți sări peste această parte și mergeți mai jos la subsecțiunea ] dacă nu doriți să cunoașteți detaliile. Asta nu va face rău. Sau citiți mai departe dacă sunteți interesat să înțelegeți lucrurile în profunzime.

în exemplul de mai jos, rabbit.__proto__ = animal., Acum hai să încercăm: în rabbit.eat() vom numi animal.eat(), folosind this.__proto__:

La linia (*) ne ia eat de la prototip (animal) și de apel în contextul actualei obiect. Vă rugăm să rețineți că .call(this) este important aici, pentru că un simplu this.__proto__.eat() ar executa părinte eat în contextul prototip, nu obiectul curent.,

și în codul de mai sus funcționează de fapt conform destinației: avem corect alert.acum, să adăugăm încă un obiect la lanț. Vom vedea cum se strică lucrurile:

codul nu mai funcționează! Putem vedea eroarea încercând să sunăm longEar.eat().este posibil să nu fie atât de evident, dar dacă urmărim longEar.eat()apel, atunci putem vedea de ce. În ambele linii (*) și (**) valoarea this este obiectul curent (longEar)., Acest lucru este esențial: toate metodele obiect obține obiectul curent ca this, nu un prototip sau ceva.

Aici este o imagine a ceea ce se întâmplă:

problema nu poate fi rezolvată prin utilizarea this singur.pentru a furniza soluția, JavaScript adaugă încă o proprietate internă specială pentru funcții: ].,

când o funcție este specificată ca metodă de clasă sau obiect, proprietatea ] devine acel obiect.

apoi super îl folosește pentru a rezolva prototipul părinte și metodele sale.

Să vedem cum funcționează, mai întâi cu obiecte simple:

metodele nu sunt „libere”

așa cum am cunoscut înainte, în general funcțiile sunt „libere”, nu sunt legate de obiecte în JavaScript. Deci, ele pot fi copiate între obiecte și apelate cu un alt this.,

însăși existența ] încalcă acest principiu, deoarece metodele își amintesc obiectele. ] nu poate fi schimbat, deci această legătură este pentru totdeauna.

singurul loc în limba în care ] este folosit – este super. Deci, dacă o metodă nu utilizează super, atunci o putem considera liberă și copiată între obiecte. Dar cu super lucrurile pot merge prost.,

Aici e demo-ul a greșit super rezultat după copiere:

Un apel la tree.sayHi() arată „sunt un animal”. Categoric greșit.

motivul este simplu:

Aici e diagrama de ceea ce se întâmplă:

Metode, nu în funcție de proprietăți

diferența poate fi non-esențial pentru noi, dar e important pentru JavaScript.

în exemplul de mai jos este utilizată o sintaxă non-metodă pentru comparație., ] proprietate nu este stabilit și moștenirea nu merge:

Sumar

  1. Pentru a extinde o clasa: class Child extends Parent:
    • Asta înseamnă Child.prototype.__proto__ va fi Parent.prototype, deci metode sunt moștenite.
  2. Când imperative un constructor:
    • trebuie să Facem apel părinte constructor ca super() în Child constructor înainte de a utiliza this.,
  3. Când imperative o altă metodă:
    • putem folosi super.method() intr-un Child metodă de a apela Parent metoda.
  4. Internals:
    • metode amintesc clasa lor / obiect în intern] proprietate. Acesta este modul în care super rezolvă metodele părinte.
    • deci nu este sigur să copiați o metodă cu super de la un obiect la altul.,

de Asemenea,

  • Săgeată funcții nu au propriile lor this sau super, astfel încât acestea se potrivesc în mod transparent în jur de context.