Classe De Extensão De JavaScript

tenho uma classe base:

function Monster() {
  this.health = 100;
}

Monster.prototype.growl = function() {
  console.log("Grr!");
}

que eu quero estender e criar outra classe com:

function Monkey extends Monster() {
  this.bananaCount = 5;
}

Monkey.prototype.eatBanana {
  this.bananaCount--;
  this.health++; //Accessing variable from parent class monster
  this.growl();  //Accessing function from parent class monster
}
Fiz bastante pesquisa e parece haver muitas soluções complicadas para fazer isso em JavaScript. Qual seria a maneira mais simples e confiável de realizar isso em JS?

Author: Zearin, 2013-03-04

9 answers

actualizado abaixo para ES6

Março de 2013 e ES5

Este documento MDN descreve bem a extensão das classes:

Https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript

Em particular, aqui estão eles a lidar com isso.
// define the Person Class
function Person() {}

Person.prototype.walk = function(){
  alert ('I am walking!');
};
Person.prototype.sayHello = function(){
  alert ('hello');
};

// define the Student class
function Student() {
  // Call the parent constructor
  Person.call(this);
}

// inherit Person
Student.prototype = Object.create(Person.prototype);

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;

// replace the sayHello method
Student.prototype.sayHello = function(){
  alert('hi, I am a student');
}

// add sayGoodBye method
Student.prototype.sayGoodBye = function(){
  alert('goodBye');
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

Note que Object.create() não é suportado em alguns navegadores mais antigos, incluindo IE8:

Object.create browser support

Se estiver na posição de necessitar de suportá-los, o documento MDN associado sugere a utilização de um polifill, ou a seguinte aproximação:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

Usar isto como Student.prototype = createObject(Person.prototype) é preferível a usar {[5] } na medida em que evita chamar a função construtora do Pai ao herdar o protótipo, e só chama o construtor pai quando o construtor do herdeiro está a ser chamado.

Maio 2017 e ES6

Felizmente, os designers de JavaScript ouviram os nossos pedidos de Ajuda e adoptaram um novo uma forma adequada de abordar esta questão.

MDN tem outro grande exemplo sobre herança de classe ES6, mas eu vou mostrar o mesmo conjunto de classes como acima reproduzido em ES6:

class Person {
    sayHello() {
        alert('hello');
    }

    walk() {
        alert('I am walking!');
    }
}

class Student extends Person {
    sayGoodBye() {
        alert('goodBye');
    }

    sayHello() {
        alert('hi, I am a student');
    }
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true
Limpo e compreensível, como todos queremos. Tenha em mente que, embora o ES6 seja bastante comum, não é suportado em todos os lugares:

ES6 browser support

 122
Author: Oliver Spryn, 2017-05-31 17:00:12

O ES6 dá-lhe agora a oportunidade de usar a classe & extende palavras-chave:

Então, o teu código será:

Tens uma classe base:

class Monster{
       constructor(){
             this.health = 100;
        }
       growl() {
           console.log("Grr!");
       }

}

Que você quer estender e criar outra classe com:

class Monkey extends Monster {
        constructor(){
            super(); //don't forget "super"
            this.bananaCount = 5;
        }


        eatBanana() {
           this.bananaCount--;
           this.health++; //Accessing variable from parent class monster
           this.growl(); //Accessing function from parent class monster
        }

}
 11
Author: Abdennour TOUMI, 2016-08-06 00:46:05

Tenta isto:

Function.prototype.extends = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

Monkey.extends(Monster);
function Monkey() {
  Monster.apply(this, arguments); // call super
}

Edit: eu colocar uma demonstração rápida aqui http://jsbin.com/anekew/1/edit. Note que extends é uma palavra reservada em JS e você pode receber avisos quando acúmulo seu código, você pode simplesmente nome é inherits, que é o que eu costumo fazer.

Com este auxiliar no lugar e usando um objecto props como parâmetro único, a herança em JS torna-se um pouco mais simples:

Function.prototype.inherits = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

function Monster(props) {
  this.health = props.health || 100;
}

Monster.prototype = {
  growl: function() {
    return 'Grrrrr';
  }
};

Monkey.inherits(Monster);
function Monkey() {
  Monster.apply(this, arguments);
}

var monkey = new Monkey({ health: 200 });

console.log(monkey.health); //=> 200
console.log(monkey.growl()); //=> "Grrrr"
 10
Author: elclanrs, 2013-03-04 00:50:21

Se não gostas da abordagem do protótipo, porque ele não se comporta bem, podias tentar isto:

var BaseClass = function() 
{
    this.some_var = "foobar";

    /**
     * @return string
     */
    this.someMethod = function() {
        return this.some_var;
    }
};

var MyClass = new Class({ extends: BaseClass }, function()
{
    /**
     * @param string value
     */
    this.__construct = function(value)
    {
        this.some_var = value;
    }
})

Usando uma biblioteca leve (2K minificado): https://github.com/haroldiedema/joii

 6
Author: Harold, 2014-01-22 15:55:15

Esta é uma extensão (desculpe o trocadilho) da solução de elclanrs para incluir detalhes sobre os métodos de instância, bem como tomar uma abordagem extensível a esse aspecto da questão; reconheço plenamente que isso é feito graças ao "JavaScript: the Definitive Guide" de David Flanagan (parcialmente ajustado para este contexto). Note-se que isto é claramente mais descritivo do que outras soluções, mas provavelmente beneficiaria a longo prazo.

Primeiro usamos a simples função "extend" do David., que copia as propriedades para um objecto específico:

function extend(o,p) {
    for (var prop in p) {
        o[prop] = p[prop];
    }
    return o;
}
Então implementamos o utilitário de definição da subclasse:
function defineSubclass(superclass,     // Constructor of our superclass
                          constructor,  // Constructor of our new subclass
                          methods,      // Instance methods
                          statics) {    // Class properties
        // Set up the prototype object of the subclass
    constructor.prototype = Object.create(superclass.prototype);
    constructor.prototype.constructor = constructor;
    if (methods) extend(constructor.prototype, methods);
    if (statics) extend(constructor, statics);
    return constructor;
}
Para a última preparação, melhoramos o nosso protótipo de função com a nova encenação do David.
Function.prototype.extend = function(constructor, methods, statics) {
    return defineSubclass(this, constructor, methods, statics);
};

Depois de definir a nossa classe de monstros, fazemos o seguinte (que é reutilizável para quaisquer novas Classes que queremos estender/herdar):

var Monkey = Monster.extend(
        // constructor
    function Monkey() {
        this.bananaCount = 5;
        Monster.apply(this, arguments);    // Superclass()
    },
        // methods added to prototype
    eatBanana: function() {
        this.bananaCount--;
        this.health++;
        this.growl();
    }
);
 1
Author: GMeister, 2014-10-09 20:45:31

Para a extensão tradicional pode simplesmente escrever superclasse como função de construtor, e depois aplica este construtor para a tua turma herdada.

     function AbstractClass() {
      this.superclass_method = function(message) {
          // do something
        };
     }

     function Child() {
         AbstractClass.apply(this);
         // Now Child will have superclass_method()
     }

Exemplo em angularjs:

Http://plnkr.co/edit/eFixlsgF3nJ1LeWUJKsd?p=preview

app.service('noisyThing', 
  ['notify',function(notify){
    this._constructor = function() {
      this.scream = function(message) {
          message = message + " by " + this.get_mouth();
          notify(message); 
          console.log(message);
        };

      this.get_mouth = function(){
        return 'abstract mouth';
      }
    }
  }])
  .service('cat',
  ['noisyThing', function(noisyThing){
    noisyThing._constructor.apply(this)
    this.meow = function() {
      this.scream('meooooow');
    }
    this.get_mouth = function(){
      return 'fluffy mouth';
    }
  }])
  .service('bird',
  ['noisyThing', function(noisyThing){
    noisyThing._constructor.apply(this)
    this.twit = function() {
      this.scream('fuuuuuuck');
    }
  }])
 0
Author: Dan Key, 2015-08-25 19:47:07

Para Autodidactos:

function BaseClass(toBePrivate){
    var morePrivates;
    this.isNotPrivate = 'I know';
    // add your stuff
}
var o = BaseClass.prototype;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';


// MiddleClass extends BaseClass
function MiddleClass(toBePrivate){
    BaseClass.call(this);
    // add your stuff
    var morePrivates;
    this.isNotPrivate = 'I know';
}
var o = MiddleClass.prototype = Object.create(BaseClass.prototype);
MiddleClass.prototype.constructor = MiddleClass;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';



// TopClass extends MiddleClass
function TopClass(toBePrivate){
    MiddleClass.call(this);
    // add your stuff
    var morePrivates;
    this.isNotPrivate = 'I know';
}
var o = TopClass.prototype = Object.create(MiddleClass.prototype);
TopClass.prototype.constructor = TopClass;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';


// to be continued...

Criar "instância" com getter e setter:

function doNotExtendMe(toBePrivate){
    var morePrivates;
    return {
        // add getters, setters and any stuff you want
    }
}
 0
Author: Steffomio, 2016-12-26 11:01:36
Eu posso propor uma variante, só tenho lido no livro, parece a mais simples:
function Parent() { 
  this.name = 'default name';
};

function Child() {
  this.address = '11 street';
};

Child.prototype = new Parent();      // child class inherits from Parent
Child.prototype.constructor = Child; // constructor alignment

var a = new Child(); 

console.log(a.name);                // "default name" trying to reach property of inherited class
 0
Author: Yarik, 2017-03-17 19:13:36

Resumo:

Existem várias maneiras que podem resolver o problema de estender uma função de construtor com um protótipo em Javascript. Qual destes métodos é a melhor solução baseada na opinião. No entanto, aqui estão dois métodos frequentemente utilizados, a fim de estender o protótipo de função de um construtor.

ES 2015 Classes:

class Monster {
  constructor(health) {
    this.health = health
  }
  
  growl () {
  console.log("Grr!");
  }
  
}


class Monkey extends Monster {
  constructor (health) {
    super(health) // call super to execute the constructor function of Monster 
    this.bananaCount = 5;
  }
}

const monkey = new Monkey(50);

console.log(typeof Monster);
console.log(monkey);

A abordagem acima de usar classes ES 2015 não é nada mais do que açúcar sintático sobre o padrão de herança prototípica em javascript. Aqui o primeiro registro onde avaliamos {[3] } podemos observar que esta é a função. Isto é porque as classes são apenas funções construtoras sob o capô. No entanto, você pode gostar desta forma de implementar a herança prototípica e definitivamente deve aprendê-la. É utilizado em quadros importantes como ReactJS e Angular2+.

Função de fábrica com Object.create():

function makeMonkey (bananaCount) {
  
  // here we define the prototype
  const Monster = {
  health: 100,
  growl: function() {
  console.log("Grr!");}
  }
  
  const monkey = Object.create(Monster);
  monkey.bananaCount = bananaCount;

  return monkey;
}


const chimp = makeMonkey(30);

chimp.growl();
console.log(chimp.bananaCount);

Este método utiliza o método Object.create() que leva um objecto que será o protótipo do objecto recém-criado que devolve. Portanto, primeiro criamos o objeto protótipo nesta função e depois chamamos Object.create() que devolve um objeto vazio com a propriedade __proto__ configurada para o objeto monstro. Depois disso podemos inicializar todas as propriedades do objeto, neste exemplo Atribuímos o bananacount ao objeto recém-criado.

 0
Author: Willem van der Veen, 2018-08-31 08:00:31