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?
9 answers
actualizado abaixo para ES6
Março de 2013 e ES5Este 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:
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:
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
}
}
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"
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
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();
}
);
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');
}
}])
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
}
}
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
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.