Como herdar de uma classe em javascript?

99

Em PHP / Java, pode-se fazer:

class Sub extends Base
{
}

E automaticamente todos os métodos públicos / protegidos, propriedades, campos, etc. da classe Super tornam-se parte da classe Sub, que pode ser substituída se necessário.

Qual é o equivalente para isso em Javascript?

Clique em votar positivamente
fonte
1
Dê uma olhada aqui: stackoverflow.com/questions/1908443/…
Darin Dimitrov
Esse jeito crockford ainda funciona? ZParenizor.inherits (Parenizor);
Loren Shqipognja
Consulte também: Classe de extensão de JavaScript
user56reinstatemonica8

Respostas:

80

Mudei a forma como faço isso agora, tento evitar o uso de funções construtoras e suas prototypepropriedades, mas minha antiga resposta de 2010 ainda está no final. Agora eu prefiro Object.create(). Object.createestá disponível em todos os navegadores modernos.

Devo observar que Object.creategeralmente é muito mais lento do que usar newcom um construtor de função.

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

Um dos grandes benefícios de usar Object.create é ser capaz de passar um argumento defineProperties , que dá a você um controle significativo sobre como as propriedades na classe podem ser acessadas e enumeradas, e também uso funções para criar instâncias, que servem como construtores de certa forma, já que você pode fazer a inicialização no final em vez de apenas retornar a instância.

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 é minha resposta 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
fonte
5
E quanto ao valor sub.prototype.constructor? Acho que também deve ser definido como subvalor.
maximus
Além de você estar usando palavras-chave reservadas ('super') como nomes de classe, não consegui colocar seu exemplo em execução: jsbin.com/ixiyet/8/edit
MOnsDaR
@MOnsDaR Eu mudei o nome para Base
Bjorn
Se eu usar alert()para ver o que instance.showColor()retorna, ainda recebo undefined. jsbin.com/uqalin/1
MOnsDaR
1
@MOnsDaR é porque ele consola logs, não retorna nada para o alerta mostrar. Você vê uma instrução de retorno em showColor?
Bjorn
190

Em JavaScript, você não tem classes, mas pode obter herança e reutilização de comportamento de várias maneiras:

Herança pseudo-clássica (por meio de prototipagem):

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

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

Deve ser usado com o newoperador:

var subInstance = new Sub();

Aplicação de função ou "encadeamento de construtor":

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


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

Essa abordagem também deve ser usada com o newoperador:

var subInstance = new Sub();

A diferença com o primeiro exemplo é que quando colocamos applyo Superconstrutor no thisobjeto interno Sub, ele adiciona as propriedades atribuídas a thison Super, diretamente na nova instância, por exemplo, subInstancecontém as propriedades member1e member2diretamente ( subInstance.hasOwnProperty('member1') == true;).

No primeiro exemplo, essas propriedades são alcançadas através da cadeia de protótipos , elas existem em um [[Prototype]]objeto interno .

Herança parasitária ou construtores de energia:

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

  return obj;
}

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

Esta abordagem é baseada basicamente no "aumento do objeto", você não precisa usar o newoperador e, como você pode ver, a thispalavra-chave não está envolvida.

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

O método acima é uma técnica de herança prototípica proposta por Crockford .

Instâncias de objeto herdam de outras instâncias de objeto, é isso.

Esta técnica pode ser melhor do que simples "aumento do objeto", porque as propriedades herdadas não são copiados sobre todos os novos casos de objeto, uma vez que a base de objeto é definido como a [[Prototype]]da alargada objeto, no exemplo acima subInstancecontém fisicamente somente a member3propriedade.

CMS
fonte
3
não use instâncias para herança - use ES5 Object.create()ou uma clone()função personalizada (por exemplo, mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) para herdar diretamente do objeto de protótipo; veja os comentários em stackoverflow.com/questions/1404559/… para uma explicação
Christoph
Obrigado @Christoph, ia mencionar o Object.createmétodo :)
CMS
1
Esta não é uma herança adequada, já que você terá membros de instância de Super no protótipo de Sub. Portanto, todas as instâncias do Sub compartilharão a mesma member1variável, o que não é desejável de forma alguma. Claro que eles podem reescrever, mas isso simplesmente não faz sentido. github.com/dotnetwise/Javascript-FastClass é a melhor solução de açúcar.
Adaptabi de
Olá @CMS, você poderia explicar por favor, por que preciso criar uma instância da classe pai no primeiro exemplo para configurar a herança para a subclasse? Eu estou falando sobre esta linha: Sub.prototype = new Super();. E se as duas classes nunca forem usadas durante a execução do script? Parece um problema de desempenho. Por que preciso criar uma classe pai se a classe filha não é realmente usada? Você pode explicar, por favor? Aqui está uma demonstração simples do problema: jsfiddle.net/slavafomin/ZeVL2 Obrigado!
Slava Fomin II
Em todos os exemplos - exceto o último - há uma "classe" para Super e uma "classe" para Sub, e então você cria uma instância de Sub. Você pode adicionar um exemplo comparável para o exemplo Object.create?
Lucas
49

Para quem chegar a esta página em 2019 ou depois

Com a versão mais recente do padrão ECMAScript (ES6) , você pode usar a palavra-chave class.

Observe que a definição da classe não é regular object; portanto, não há vírgulas entre os membros da classe. Para criar uma instância de uma classe, você deve usar a newpalavra - chave. Para herdar de uma classe 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 herdar de uma classe 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

A partir da classe derivada, você pode usar super de qualquer construtor ou método para acessar sua classe base:

  • Para chamar o construtor pai, use super().
  • Para chamar outro membro, use, por exemplo super.getName(),.

Há mais coisas sobre o uso de classes. Se você quiser se aprofundar no assunto, recomendo " Classes in ECMAScript 6 ", do Dr. Axel Rauschmayer. *

fonte

Merlin
fonte
1
Sob o capô, classe extendsé (ultra útil) a sintaxe de açúcar para a cadeia de protótipo: stackoverflow.com/a/23877420/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
apenas para sua informação 'instance.name' aqui 'mycar.name' retornará o nome da classe. Este é um comportamento padrão do ES6 e ESnext. Aqui, para mycar.name retornará 'Vehicle'
Shiljo Paulson
7

Bem, em JavaScript não há "herança de classe", há apenas "herança de protótipo". Então você não faz uma classe "caminhão" e a marca como uma subclasse de "automóvel". Em vez disso, você faz um objeto "Jack" e diz que ele usa "John" como protótipo. Se John sabe quanto é "4 + 4", então Jack também sabe.

Eu sugiro que você leia o artigo de Douglas Crockford sobre herança de protótipos aqui: http://javascript.crockford.com/prototypal.html Ele também mostra como você pode fazer com que o JavaScript tenha uma herança "semelhante" a outras linguagens OO e então explica que isso na verdade, significa quebrar o javaScript de uma maneira que não deveria ser usado.

ingênuos
fonte
Vamos supor que o protótipo de Jack seja John. Durante o tempo de execução, adicionei uma propriedade / comportamento ao John. Vou receber essa propriedade / comportamento de Jack?
Ram Bavireddi
Você com certeza vai. Por exemplo, é assim que as pessoas normalmente adicionam o método "trim ()" a todos os objetos de string (não é embutido). Veja um exemplo aqui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
ingênuos
6

Acho esta citação a mais esclarecedora:

Em essência, uma "classe" JavaScript é apenas um objeto Function que serve como um construtor mais um objeto protótipo anexado. ( Fonte: Guru Katz )

Gosto de usar construtores em vez de objetos, portanto, gosto do método de "herança pseudo-clássica" descrito aqui pelo CMS . Aqui está um exemplo de herança múltipla com uma cadeia de protótipo :

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

Aqui está outro bom recurso do MDN , e aqui está um jsfiddle para que você possa experimentá-lo .

Lucas
fonte
4

A herança do Javascript é um pouco diferente do Java e do PHP, porque realmente não tem classes. Em vez disso, ele possui objetos de protótipo que fornecem métodos e variáveis ​​de membro. Você pode encadear esses protótipos para fornecer herança de objeto. O padrão mais comum que encontrei ao pesquisar essa questão é descrito na Mozilla Developer Network . Eu atualizei o exemplo deles para incluir uma chamada para um método da superclasse e para mostrar o log em uma mensagem 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);

Pessoalmente, acho a herança em Javascript estranha, mas esta é a melhor versão que encontrei.

Don Kirkby
fonte
3

você não pode (no sentido clássico). Javascript é uma linguagem prototípica. Você observará que nunca declara uma "classe" em Javascript; você simplesmente define o estado e os métodos de um objeto. Para produzir herança, você pega um objeto e o protótipo. O protótipo é estendido com novas funcionalidades.

DC
fonte
1

Você pode usar .inheritWithe .fastClass biblioteca . É mais rápido do que a maioria das bibliotecas populares e às vezes até mais rápido do que a versão nativa.

Muito 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
fonte
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
fonte
1

Depois de ler muitos posts, eu encontrei essa solução ( jsfiddle aqui ). Na maioria das vezes eu não preciso de algo mais 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
fonte
1

Graças à resposta do CMS e depois de mexer um pouco com prototype e Object.create e tudo o mais, consegui chegar a uma solução bacana para minha herança usando aplicar como mostrado aqui:

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
fonte
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
fonte
2
Por favor, não poste apenas o código, explique o que ele faz e como responde à pergunta.
Patrick Hund
1

Aulas ES6:

Javascript não possui classes. As classes em javascript são apenas açúcar sintático construído em cima do padrão de herança prototípico que o javascript. Você pode usar o JS classpara impor a herança prototípica, mas é importante perceber que, na verdade, você ainda está usando funções de construtor nos bastidores.

Esses conceitos também se aplicam quando você está estendendo de uma es6'classe' usando a palavra-chave extends. Isso apenas cria um link adicional na cadeia de protótipo. o__proto__

Exemplo:

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()também é uma forma de criar herança em JS em javascript. Object.create()é uma função que cria um novo objeto, e toma um objeto existente como argumento. Ele atribuirá o objeto que foi recebido como um argumento à __proto__propriedade do objeto recém-criado. Novamente, é importante perceber que estamos vinculados ao paradigma de herança prototípico que JS incorpora.

Exemplo:

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
fonte
0

Você não pode herdar de uma classe em JavaScript, porque não há classes em JavaScript.

Jörg W Mittag
fonte