Implementation of Inheritance Mechanism in ECMAScript

Implementacja mechanizmu dziedziczenia

Aby zaimplementować mechanizm dziedziczenia w ECMAScript, możesz zacząć od klasy bazowej, którą chcesz dziedziczyć. Wszystkie klasy zdefiniowane przez deweloperów mogą być klasami bazowymi. Ze względów bezpieczeństwa, lokalne klasy i klasy hosta nie mogą być klasami bazowymi, aby zapobiec publicznemu dostępowi do skompilowanego kodu poziomu przeglądarki, który może być użyty do ataków złośliwych.

Po wybraniu klasy bazowej, możesz stworzyć jej podklasę. Użycie klasy bazowej zależy całkowicie od ciebie. Czasami możesz chcieć stworzyć klasę bazową, która nie może być bezpośrednio używana, ale służy tylko do dostarczania ogólnych funkcji dla podklas. W takim przypadku klasa bazowa jest traktowana jako klasa abstrakcyjna.

Chociaż ECMAScript nie definiuje tak ściśle klas abstrakcyjnych jak inne języki, czasami rzeczywiście tworzy klasy, które nie są dozwolone do użycia. Zwykle takie klasy nazywamy klasami abstrakcyjnymi.

Tworzone podklasy dziedziczą wszystkie atrybuty i metody superklasy, w tym implementację konstruktora i metod. Pamiętaj, że wszystkie atrybuty i metody są publiczne, więc podklasy mogą bezpośrednio dostępuwać do tych metod. Podklasy mogą również dodawać nowe atrybuty i metody, które nie istnieją w superklasie, lub nadpisywać atrybuty i metody superklasy.

Sposoby dziedziczenia

Jak i inne funkcje, ECMAScript realizuje dziedziczenie na wiele sposobów. To dlatego, że mechanizm dziedziczenia w JavaScript nie jest ściśle określony, ale jest realizowany poprzez naśladowanie. Oznacza to, że wszystkie szczegóły dziedziczenia nie są w pełni przetwarzane przez interpreter. Jako deweloper masz prawo decydować o najbardziej odpowiednim sposobie dziedziczenia.

Poniżej przedstawiam kilka konkretnych sposobów dziedziczenia.

Maskowanie obiektów

Podczas projektowania oryginalnego ECMAScript nie miało się na myśli projektowania maskowania obiektów (object masquerading). Rozwinęło się to dopiero, gdy deweloperzy zaczęli rozumieć działanie funkcji, zwłaszcza sposób użycia słowa kluczowego this w środowisku funkcji.

Jego zasada działania jest następująca: konstruktor używa słowa kluczowego this do przypisywania wartości wszystkim atrybutom i metodom (tj. w sposób, w jaki jest używany deklarowany konstruktor klasy). Ponieważ konstruktor jest po prostu funkcją, można go użyć jako metody ClassA dla ClassB, a następnie wywołać ją. ClassB otrzyma atrybuty i metody zdefiniowane w konstruktorze ClassA. Na przykład, można zdefiniować ClassA i ClassB w następujący sposób:

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

Pamiętasz? Słowo kluczowe this odnosi się do obiektu, który jest aktualnie tworzony przez konstruktora. Jednak w tym metodzie, this odnosi się do obiektu, do którego należy. Zasada ta polega na tym, że ClassA jest używana jako zwykła funkcja do tworzenia mechanizmu dziedziczenia, zamiast jako konstruktora. Można osiągnąć mechanizm dziedziczenia, używając konstruktora ClassB w następujący sposób:

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

W tym kodzie, metoda newMethod klasy ClassA przypisana jest do ClassB (pamiętaj, że nazwa funkcji to jedynie wskaźnik do niej). Następnie wywoływana jest ta metoda, przekazując jej parametr sColor z konstruktora ClassB. Ostatni wiersz kodu usuwa odwołanie do ClassA, więc w przyszłości nie można już jej wywoływać.

Wszystkie nowe atrybuty i metody muszą być zdefiniowane po usunięciu kodu nowej metody. W przeciwnym razie, może się to skończyć nadpisywaniem odpowiednich atrybutów i metod nadklasy:

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

Aby udowodnić, że poprzedni kod działa poprawnie, można uruchomić poniższy przykład:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//Wyjście "blue"
objB.sayColor();	//Wyjście "red"
objB.sayName();		//wyświetla "John"

Maskowanie obiektów może realizować wielodziedziczenie

Ciekawe jest to, że maskowanie obiektów może wspierać wielodziedziczenie. Oznacza to, że klasa może dziedziczyć po wielu nadklasach. Mechanizm wielodziedziczenia przedstawiony za pomocą UML pokazany jest na rysunku poniżej:

Inheritance Mechanism UML Diagram Example

Na przykład, jeśli istnieją dwie klasy ClassX i ClassY, a klasa ClassZ chce dziedziczyć po nich, można użyć poniższego kodu:

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

TIY

Istnieje pewna wada, jeśli istnieją dwie klasy ClassX i ClassY o同名属性 lub metodach, ClassY ma wyższy priorytet, ponieważ dziedziczy z klas z tyłu. Poza tym,多重继承机制可以通过对象冒充轻松实现.

Z powodu popularności tej metody, trzecia wersja ECMAScript dodała dwa metody do obiektu Function, tj. call() i apply().

call() metoda

call() metoda jest najbardziej podobna do klasycznego metody pseudonastępczości. Pierwszy parametr jest używany jako obiekt this. Pozostałe parametry są bezpośrednio przekazywane do funkcji. Na przykład:

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.");

W tym przykładzie, funkcja sayColor() jest zdefiniowana poza obiektem, nawet jeśli nie należy do żadnego obiektu, można odnosić się do kluczowego słowa this. Atrybut color obiektu obj wynosi blue. Podczas wywoływania metody call(), pierwszym parametrem jest obj, co oznacza, że wartość kluczowego słowa this w funkcji sayColor() powinna być obj. Drugi i trzeci parametry to ciągi znaków. Pasują one do parametrów sPrefix i sSuffix w funkcji sayColor(). Ostateczna wiadomość "The color is blue, a very nice color indeed." zostanie wyświetlona.

Aby użyć tej metody z metodami pseudonastępczości, wystarczy zastąpić pierwsze trzy wiersze przypisania, wywołania i usunięcia kodu:

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

W tym przypadku, musimy ustawić kluczowe słowo this w klasie ClassA na nowo utworzony obiekt ClassB, więc this jest pierwszym parametrem. Drugi parametr sColor jest unikalny dla obu klas.

apply() metoda

apply() metoda ma dwa parametry, które są używane jako obiekt this i tablica argumentów do przekazania funkcji. Na przykład:

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."));

Ten przykład jest podobny do poprzedniego, tylko teraz wywoływana jest metoda apply(). Przy wywoływaniu metody apply(), pierwszym parametrem jest nadal obj, co oznacza, że wartością kluczowego słowa this w funkcji sayColor() powinna być obj. Drugim parametrem jest tablica z dwoma ciągami znaków, która pasuje do parametrów sPrefix i sSuffix w funkcji sayColor(), a utworzone wiadomość "The color is blue, a very nice color indeed." zostanie wyświetlona.

Ta metoda jest również używana do zastąpienia trzech linii przypisania, wywołania i usunięcia nowej metody:

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);
    };
}

Tak samo, pierwszym parametrem jest nadal this, drugim parametrem jest tablica z jednym wartością color. Cały obiekt arguments klasy ClassB można przekazać jako drugi parametr do metody apply():

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

Oczywiście, parametry obiektu mogą być przekazywane tylko wtedy, gdy kolejność parametrów w klasie nadrzędnej jest identyczna z kolejnością parametrów w klasie podrzędnej. W przeciwnym razie, musi się utworzyć osobny tablicę, umieszczając parametry w odpowiedniej kolejności. Ponadto, można również użyć metody call().

Prototyp chain (prototype chaining)

Tego typu dziedziczenie w ECMAScript jest pierwotnie przeznaczone do prototyp chain. W poprzednim rozdziale omówiliśmy sposób definiowania prototypu klasy. Prototyp chain rozszerza ten sposób, realizując w interesujący sposób mechanizm dziedziczenia.

Jak nauczyliśmy się w poprzednim rozdziale, obiekt prototype jest szablonem, na którym opierają się wszystkie instancje do stworzenia. W skrócie, każda właściwość i metoda obiektu prototype jest przekazywana do wszystkich instancji tego klasy. Wzorzec prototypu wykorzystuje tę funkcję do realizacji mechanizmu dziedziczenia.

Jeśli użyć prototypowego podejścia do ponownego zdefiniowania klasy z poprzedniego przykładu, będą one miały następującą formę:

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

Zwyczajne miejsce原型ways polega na podkreślonym niebieskim kodzie. Tutaj, ustawienie właściwości prototype ClassB na instancję ClassA. To jest interesujące, ponieważ chcemy wszystkich właściwości i metod ClassA, ale nie chcemy je ręcznie przypisywać do właściwości prototype ClassB. Czy jest lepszy sposób, aby to zrobić, niż przypisać instancję ClassA do właściwości prototype?

Uwaga:Wywołanie konstruktora ClassA bez przekazywania mu parametrów. To jest standardowa praktyka w原型链. Upewnij się, że konstruktor nie ma żadnych parametrów.

Podobnie jak w podrobieniu obiektu, wszystkie właściwości i metody podrzędnej klasy muszą być umieszczone po przypisaniu wartości do właściwości prototype, ponieważ wszystkie metody przypisane przed tym zostaną usunięte. Dlaczego? Ponieważ właściwość prototype zostanie zastąpiona nowym obiektem, oryginalny obiekt z dodanymi metodami zostanie zniszczony. Dlatego kod dodający właściwość name i metodę sayName() dla klasy ClassB wygląda tak:

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

Możesz przetestować ten kod, uruchamiając poniższy przykład:

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

TIY

Dodatkowo, w原型链, sposób działania operatora instanceof jest również unikalny. Dla wszystkich instancji ClassB, instanceof zwraca true zarówno dla ClassA, jak i ClassB. Na przykład:

var objB = new ClassB();
alert(objB instanceof ClassA); // Wyjście "true"
alert(objB instanceof ClassB); // Wyjście "true"

W świecie słabo typowym ECMAScript to niezwykle użyteczny narzędzie, ale nie można go używać przy podrobieniu obiektu.

Wady原型链 to brak wsparcia dla wielokrotnego dziedziczenia. Pamiętaj, że原型链 używa innego typu obiektu do przepisania właściwości prototype klasy.

Mieszany sposób

Ten sposób dziedziczenia używa konstruktora do definiowania klas, a nie żadnych prototypów. Głównym problemem z podrobieniem obiektu jest konieczność użycia konstruktora, co nie jest najlepszym wyborem. Jednak jeśli używasz原型链, nie możesz używać konstruktora z parametrami. Jak deweloperzy mogą wybrać? Odpowiedź jest prosta, używaj obu.

W poprzednim rozdziale omówiliśmy najlepszy sposób tworzenia klas, używając funkcji konstruktora do definiowania atrybutów, a prototypu do definiowania metod. Ta metoda jest również odpowiednia dla mechanizmu dziedziczenia, używając obiektu do naśladowania dziedziczenia atrybutów konstruktora, a prototypu do dziedziczenia metod obiektu prototype. Przepisując poprzedni przykład za pomocą tych dwóch sposobów, kod wygląda następująco:

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);
};

W tym przykładzie mechanizm dziedziczenia jest realizowany przez dwa wyróżnione niebieskie kodu. W pierwszym wyróżnionym kodzie, w konstruktorze ClassB, używa się obiektu do naśladowania dziedziczenia atrybutu sColor klasy ClassA. W drugim wyróżnionym kodzie, używa się prototypu do dziedziczenia metod klasy ClassA. Ponieważ ten sposób mieszania używa prototypu, operator instanceof nadal działa poprawnie.

Poniższy przykład testuje ten kod:

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

TIY