Is there any way to extend an object in javascript?

Asked

Viewed 1,065 times

27

In PHP, we can usually extend a class using the keyword extends which points to the class that will have inherited methods and attributes.

Example:

class MyObject extends ArrayObject
{
}

And if I wanted to do this in Javascript?

How could I, for example, create an object that inherits the attributes of the object String or Number?

4 answers

40

You can see the below example of inheritance in which a Dog extends from an Animal and which specifies the "speech" with the bark:

And yes This is Javascript, ES6.

class Animal { 
  constructor(nome) {
    this.nome = nome;
  }
  
  fala() {
    console.log(this.nome + ' faz uma especie de barulho...');
  }
}

class Cao extends Animal {
  fala() {
    console.log(this.nome + ' ladra!');
  }
}

Example: http://www.es6fiddle.net/if9wz3hd/

  • 10

    @ phb: please leave this answer open. perhaps out of ignorance they voted negative, but I’ll ask for a second look at the answer for your contribution to be better recognized. If you want to complete the answer even better. And welcome to [pt.so]! +1

  • 4

    ES6 is not yet supported on all browsers. But I recognize that it is a valid answer

23


The problem with doing this in Javascript is that - at least until Ecmascript 5 (the version currently most supported by browsers) - This language has no classes! While in PHP, or Java, C#, etc "class inherits from class" and "class instance object", in Javascript "object inherits from object".

So what?

It may seem a theoretical detail without much importance (amplified by the fact that Javascript "pretend" that supports Classical OO), but it has a very precise practical significance: the inherited object does not have a copy attributes of the original object, both share the same attributes! This means that if the attribute of the parent object is mutable, and you try to change it in the child object, you are changing the same object.

Another consequence of this mode of inheritance (which is called "prototypical inheritance") is that it does not give the attributes of the "subclass" to inherit the default value of the attributes of the "superclass" - a specific instance of the "superclass" is required for the inheritance to be actually possible:

function Superclasse(foo) {
    this.foo = (typeof foo == "undefined" ? "bar" : foo);
}

function Subclasse() { }
Subclasse.prototype = new Superclasse("baz");

var obj = new Subclasse(); // Não herda de "Superclasse", mas de uma instância
                           // específica de "Superclasse"
console.log(obj.foo); // baz

In this simple example the solution is obvious: do not pass the parameter "baz" in the prototype builder (leave it with the default value) and make the constructor of Subclasse call the builder of Superclasse. But in more complex situations it is necessary to be careful, especially because this prototype is a complete object, it can be accessed, have its methods called, etc.

That my answer the other question on the subject addresses other potential problems with Javascript inheritance. Perhaps because of this confusing nature, I don’t know, it was decided that Ecmascript 6 would provide appropriate class support, as exemplified in phb response. But until then...

How then?

You can do it in the "traditional" way (i.e. imitating classical heritage) or in the "explicit" way (making it clear what you are inheriting from that). I personally always prefer the explicit mode, but I often use the traditional for reasons of force majeure (some time ago this method had even better performance in Chrome than the explicit).

"Builders" and new

If you call a function f anyone using the keyword new, the following things happen:

  • A new object is created;
  • Your prototype becomes f.prototype (that by the way is not the prototype of f!);
  • The function f is called, using this new object as the this;
  • The return of the function is:
    • ignored, if not of the type object (!) and the new returns this new object; or:
    • returned, if the type object, ignoring the this newly created (!!).

Confused? A little, but if you follow the "magic formula" everything seems to work well:

// Cria uma função com o papel de "construtora"
function Animal(nome) {
    this.nome = (typeof nome == "undefined" ? "Anônimo" : nome);
    //return 42;      // Não retorne nada
}
// "prototype" é o protótipo dos objetos criados por esse construtor
Animal.prototype.reino = "Animalia";
Animal.prototype.descricao = function() {
    return this.nome + " é um membro do reino " + this.reino;
}

function Cachorro(nome, cor) {
    Animal.call(this, nome);
    this.pelos = cor;
}
Cachorro.prototype = new Animal(); // O protótipo dos cachorros é uma instância de animal
Cachorro.prototype.familia = "Canidae";
Cachorro.prototype.descricao = function() {
    return this.nome + " é um membro do reino " + this.reino + 
           ", família dos " + this.familia + ". Seu pelo é " + this.pelos;
}

function Calopsita(nome, cor) {
    Animal.call(this, nome);
    this.penas = cor;
}
Calopsita.prototype = new Animal(); // idem, mas note que é uma instância diferente
Calopsita.prototype.ordem = "Psittaciformes";
Calopsita.prototype.descricao = function() {
    return this.nome + " é um membro do reino " + this.reino + 
           ", ordem dos " + this.ordem + ". Suas penas são " + this.penas;
}

// Criar as "classes" pode ser difícil, mas instanciar é super fácil
var animais = [
    new Cachorro("Sebastian", "branco"),
    new Calopsita("Rex", "amarelas"),
    new Animal("pombo que pousou na minha janela")
];
for ( var i = 0 ; i < animais.length ; i++ )
    document.body.innerHTML += "<p>" + animais[i].descricao() + "</p>";

If you want to extend a "class", you can do it even if there are already "instances" of it, simply modifying the object that was created as a prototype:

Calopsita.prototype.clade = "Dinossauria";

Object.create

When you call Object.create, a new object is created, with the prototype informed (you can use null if you do not want any prototype, but for equivalence with a literal object, use Object.prototype) and optionally with a set of property descriptors. Simple and direct:

// Literal de objetos (mais simples; seu protótipo é Object.prototype)
var animal = {
    nome: "Anônimo",
    reino: "Animalia",
    descricao: function() {
        return this.nome + " é um membro do reino " + this.reino;
    }
};

// Object.create (o chato são esses "descritores de propriedades"...)
var cachorro = Object.create(animal, {
    familia: { value:"Canidae" },
    descricao: { value: function() {
        return this.nome + " é um membro do reino " + this.reino + 
               ", família dos " + this.familia + ". Seu pelo é " + this.pelo;
    }}
});

var calopsita = Object.create(animal, {
    ordem: { value: "Psittaciformes" },
    descricao: { value: function() {
       return this.nome + " é um membro do reino " + this.reino + 
              ", ordem dos " + this.ordem + ". Suas penas são " + this.penas;
    }}
});

// Instanciar, no entanto, é bastante verboso...
var animais = [
    Object.create(cachorro, { nome: { value:"Sebastian" }, pelo: { value: "branco" } }),
    Object.create(calopsita, { nome: { value:"Rex" }, penas: { value:"amarelas" } }),
    Object.create(animal, { nome: { value:"pombo que pousou na minha janela" } })
];
for ( var i = 0 ; i < animais.length ; i++ )
    document.body.innerHTML += "<p>" + animais[i].descricao() + "</p>";

Similarly, members added to the prototype reflect on inherited objects:

calopsita.clade = "Dinossauria";

Etc.?

These are not the only two ways to extend an object in Javascript: there are others building patterns, some simpler and others more complex, but all end up falling into one of these two forms (new or Object.create). It is not worth further detailing the matter here, even by what inheritance is complex enough in classical languages, in Javascript further, better structure your code via composition whenever possible, only using inheritance when strictly necessary.

Extending native types

Okay, after this whole turn, your original doubt seems not to have been addressed: can you extend native Javascript types like numbers, strings and arrays? The answer is... yes, but avoid doing that rsrs!

The fact that the inheritance is prototypical makes it trivial to add new fields and methods in the basic types, just modify the field prototype of its builders (who, reiterating once again, is not the prototype of the same, but rather the prototype of the objects created by them via new):

Number.prototype.quadrado = function() {
  return this * this;
};

document.body.innerHTML += (10).quadrado();

This is often used to extend basic types with useful functions. Used too much. Abused, I would say. In fact, I don’t trust any code that does this, because you never know which members have been added/modified, if one has overwritten another that already existed, etc. This pollutes the namespace, in the same way that global variables would pollute, so that I personally do not recommend - as much as the idea is useful (there are better ways for a language to support this, see Extension methods of the C#).

The "second best thing" would then be to create subtypes of these basic types; something that seem with a string, but built in a "special" way and only objects of the same type have customized members. Is there any way? Well, I myself already beat myself up with this idea a while ago, and I gave up, because though at first this is possible the construction of the basic types actually involves a lot of "magic". See for example the case of "wear or not new when building an array", and how the situation changes when you build a string... So even though it’s a useful concept, it’s not widely used in practice, and maybe it’s best to keep it that way (if you want some custom type to have all the methods of Array, for example, simply copy them to your prototype! See the concept of mixins).

  • Hehe :) Excellent timming! +1

11

Actually this is possible, and with the same syntax as PHP.

The novelty is part of ES6, the new version approved by Ecmascript, but is not yet possible on all browsers (2015). However it is already possible on Node.js.

An example would be like this:

class Animal {
  constructor(especie){
   this.especie = especie;
  }
  dizEspecie(){
    return this.especie;
  }
}

class Humano extends Animal {
  constructor(nome, especie){
    super(especie);
    this.nome = nome;
  }
  dizNome(){
    return this.nome + ' é um ' + super.dizEspecie();
  }
}

var paulo = new Humano('Paulo', 'Humano');

console.log(paulo.dizNome()); // Paulo é um Humano

Example: http://www.es6fiddle.net/if9zwob3/

In today’s Javascript, this is also possible. Through the prototype as described in other questions (1), (2), (3). The library that took this further in creating the same classes was the Mootools.

Mootools created Classes already in 2007 and the current API looks like this:

var Animal = new Class({
    initialize: function(age){
        this.age = age;
    }
});

var Cat = new Class({
    Extends: Animal,
    initialize: function(name, age){
        // calls initalize method of Animal class
        this.parent(age);
        this.name = name;
    }
});

var myCat = new Cat('Micia', 20);
alert(myCat.name); // alerts 'Micia'.
alert(myCat.age); // alerts 20.

Example: http://jsfiddle.net/8w45upvL/

Natively this can be done (among others):

function Animal(especie) {
    this.especie = especie;
    this.dizEspecie = function() {
        return this.especie;
    }
}

var Humano = function (nome) {
    this.nome = nome;
    this.dizNome = function () {
        return this.nome + ' é um ' + this.especie;
    }
}
Humano.prototype = new Animal('Humano');

var paulo = new Humano('Paulo');
var rui = new Humano('Rui');
var bobby = new Animal('canino');

console.log(paulo.dizNome()); // Paulo é um Humano
console.log(rui.dizNome()); // Rui é um Humano
console.log(typeof bobby.dizNome); // undefined
console.log(bobby.dizEspecie()); // canino

Example: http://jsfiddle.net/wvbk3kb8/2/

  • 2

    +1 . Thanks for the information. I am excited to delve into javascript after these answers

  • @Nothing wallacemaxters. I’m glad the answer referred to the requirements of the problems.

10

Yes, it is possible to extend using prototype.

function Empregado(){
   this.nome = "";
}

function Gerente(){
   this.departamento = "";
}
Gerente.prototype = new Empregado

Browser other questions tagged

You are not signed in. Login or sign up in order to post.