Jak dziedziczyć z klasy w javascript?


99

W PHP / Java można:

class Sub extends Base
{
}

I automatycznie wszystkie publiczne / chronione metody, właściwości, pola itp. Klasy Super stają się częścią klasy Sub, którą można w razie potrzeby przesłonić.

Jaki jest odpowiednik tego w Javascript?





Czy ten sposób Crockforda nadal działa? ZParenizor.inherits (Parenizor);
Loren Shqipognja

Odpowiedzi:


80

Zmieniłem sposób, w jaki teraz to robię, staram się unikać używania funkcji konstruktorów i ich prototypewłaściwości, ale moja stara odpowiedź z 2010 roku wciąż jest na dole. Teraz wolę Object.create(). Object.createjest dostępny we wszystkich nowoczesnych przeglądarkach.

Powinienem zauważyć, że Object.createjest to zwykle znacznie wolniejsze niż użycie newz konstruktorem funkcji.

//The prototype is just an object when you use `Object.create()`
var Base = {};

//This is how you create an instance:
var baseInstance = Object.create(Base);

//If you want to inherit from "Base":
var subInstance = Object.create(Object.create(Base));

//Detect if subInstance is an instance of Base:
console.log(Base.isPrototypeOf(subInstance)); //True

jsfiddle

Jedną z największych zalet korzystania z Object.create jest możliwość przekazania argumentu defineProperties , który zapewnia znaczną kontrolę nad tym, jak można uzyskać dostęp do właściwości w klasie i nad nimi wyliczać, a także używam funkcji do tworzenia instancji, służą one jako konstruktorów w taki sposób, jak można wykonać inicjalizację na końcu zamiast po prostu zwrócić instancję.

var Base = {};

function createBase() {
  return Object.create(Base, {
    doSomething: {
       value: function () {
         console.log("Doing something");
       },
    },
  });
}

var Sub = createBase();

function createSub() {
  return Object.create(Sub, {
    doSomethingElse: {
      value: function () {
        console.log("Doing something else");
      },
    },
  }); 
}

var subInstance = createSub();
subInstance.doSomething(); //Logs "Doing something"
subInstance.doSomethingElse(); //Logs "Doing something else"
console.log(Base.isPrototypeOf(subInstance)); //Logs "true"
console.log(Sub.isPrototypeOf(subInstance)); //Logs "true

jsfiddle

Oto moja oryginalna odpowiedź z 2010 roku:

function Base ( ) {
  this.color = "blue";
}

function Sub ( ) {

}
Sub.prototype = new Base( );
Sub.prototype.showColor = function ( ) {
 console.log( this.color );
}

var instance = new Sub ( );
instance.showColor( ); //"blue"

5
A co z wartością sub.prototype.constructor? Myślę, że to też powinno być ustawione na wartość podrzędną.
maximus

Poza tym, że używasz zarezerwowanych słów kluczowych („super”) jako nazw
klas

@MOnsDaR Zmieniłem nazwę na Base
Bjorn

Jeśli użyję, alert()aby zobaczyć, jakie instance.showColor()zwroty nadal otrzymuję undefined. jsbin.com/uqalin/1
MOnsDaR

1
@MOnsDaR to dlatego, że rejestruje konsolę, nie zwraca niczego, co mogłoby pokazać alert. Czy widzisz instrukcję return w showColor?
Bjorn,

190

W JavaScript nie masz klas, ale możesz uzyskać dziedziczenie i ponowne wykorzystanie zachowań na wiele sposobów:

Dziedziczenie pseudoklasyczne (poprzez prototypowanie):

function Super () {
  this.member1 = 'superMember1';
}
Super.prototype.member2 = 'superMember2';

function Sub() {
  this.member3 = 'subMember3';
  //...
}
Sub.prototype = new Super();

Powinien być używany z newoperatorem:

var subInstance = new Sub();

Aplikacja funkcji lub „łańcuch konstruktora”:

function Super () {
  this.member1 = 'superMember1';
  this.member2 = 'superMember2';
}


function Sub() {
  Super.apply(this, arguments);
  this.member3 = 'subMember3';
}

Takie podejście należy również zastosować w przypadku newoperatora:

var subInstance = new Sub();

Różnica w porównaniu z pierwszym przykładem jest to, że kiedy konstruktor do wnętrza obiektu , dodaje właściwości przypisane na bezpośrednio na nową instancję np zawiera właściwości i bezpośrednio ( ).applySuperthisSubthisSupersubInstancemember1member2subInstance.hasOwnProperty('member1') == true;

W pierwszym przykładzie te właściwości są osiągane przez łańcuch prototypów , istnieją w [[Prototype]]obiekcie wewnętrznym .

Dziedziczenie pasożytnicze lub konstruktorzy mocy:

function createSuper() {
  var obj = {
    member1: 'superMember1',
    member2: 'superMember2'
  };

  return obj;
}

function createSub() {
  var obj = createSuper();
  obj.member3 = 'subMember3';
  return obj;
}

To podejście opiera się w zasadzie na „rozszerzaniu obiektów”, nie musisz używać newoperatora i jak widać, thissłowo kluczowe nie jest używane.

var subInstance = createSub();

ECMAScript 5th Ed. Object.createmetoda:

// Check if native implementation available
if (typeof Object.create !== 'function') {
  Object.create = function (o) {
    function F() {}  // empty constructor
    F.prototype = o; // set base object as prototype
    return new F();  // return empty object with right [[Prototype]]
  };
}

var superInstance = {
  member1: 'superMember1',
  member2: 'superMember2'
};

var subInstance = Object.create(superInstance);
subInstance.member3 = 'subMember3';

Powyższa metoda jest prototypową techniką dziedziczenia zaproponowaną przez Crockforda .

Instancje obiektów dziedziczą po innych instancjach obiektów, to wszystko.

Technika ta może być lepiej niż proste „obiekt powiększania”, ponieważ odziedziczone właściwości nie są kopiowane wszystkich nowych instancji obiektów, ponieważ baza obiekt jest ustawiony jak [[Prototype]]z rozszerzonego przedmiotu, w powyższym przykładzie subInstancezawiera fizycznie tylko member3nieruchomości.


3
nie używaj instancji do dziedziczenia - użyj ES5 Object.create()lub clone()funkcji niestandardowej (np. mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ), aby dziedziczyć bezpośrednio z obiektu prototypowego; zobacz komentarze do stackoverflow.com/questions/1404559/ ... po wyjaśnienie
Christoph

Dzięki @Christoph, miałem już wspomnieć o Object.createmetodzie :)
CMS

1
Nie jest to właściwe dziedziczenie, ponieważ członków instancji Super będziesz mieć w prototypie Sub. Dlatego wszystkie instancje Sub będą miały tę samą member1zmienną, co w ogóle nie jest pożądane. Oczywiście mogą to przepisać, ale to po prostu nie ma sensu. github.com/dotnetwise/Javascript-FastClass to lepsze rozwiązanie dla cukru.
Adaptabi

Witaj @CMS, czy możesz wyjaśnić, dlaczego muszę utworzyć wystąpienie klasy nadrzędnej w pierwszym przykładzie, aby skonfigurować dziedziczenie dla podklasy? Mówię o tej linii: Sub.prototype = new Super();. A jeśli obie klasy nigdy nie będą używane podczas wykonywania skryptu? Wygląda na problem z wydajnością. Dlaczego muszę tworzyć klasę nadrzędną, jeśli klasa podrzędna nie jest faktycznie używana? Czy możesz to rozwinąć? Oto prosta demonstracja problemu: jsfiddle.net/slavafomin/ZeVL2 Dzięki!
Slava Fomin II

We wszystkich przykładach - z wyjątkiem ostatniego - istnieje „klasa” dla Super i „klasa” dla Sub, a następnie tworzysz instancję Sub. Czy możesz dodać podobny przykład do przykładu Object.create?
Łukasz

49

Dla tych, którzy dotrą na tę stronę w 2019 roku lub później

W najnowszej wersji standardu ECMAScript (ES6) można użyć słowa kluczowego class.

Zauważ, że definicja klasy nie jest regularna object; dlatego nie ma przecinków między członkami klasy. Aby utworzyć instancję klasy, musisz użyć newsłowa kluczowego. Aby dziedziczyć z klasy bazowej, użyj extends:

class Vehicle {
   constructor(name) {
      this.name = name;
      this.kind = 'vehicle';
   }
   getName() {
      return this.name;
   }   
}

// Create an instance
var myVehicle = new Vehicle('rocky');
myVehicle.getName(); // => 'rocky'

Aby dziedziczyć z klasy bazowej, użyj extends:

class Car extends Vehicle {
   constructor(name) {
      super(name);
      this.kind = 'car'
   }
}

var myCar = new Car('bumpy');

myCar.getName(); // => 'bumpy'
myCar instanceof Car; // => true
myCar instanceof Vehicle; // => true

Z klasy pochodnej możesz użyć super z dowolnego konstruktora lub metody, aby uzyskać dostęp do jej klasy bazowej:

  • Aby wywołać konstruktora nadrzędnego, użyj super().
  • Aby zadzwonić do innego członka, użyj na przykład super.getName().

Korzystanie z klas to nie wszystko. Jeśli chcesz zagłębić się w temat, polecam „ Zajęcia w ECMAScript 6 ” dr Axela Rauschmayera. *

źródło


1
Pod maską classi extendsjest (bardzo przydatnym) cukrem składniowym dla łańcucha prototypów: stackoverflow.com/a/23877420/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

tylko dla twojej informacji „instance.name” tutaj „mycar.name” zwróci nazwę klasy. Jest to domyślne zachowanie ES6 i ESnext. Tutaj dla mycar.name zwróci „Vehicle”
Shiljo Paulson

7

Cóż, w JavaScript nie ma „dziedziczenia klas”, jest tylko „dziedziczenie prototypów”. Więc nie tworzysz klasy „ciężarówki”, a następnie oznaczasz ją jako podklasę „samochód”. Zamiast tego tworzysz obiekt „Jack” i mówisz, że używa on „John” jako prototypu. Jeśli John wie, ile wynosi „4 + 4”, to Jack też to wie.

Sugeruję przeczytanie artykułu Douglasa Crockforda o dziedziczeniu prototypowym tutaj: http://javascript.crockford.com/prototypal.html . Pokazuje on również, jak sprawić, by JavaScript miał dziedziczenie „podobne”, jak w innych językach OO, a następnie wyjaśnia, że ​​to w rzeczywistości oznacza złamanie javaScript w sposób, w jaki nie był przeznaczony.


Załóżmy, że prototyp Jacka to John. W czasie wykonywania dodałem właściwość / zachowanie do Johna. Czy dostanę tę własność / zachowanie od Jacka?
Ram Bavireddi

Na pewno. Na przykład w ten sposób ludzie zazwyczaj dodają metodę „trim ()” do wszystkich obiektów typu string (nie jest ona wbudowana). Zobacz przykład tutaj: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
naiwniści

6

Uważam, że ten cytat jest najbardziej pouczający:

W istocie „klasa” JavaScript to po prostu obiekt Function, który służy jako konstruktor oraz dołączony obiekt prototypowy. ( Źródło: Guru Katz )

Lubię używać konstruktorów, a nie obiektów, więc jestem zwolennikiem metody "pseudo-klasycznego dziedziczenia" opisanej tutaj przez CMS . Oto przykład wielokrotnego dziedziczenia z łańcuchem prototypów :

// Lifeform "Class" (Constructor function, No prototype)
function Lifeform () {
    this.isLifeform = true;
}

// Animal "Class" (Constructor function + prototype for inheritance)
function Animal () {
    this.isAnimal = true;
}
Animal.prototype = new Lifeform();

// Mammal "Class" (Constructor function + prototype for inheritance)
function Mammal () {
    this.isMammal = true;
}
Mammal.prototype = new Animal();

// Cat "Class" (Constructor function + prototype for inheritance)
function Cat (species) {
    this.isCat = true;
    this.species = species
}
Cat.prototype = new Mammal();

// Make an instance object of the Cat "Class"
var tiger = new Cat("tiger");

console.log(tiger);
// The console outputs a Cat object with all the properties from all "classes"

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform);
// Outputs: true true true true

// You can see that all of these "is" properties are available in this object
// We can check to see which properties are really part of the instance object
console.log( "tiger hasOwnProperty: "
    ,tiger.hasOwnProperty("isLifeform") // false
    ,tiger.hasOwnProperty("isAnimal")   // false
    ,tiger.hasOwnProperty("isMammal")   // false
    ,tiger.hasOwnProperty("isCat")      // true
);

// New properties can be added to the prototypes of any
// of the "classes" above and they will be usable by the instance
Lifeform.prototype.A    = 1;
Animal.prototype.B      = 2;
Mammal.prototype.C      = 3;
Cat.prototype.D         = 4;

console.log(tiger.A, tiger.B, tiger.C, tiger.D);
// Console outputs: 1 2 3 4

// Look at the instance object again
console.log(tiger);
// You'll see it now has the "D" property
// The others are accessible but not visible (console issue?)
// In the Chrome console you should be able to drill down the __proto__ chain
// You can also look down the proto chain with Object.getPrototypeOf
// (Equivalent to tiger.__proto__)
console.log( Object.getPrototypeOf(tiger) );  // Mammal 
console.log( Object.getPrototypeOf(Object.getPrototypeOf(tiger)) ); // Animal
// Etc. to get to Lifeform

Oto kolejny dobry zasób z MDN , a tutaj jest jsfiddle, więc możesz go wypróbować .


4

Dziedziczenie JavaScript różni się nieco od Javy i PHP, ponieważ tak naprawdę nie ma klas. Zamiast tego ma obiekty prototypowe, które udostępniają metody i zmienne składowe. Możesz łączyć te prototypy, aby zapewnić dziedziczenie obiektów. Najpopularniejszy wzorzec, jaki znalazłem podczas badania tego pytania, jest opisany w sieci Mozilla Developer Network . Zaktualizowałem ich przykład, aby zawierał wywołanie metody nadklasy i pokazywał dziennik w komunikacie ostrzegawczym:

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  log += 'Shape moved.\n';
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// Override method
Rectangle.prototype.move = function(x, y) {
  Shape.prototype.move.call(this, x, y); // call superclass method
  log += 'Rectangle moved.\n';
}

var log = "";
var rect = new Rectangle();

log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
alert(log);

Osobiście uważam, że dziedziczenie w Javascript jest niezręczne, ale to najlepsza wersja, jaką znalazłem.


3

nie możesz (w klasycznym sensie). Javascript jest językiem prototypowym. Zauważysz, że nigdy nie deklarujesz „klasy” w Javascript; definiujesz jedynie stan i metody obiektu. Aby stworzyć dziedziczenie, bierzesz jakiś obiekt i prototypujesz go. Prototyp zostaje rozbudowany o nową funkcjonalność.


1

Możesz użyć .inheritWithi .fastClass biblioteki . Jest szybszy niż większość popularnych bibliotek, a czasem nawet szybszy niż wersja natywna.

Bardzo łatwy w użyciu:

function Super() {
   this.member1 = "superMember";//instance member
}.define({ //define methods on Super's prototype
   method1: function() { console.log('super'); } //prototype member
}.defineStatic({ //define static methods directly on Super function 
   staticMethod1: function() { console.log('static method on Super'); }
});

var Sub = Super.inheritWith(function(base, baseCtor) {
   return {
      constructor: function() {//the Sub constructor that will be returned to variable Sub
         this.member3 = 'subMember3'; //instance member on Sub
         baseCtor.apply(this, arguments);//call base construcor and passing all incoming arguments
      },
      method1: function() { 
         console.log('sub'); 
         base.method1.apply(this, arguments); //call the base class' method1 function
      }
}

Stosowanie

var s = new Sub();
s.method1(); //prints:
//sub 
//super

1
function Person(attr){
  this.name = (attr && attr.name)? attr.name : undefined;
  this.birthYear = (attr && attr.birthYear)? attr.birthYear : undefined;

  this.printName = function(){
    console.log(this.name);
  }
  this.printBirthYear = function(){
    console.log(this.birthYear);
  }
  this.print = function(){
    console.log(this.name + '(' +this.birthYear+ ')');
  }
}

function PersonExt(attr){
  Person.call(this, attr);

  this.print = function(){
    console.log(this.name+ '-' + this.birthYear);
  }
  this.newPrint = function(){
    console.log('New method');
  }
}
PersonExt.prototype = new Person();

// Init object and call methods
var p = new Person({name: 'Mr. A', birthYear: 2007});
// Parent method
p.print() // Mr. A(2007)
p.printName() // Mr. A

var pExt = new PersonExt({name: 'Mr. A', birthYear: 2007});
// Overwriten method
pExt.print() // Mr. A-2007
// Extended method
pExt.newPrint() // New method
// Parent method
pExt.printName() // Mr. A

1

Po przeczytaniu wielu postów wymyśliłem to rozwiązanie ( tutaj jsfiddle ). Przez większość czasu nie potrzebuję czegoś bardziej wyrafinowanego

var Class = function(definition) {
    var base = definition.extend || null;
    var construct = definition.construct || definition.extend || function() {};

    var newClass = function() { 
        this._base_ = base;        
        construct.apply(this, arguments);
    }

    if (definition.name) 
        newClass._name_ = definition.name;

    if (definition.extend) {
        var f = function() {}       
        f.prototype = definition.extend.prototype;      
        newClass.prototype = new f();   
        newClass.prototype.constructor = newClass;
        newClass._extend_ = definition.extend;      
        newClass._base_ = definition.extend.prototype;         
    }

    if (definition.statics) 
        for (var n in definition.statics) newClass[n] = definition.statics[n];          

    if (definition.members) 
        for (var n in definition.members) newClass.prototype[n] = definition.members[n];    

    return newClass;
}


var Animal = Class({

    construct: function() {        
    },

    members: {

        speak: function() {
            console.log("nuf said");                        
        },

        isA: function() {        
            return "animal";           
        }        
    }
});


var Dog = Class({  extend: Animal,

    construct: function(name) {  
        this._base_();        
        this.name = name;
    },

    statics: {
        Home: "House",
        Food: "Meat",
        Speak: "Barks"
    },

    members: {
        name: "",

        speak: function() {
            console.log( "ouaf !");         
        },

        isA: function(advice) {
           return advice + " dog -> " + Dog._base_.isA.call(this);           
        }        
    }
});


var Yorkshire = Class({ extend: Dog,

    construct: function(name,gender) {
        this._base_(name);      
        this.gender = gender;
    },

    members: {
        speak: function() {
            console.log( "ouin !");           
        },

        isA: function(advice) {         
           return "yorkshire -> " + Yorkshire._base_.isA.call(this,advice);       
        }        
    }
});


var Bulldog = function() { return _class_ = Class({ extend: Dog,

    construct: function(name) {
        this._base_(name);      
    },

    members: {
        speak: function() {
            console.log( "OUAF !");           
        },

        isA: function(advice) {         
           return "bulldog -> " + _class_._base_.isA.call(this,advice);       
        }        
    }
})}();


var animal = new Animal("Maciste");
console.log(animal.isA());
animal.speak();

var dog = new Dog("Sultan");
console.log(dog.isA("good"));
dog.speak();

var yorkshire = new Yorkshire("Golgoth","Male");
console.log(yorkshire.isA("bad"));
yorkshire.speak();

var bulldog = new Bulldog("Mike");
console.log(bulldog.isA("nice"));
bulldog.speak();

1

Dzięki odpowiedzi CMS i po pewnym czasie bawiąc się prototypem i Object.create, a co nie, udało mi się wymyślić zgrabne rozwiązanie dla mojego dziedziczenia za pomocą aplikacji, jak pokazano tutaj:

var myNamespace = myNamespace || (function() {
    return {

        BaseClass: function(){
            this.someBaseProperty = "someBaseProperty";
            this.someProperty = "BaseClass";
            this.someFunc = null;
        },

        DerivedClass:function(someFunc){
            myNamespace.BaseClass.apply(this, arguments);
            this.someFunc = someFunc;
            this.someProperty = "DerivedClass";
        },

        MoreDerivedClass:function(someFunc){
            myNamespace.DerivedClass.apply(this, arguments);
            this.someFunc = someFunc;
            this.someProperty = "MoreDerivedClass";
        }
    };
})();


1
function Base() {
    this.doSomething = function () {
    }
}

function Sub() {
    Base.call(this); // inherit Base's method(s) to this instance of Sub
}

var sub = new Sub();
sub.doSomething();

2
Nie wysyłaj tylko kodu pocztowego, ale wyjaśnij, co robi i jak odpowiada na pytanie.
Patrick Hund

1

Klasy ES6:

Javascript nie ma klas. Klasy w javascript są po prostu cukrem składniowym opartym na prototypowego wzorca dziedziczenia , który javascript. Możesz użyć JS classdo wymuszenia dziedziczenia prototypów, ale ważne jest, aby zdać sobie sprawę, że nadal używasz funkcji konstruktora pod maską.

Pojęcia te mają również zastosowanie, gdy rozszerzasz z es6klasy za pomocą słowa kluczowego extends. To po prostu tworzy dodatkowe łącze w łańcuchu prototypów. Plik__proto__

Przykład:

class Animal {
  makeSound () {
    console.log('animalSound');
  }
}

class Dog extends Animal {
   makeSound () {
    console.log('Woof');
  }
}


console.log(typeof Dog)  // classes in JS are just constructor functions under the hood

const dog = new Dog();

console.log(dog.__proto__ === Dog.prototype);   
// First link in the prototype chain is Dog.prototype

console.log(dog.__proto__.__proto__ === Animal.prototype);  
// Second link in the prototype chain is Animal.prototype
// The extends keyword places Animal in the prototype chain
// Now Dog 'inherits' the makeSound property from Animal

Object.create ()

Object.create()to także sposób na tworzenie dziedziczenia w JS w javascript. Object.create()jest funkcją, która tworzy nowy obiekt, jako argument przyjmuje istniejący obiekt. Przydzieli obiekt, który został odebrany jako argument, do__proto__ właściwości nowo utworzonego obiektu. Ponownie ważne jest, aby zdać sobie sprawę, że jesteśmy związani prototypowym paradygmatem dziedziczenia, który ucieleśnia JS.

Przykład:

const Dog = {
  fluffy: true,
  bark: () => {
      console.log('woof im a relatively cute dog or something else??');
  }
};

const dog = Object.create(Dog);

dog.bark();


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.