¿Cómo heredar de una clase en javascript?

99

En PHP / Java uno puede hacer:

class Sub extends Base
{
}

Y automáticamente todos los métodos, propiedades, campos, etc. públicos / protegidos de la clase Super pasan a formar parte de la clase Sub, que se puede anular si es necesario.

¿Cuál es el equivalente de eso en Javascript?

Haga clic en Upvote
fuente
1
Eche un vistazo aquí: stackoverflow.com/questions/1908443/…
Darin Dimitrov
¿Sigue funcionando esa forma de Crockford? ZParenizor. hereda (Parenizor);
Loren Shqipognja
Ver también: Clase extensible de JavaScript
user56reinstatemonica8

Respuestas:

80

He cambiado la forma en que hago esto ahora, trato de evitar el uso de funciones de constructor y su prototypepropiedad, pero mi respuesta anterior de 2010 todavía está en la parte inferior. Ahora prefiero Object.create(). Object.createestá disponible en todos los navegadores modernos.

Debo señalar que Object.createsuele ser mucho más lento que usarlo newcon un constructor de funciones.

//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

Uno de los grandes beneficios de usar Object.create es poder pasar un argumento defineProperties , lo que le brinda un control significativo sobre cómo se puede acceder y enumerar las propiedades en la clase, y también uso funciones para crear instancias, estas sirven como constructores de alguna manera, ya que puede hacer la inicialización al final en lugar de simplemente devolver la instancia.

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

Esta es mi respuesta original de 2010:

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"
Bjorn
fuente
5
¿Qué tal el valor de sub.prototype.constructor? Creo que también debería establecerse en subvalor.
maximus
Además de que está utilizando palabras clave reservadas ('super') como nombres de clase, no pude ejecutar su ejemplo: jsbin.com/ixiyet/8/edit
MOnsDaR
@MOnsDaR Lo renombré a Base
Bjorn
Si utilizo alert()para ver qué instance.showColor()devoluciones todavía obtengo undefined. jsbin.com/uqalin/1
MOnsDaR
1
@MOnsDaR eso se debe a que registra los registros de la consola, no devuelve nada para mostrar la alerta. ¿Ves una declaración de devolución en showColor?
Bjorn
190

En JavaScript no tiene clases, pero puede obtener herencia y reutilización del comportamiento de muchas maneras:

Herencia pseudo-clásica (mediante la creación de prototipos):

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

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

Debe usarse con el newoperador:

var subInstance = new Sub();

Aplicación de función o "encadenamiento de constructores":

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


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

Este enfoque también debe usarse con el newoperador:

var subInstance = new Sub();

La diferencia con el primer ejemplo es que cuando applyel Superconstructor del thisobjeto dentro Sub, agrega las propiedades asignadas a thison Super, directamente en la nueva instancia, por ejemplo, subInstancecontiene las propiedades member1y member2directamente (subInstance.hasOwnProperty('member1') == true; ).

En el primer ejemplo, esas propiedades se alcanzan a través de la cadena de prototipos , existen en un [[Prototype]]objeto interno .

Herencia parasitaria o constructores de energía:

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

  return obj;
}

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

Este enfoque se basa básicamente en el "aumento de objetos", no es necesario utilizar el newoperador y, como puede ver, la thispalabra clave no está involucrada.

var subInstance = createSub();

ECMAScript 5th Ed. Object.createmétodo:

// 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';

El método anterior es una técnica de herencia prototípica propuesta por Crockford .

Las instancias de objetos heredan de otras instancias de objetos, eso es todo.

Esta técnica puede ser mejor que el simple "aumento de objetos" porque las propiedades heredadas no se copian en todas las instancias de nuevos objetos, ya que el objeto base se establece como el [[Prototype]]del objeto extendido , en el ejemplo anterior subInstancecontiene físicamente solo la member3propiedad.

CMS
fuente
3
no use instancias para herencia - use ES5 Object.create()o una clone()función personalizada (por ejemplo, mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) para heredar directamente del objeto prototipo; vea los comentarios en stackoverflow.com/questions/1404559/… para una explicación
Christoph
Gracias @Christoph, estaba a punto de mencionar el Object.createmétodo :)
CMS
1
Esta no es una herencia adecuada, ya que tendrá miembros de instancia de Super en el prototipo de Sub. Por lo tanto, todas las instancias de Sub compartirán la misma member1variable, lo que no es deseable en absoluto. Por supuesto que pueden reescribirlo, pero eso simplemente no tiene sentido. github.com/dotnetwise/Javascript-FastClass es una mejor solución de azúcar.
Adaptabi
Hola @CMS, ¿podría explicarme, por favor, por qué necesito crear una instancia de la clase principal en el primer ejemplo para configurar la herencia de la subclase? Estoy hablando de esta línea: Sub.prototype = new Super();. ¿Qué pasa si ambas clases nunca se van a utilizar durante la ejecución del script? Parece un problema de rendimiento. ¿Por qué necesito crear una clase principal si la clase secundaria no se usa realmente? ¿Puede explicarme por favor? Aquí está la demostración simple del problema: jsfiddle.net/slavafomin/ZeVL2 ¡Gracias!
Slava Fomin II
En todos los ejemplos, excepto en el último, hay una "clase" para Super y una "clase" para Sub, y luego crea una instancia de Sub. ¿Puede agregar un ejemplo comparable para el ejemplo de Object.create?
Lucas
49

Para aquellos que acceden a esta página en 2019 o después

Con la última versión del estándar ECMAScript (ES6) , puede utilizar la palabra clave class.

Tenga en cuenta que la definición de clase no es regular object; por lo tanto, no hay comas entre los miembros de la clase. Para crear una instancia de una clase, debe utilizar la newpalabra clave. Para heredar de una clase base, use 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'

Para heredar de una clase base, use 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

Desde la clase derivada, puede usar super desde cualquier constructor o método para acceder a su clase base:

  • Para llamar al constructor padre, use super().
  • Para llamar a otro miembro, utilice, por ejemplo super.getName(),.

Hay más en el uso de clases. Si desea profundizar en el tema, le recomiendo “ Clases en ECMAScript 6 ” del Dr. Axel Rauschmayer. *

fuente

Esmerejón
fuente
1
Bajo el capó, classy extendses azúcar de sintaxis (ultra útil) para la cadena de prototipos: stackoverflow.com/a/23877420/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
solo para su información 'instancia.nombre' aquí 'mycar.nombre' devolverá el nombre de la clase. Este es un comportamiento predeterminado de ES6 y ESnext. Aquí para mycar.name devolverá 'Vehículo'
Shiljo Paulson
7

Bueno, en JavaScript no hay "herencia de clases", solo hay "herencia de prototipos". Por lo tanto, no crea una clase "camión" y luego la marca como una subclase de "automóvil". En su lugar, crea un objeto "Jack" y dice que usa "John" como prototipo. Si John sabe cuánto es "4 + 4", entonces Jack también lo sabe.

Le sugiero que lea el artículo de Douglas Crockford sobre la herencia prototípica aquí: http://javascript.crockford.com/prototypal.html También muestra cómo puede hacer que JavaScript tenga una herencia "similar" como en otros lenguajes OO y luego explica que esto en realidad, significa romper javaScript de una manera que no debe usarse.

ingenuos
fuente
Supongamos que el prototipo de Jack es John. Durante el tiempo de ejecución, agregué una propiedad / comportamiento a John. ¿Conseguiré esa propiedad / comportamiento de Jack?
Ram Bavireddi
Seguro que lo harás. Por ejemplo, así es como la gente suele agregar el método "trim ()" a todos los objetos de cadena (no está integrado). Vea un ejemplo aquí: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
ingenuos
6

Encuentro que esta cita es la más esclarecedora:

En esencia, una "clase" de JavaScript es simplemente un objeto Function que sirve como constructor más un objeto prototipo adjunto. ( Fuente: Guru Katz )

Me gusta usar constructores en lugar de objetos, por lo que soy parcial al método de "herencia pseudoclásica" descrito aquí por CMS . A continuación, se muestra un ejemplo de herencia múltiple con una cadena de prototipos :

// 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

Aquí hay otro buen recurso de MDN , y aquí hay un jsfiddle para que pueda probarlo .

Luke
fuente
4

La herencia de Javascript es un poco diferente de Java y PHP, porque realmente no tiene clases. En su lugar, tiene objetos prototipo que proporcionan métodos y variables miembro. Puede encadenar esos prototipos para proporcionar herencia de objetos. El patrón más común que encontré al investigar esta pregunta se describe en Mozilla Developer Network . Actualicé su ejemplo para incluir una llamada a un método de superclase y mostrar el registro en un mensaje de alerta:

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

Personalmente, encuentro la herencia en Javascript incómoda, pero esta es la mejor versión que he encontrado.

Don Kirkby
fuente
3

no puedes (en el sentido clásico). Javascript es un lenguaje prototípico. Observará que nunca declara una "clase" en Javascript; simplemente define el estado y los métodos de un objeto. Para producir herencia, se toma un objeto y se crea un prototipo. El prototipo se amplía con nueva funcionalidad.

corriente continua
fuente
1

Puede utilizar .inheritWithy .fastClass biblioteca . Es más rápido que las bibliotecas más populares y, a veces, incluso más rápido que la versión nativa.

Muy fácil de usar:

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

Uso

var s = new Sub();
s.method1(); //prints:
//sub 
//super
Adaptabi
fuente
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
nnattawat
fuente
1

Después de leer muchas publicaciones, se me ocurrió esta solución ( jsfiddle aquí ). La mayoría de las veces no necesito algo más sofisticado.

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();
Chauwel
fuente
1

Gracias a la respuesta de CMS y después de jugar un rato con prototype y Object.create y lo que no, pude encontrar una solución ordenada para mi herencia usando apply como se muestra aquí:

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";
        }
    };
})();
pasx
fuente
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();
Kai Hartmann
fuente
2
No se limite a publicar el código, explique qué hace y cómo responde a la pregunta.
Patrick Hund
1

Clases de ES6:

Javascript no tiene clases. Las clases en javascript son simplemente azúcar sintáctico construido sobre el patrón de herencia prototípico que javascript. Puede usar JS classpara hacer cumplir la herencia de prototipos, pero es importante darse cuenta de que en realidad todavía está usando funciones de constructor bajo el capó.

Estos conceptos también se aplican cuando se extiende desde una es6'clase' utilizando la palabra clave extiende. Esto solo crea un eslabón adicional en la cadena del prototipo. los__proto__

Ejemplo:

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()también es una forma de crear herencia en JS en javascript. Object.create()es una función que crea un nuevo objeto, y toma un objeto existente como argumento. Asignará el objeto que se recibió como argumento al__proto__ propiedad del objeto recién creado. Una vez más, es importante darse cuenta de que estamos sujetos al paradigma de herencia prototípico que encarna JS.

Ejemplo:

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

const dog = Object.create(Dog);

dog.bark();

Willem van der Veen
fuente
0

No puede heredar de una clase en JavaScript, porque no hay clases en JavaScript.

Jörg W Mittag
fuente