Implementation of Inheritance Mechanism in ECMAScript

Implementering af arvsmekanismen

For at implementere arvsmekanismen med ECMAScript kan du starte med den grundlæggende klasse, du ønsker at arve fra. Alle klasser defineret af udviklere kan bruges som grundklasser. Af sikkerhedsmæssige grunde kan lokale klasser og værter ikke bruges som grundklasser, hvilket forhindrer公用adgang til compilerede browser-level kode, som kan bruges til ondsindede angreb.

Efter at have valgt grundklassen, kan du oprette dens underklasser. Om du bruger grundklassen, er helt op til dig. Nogle gange kan du ønske at oprette en grundklasse, der ikke kan bruges direkte, men kun bruges til at give underklasser almindelige funktioner. I dette tilfælde betragtes grundklassen som en abstrakt klasse.

Selvom ECMAScript ikke definerer abstrakte klasser så strengt som andre sprog, opretter det undertiden nogle klasser, der ikke må bruges. Typisk kalder vi disse klasser for abstrakte klasser.

Oprette underklasser arver alle egenskaber og metoder fra overklassen, herunder konstruktoren og metoderne. Husk, at alle egenskaber og metoder er offentlige, så underklassen kan direkte tilgå disse metoder. Underklassen kan også tilføje nye egenskaber og metoder, der ikke findes i overklassen, og kan også overridere egenskaber og metoder fra overklassen.

Arvsmekanismer

Som andre funktioner har ECMAScript flere måder at implementere arv på. Dette skyldes, at arvemekanismen i JavaScript ikke er klart specificeret, men opnås gennem efterligning. Dette betyder, at alle detaljer om arv ikke behandles fuldt ud af fortolkeren. Som udvikler har du ret til at beslutte den mest passende arvsmekanisme.

Her vil vi præsentere nogle specifikke arvemekanismer.

Object masquerading

Når der blev konceptualiseret ECMAScript, var der ingen planer om at designe object masquerading (object masquerading). Det udviklede sig først, efter at udviklere begyndte at forstå, hvordan funktioner fungerer, især hvordan nøglenordet this bruges i funktionens miljø.

Princippet er som følger: Konstruktøren bruger nøglenordet this til at tildel alle egenskaber og metoder (dvs. i overensstemmelse med konstruktørens erklæring i klassen). Da konstruktøren kun er en funktion, kan konstruktøren ClassA blive brugt som en metode for ClassB, og derefter kaldes den. ClassB vil modtage egenskaber og metoder defineret i ClassA's konstruktør. For eksempel kan ClassA og ClassB defineres som følger:

function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor) {
}

Husker du? Nøglenordet this refererer til den objekt, der aktuelt oprettes af konstruktøren. Men i denne metode peger this på det objekt, som tilhører. Denne princip er at bruge ClassA som en almindelig funktion til at opbygge arvemekanismen, i stedet for som en konstruktør. Som følge heraf kan konstruktøren ClassB implementere arvemekanismen som følger:

function ClassB(sColor) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
}

I denne kode tildeler vi metoden newMethod til ClassA (husk, at funktionens navn kun er en pejle til den). Derefter kalder vi denne metode og overfører ClassB's konstruktionsparametre sColor til den. Det sidste stykke kode sletter referencen til ClassA, så den kan ikke længere kalles fremover.

Alle nye egenskaber og metoder skal defineres efter at den nye metode er blevet slettet. ellers kan det være, at de overordnede klassers relaterede egenskaber og metoder bliver overskrevet:

function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

For at bevise, at den tidligere kode virker, kan du køre følgende eksempel:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//Udskriver "blue"
objB.sayColor();	//Udskriver "red"
objB.sayName();        //Udskriver "John"

Object masquerading kan opnå flerarvelighed

Det er interessant, at object masquerading understøtter flerarvelighed. Det vil sige, at en klasse kan arve flere superklasser. Flerarvelighedsmechanismen vist med UML er som vist nedenfor:

Inheritance Mechanism UML Diagram Example

For eksempel, hvis der findes to klasser ClassX og ClassY, kan ClassZ arve disse to klasser ved hjælp af følgende kode:

function ClassZ() {
    this.newMethod = ClassX;
    this.newMethod();
    delete this.newMethod;
    this.newMethod = ClassY;
    this.newMethod();
    delete this.newMethod;
}

TIY

Der er en ulempe, hvis to klasser ClassX og ClassY har samme navngivne egenskaber eller metoder, har ClassY højere prioritet, fordi den arver fra klassen bagved. Udover dette lille problem kan man nemt implementere en flerledet arvemekanisme med objektmekanisme.

På grund af populariteten af denne arvemekanisme tilføjede ECMAScript's tredje version to metoder til Function-objektet, nemlig call() og apply().

call() metoden

call() metoden er mest lignende til den klassiske objektmekanisme til påfald. Den første parameter bruges som objekt for this. De øvrige parametre passes direkte til funktionen. For eksempel:

function sayColor(sPrefix,sSuffix) {
    alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");

I dette eksempel er funktionen sayColor() defineret uden for objektet, selvom den ikke tilhører nogen objekt, kan vi bruge nøgleordet this. Objektets color-egenskab er lig med blue. Når call() metoden kaldes, er den første parameter obj, hvilket betyder, at værdien for this i sayColor() funktionen skal være obj. De anden og tredje parameter er strenge. De matcher parameterne sPrefix og sSuffix i sayColor() funktionen, og den sidst genererede besked "The color is blue, a very nice color indeed." vises til sidst.

For at bruge denne metode sammen med objektmekanisme til arv, skal du blot erstatte de første tre linjer med tildeling, kald og sletning af kode:

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;
    ClassA.call(this, sColor);
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

TIY

Her skal vi have ClassA's nøgleord this at være lig med den nyoprettede ClassB-objekt, så this er den første parameter. Den anden parameter sColor er unik for begge klasser.

apply() metoden

apply() metoden har to parametre, der bruges som objekt for this og en array af parametre, der skal passes til funktionen. For eksempel:

function sayColor(sPrefix,sSuffix) {
    alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));

Dette eksempel er det samme som det tidligere eksempel, men nu kaldes der apply() -metoden. Når man kalder apply() -metoden, er den første parameter stadig obj, hvilket betyder, at man skal tildele værdien af this-nøglen til sayColor() -funktionen obj. Den anden parameter er en array med to strenge, der matcher parameterne sPrefix og sSuffix i sayColor() -funktionen, og den genererede besked er stadig "The color is blue, a very nice color indeed.". Beskeden vises derefter.

Denne metode bruges også til at erstatte de tre første linjer med tildeling, kald og sletning af den nye metode:

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;
    ClassA.apply(this, new Array(sColor));
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

På samme måde er den første parameter stadig this, og den anden parameter er et array med én værdi color. Man kan overføre hele arguments-objektet fra ClassB som den anden parameter til apply() -metoden:

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;
    ClassA.apply(this, arguments);
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

TIY

Selvfølgelig kan man kun overføre et parameterobjekt, hvis rækkefølgen af parametre i superklassen er identisk med rækkefølgen af parametre i subclasses. Hvis ikke, skal man oprette en separat array, hvor parametrene placeres i den korrekte rækkefølge. Derudover kan man bruge call() -metoden.

Prototypelængen (prototype chaining)

Denne form for arv bruges oprindeligt i ECMAScript til prototype-længen. Det forrige kapitel introducerede metoden til at definere klasser ved hjælp af prototype. Prototypelængen udvider denne metode og implementerer arvsmekanismer på en interessant måde.

Som nævnt i det forrige kapitel, er prototype-objektet en skabelon, og alle instanser, der skal oprettes, baseres på denne skabelon. Kort sagt, alle egenskaber og metoder på prototype-objektet overføres til alle instanser af klassen. Prototypelængen udnytter denne funktion til at implementere arvsmekanismer.

Hvis man omdефinerer klassen ved hjælp af prototype-metoden, vil den blive formet som følger:

function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA();

Det magiske ved prototype-metoden ligger i fremhævede blå kode linje. Her sættes ClassB's prototype-attribut til en instans af ClassA. Det er meget interessant, fordi man ønsker alle egenskaber og metoder fra ClassA, men ikke ønsker at tilføje dem en efter en til ClassB's prototype-attribut. Er der nogen bedre måde at gøre det på ved at tildelle en instans af ClassA til prototype-attributten?

Bemærk:Kald ClassA's konstruktionsfunktion uden at give den nogen parametre. Dette er standardpraksis i prototype-kæden. Sørg for, at konstruktionsfunktionen ikke har nogen parametre.

Ligesom med objektets forfalskning skal alle egenskaber og metoder for subclasses optræde efter at prototype-attributten er tildelt, fordi alle metoder, der tildeleres før, vil blive slettet. Hvorfor? Fordi prototype-attributten bliver erstattet af et nyt objekt, og det oprindelige objekt, der tilføjede metoderne, vil blive ødelagt. Så, følgende kode tilføjer name-attributten og sayName()-metoden til ClassB-klassen:

function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};

Du kan teste dette kodefragment ved at køre nedenstående eksempel:

var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();

TIY

Derudover fungerer instanceof-operatoren også unormalt i prototype-kæden. For alle instanser af ClassB returnerer instanceof både ClassA og ClassB true. For eksempel:

var objB = new ClassB();
alert(objB instanceof ClassA);	//Udskriver "true"
alert(objB instanceof ClassB);	//Udskriver "true"

I ECMAScript's svage typemiljø er dette en meget nyttig værktøj, men det kan ikke bruges under objektets forfalskning.

Ulempen ved prototype-kæden er, at den ikke understøtter flerarv. Husk, at prototype-kæden vil overskrive klassens prototype-attribut med en anden type objekt.

Blandingsmetode

Denne arvsmetode bruger konstruktionsfunktionen til at definere klassen, ikke nogen prototype. Det vigtigste problem med objektets forfalskning er, at det er nødvendigt at bruge konstruktionsfunktionen, hvilket ikke er det bedste valg. Men hvis du bruger prototype-kæden, kan du ikke bruge konstruktionsfunktioner med parametre. Hvordan vælger udvikleren? Svaret er simpelt, begge dele bruges.

I det forrige kapitel har vi tidligere forklaret, at den bedste måde at oprette klasser på er ved at definere egenskaber med konstruktører og metoder med prototyper. Denne metode gælder også for arvemechanismen, hvor man bruger objekter til at efterligne arv af konstruktørens egenskaber og bruger prototypekæden til at arve metoderne fra prototype-objektet. Ved at bruge disse to metoder kan man omskrive det tidligere eksempel, og koden ser sådan ud:

function ClassA(sColor) {
    this.color = sColor;
}
ClassA.prototype.sayColor = function () {
    alert(this.color);
};
function ClassB(sColor, sName) {
    ClassA.call(this, sColor);
    this.name = sName;
}
ClassB.prototype = new ClassA();
ClassB.prototype.sayName = function () {
    alert(this.name);
};

I dette eksempel implementeres arvemechanismen af de to fremhævede blå linjer. I det første fremhævede linje, i ClassB konstruktøren, bruges objektet til at efterligne arv af ClassA klasseens sColor egenskab. I det andet fremhævede linje bruges prototypekæden til at arve ClassA klasseens metoder. På grund af denne blandede metode, der bruger prototypekæden, fungerer instanceof operator stadig korrekt.

Nedenstående eksempel tester dette kode afsnit:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//Udskriver "blue"
objB.sayColor();	//Udskriver "red"
objB.sayName();	//Udskriver "John"

TIY