Herança em JavaScript parte II

Herança em JavaScript parte II

O JavaScript é uma linguagem orientada a objetos baseada em protótipos mas que, ao longo de seu desenvolvimento, foi forçada a “parecer” uma linguagem baseada em classes. Com o ECMAScript 5 é finalmente possível programar em JavaScript utilizando os conceitos de prototipação, herdando propriedades diretamente de um objeto.

Clonando

Um dos novos métodos adicionados pela especificação ECMAScript 5 é o Object.create. Com ele nós podemos criar um novo objeto clone de um outro. Em outras palavras, usar um objeto existente como protótipo.

No exemplo abaixo primeiro definimos um objeto pessoa, para logo depois criarmos um novo objeto joao que contém exatamente todas as propriedades de pessoa.

var pessoa = {
  caminhar: function() {
    // ...
  },
  falar: function() {
    // ...
  }
}

var joao = Object.create(pessoa)
joao.caminhar() // método clonado de *pessoa*

Com este método é possível também criarmos um objeto sem propriedades, que não usa nenhum outro como protótipo. O que era impossível antes.

var obj = {}
obj.toString() // método herdado de Object

var objVirgem = Object.create(null)
objVirgem.toString() // não possui nenhum método

Extendendo

Além de simplesmente clonar, é possível criar um novo objeto extendendo um já existente.

var animal = {
  respirar: function() {
    // ...
  },
  reproduzir: function() {
    // ...
  }
}

var cachorro = Object.create(animal, {
  latir: {
    value: function() {
      // ...
    }
  }
})

cachorro.respirar() // método herdado
cachorro.latir() // método adicionado

No exemplo acima criamos um novo objeto do tipo cachorro que contem todas as propriedades de animal além de um novo método latir.

Definindo propriedades

O novo meio de definirmos novas propriedades em um objeto é através do método Object.defineProperty. No ECMAScript 5, as propriedades de um objeto não são mais apenas valores acessados através de uma chave. Agora podemos definir setters, getters, se a propriedade é apenas de leitura, enumerável, …

Object.defineProperty(cachorro, 'dormir', {
  value: function() {
    // ...
  },
  writable: false,
  enumerable: false,
  configurable: false
})

Os descriptors de uma propriedade podem ser:

  • value: o valor da propriedade. pode ser uma função, um objeto, um número, …
  • writable: se false, o valor não pode ser alterado.
  • configurable: se false, não é possível deletar a propriedade ou modificar seus atributos (writable, configurable ou enumerable)
  • enumerable: se true, a propriedade será iterada em um loop for (var prop in obj){}

Além disso é possivel definir getters and setters que irão ser disparados de forma transparente toda vez que a propriedade for acessada ou modificada.

Object.defineProperty(cachorro, 'idade', {
  value: 1,
  get: funtion() {
    // já que cada ano de um cachorro é equivalente a 7 anos nossos
    return idade * 7
  },
  set: function(value) {
    idade = value
  }
})

cachorro.idade = 3
cachorro.idade // retorna 21 (3*7)

Métodos super

No exemplo abaixo, definimos uma nova propriedade respirar no objeto cachorro. Mas essa propriedade já havia sido herdada do objeto animal, então acabamos a sobreescrevendo.

Object.defineProperty(cachorro, 'respirar', {
  value: function(){
    // ...
  }
})
cachorro.respirar() // dispara função que acabamos de definir

Mas mesmo assim ainda podemos percorrer a cadeia de protótipos e acessar o método definido no protótipo de cachorro, o método definido no objeto animal. O novo método Object.getPrototypeOf nos dá acesso ao protótipo de um objeto. Então basta acessarmos o protótipo de cachorro e chamarmos o método anteriormente sobreescrito.

Object.getPrototypeOf(cachorro).respirar()
// o mesmo que
animal.respirar()

Modificando protótipo em tempo de execução

Em linguagens baseadas em protótipos podemos facilmente adicionar ou remover propriedades a objetos, bastando modificar seus protótipos. Por exemplo, caso um novo método seja implementado em animal, todos os objetos que usam animal como protótipo terão este método disponível.

cachorro.morrer() // undefined

Object.defineProperty(animal, 'morrer', {
  value: function() {  }
})
cachorro.morrer() // dispara função definida em animal

Então imaginem que queremos dar a todos os cachorros já criados em nossa aplicação um novo método. Para isso basta acessar o protótipo de cachorro e definir uma nova propriedade. Após isso, todos os cachorros já poderão usar o novo método.

Object.defineProperty(Object.getPrototypeOf(cachorro), 'comer', {
  value: function() {}
})

E no IE?

Infelizmente apenas a partir da versão 9 do IE que temos suporte a estas novas features do JavaScript. E sabemos que o IE 9 não roda no Windows XP, então ainda temos algum tempo para aprender e dominar este novo jeito de desenvolver com JavaScript.

Suporte

Object.create()5.05.04.09.011.60
Object.defineProperty()5.05.14.09.011.60
Object.getPrototypeOf()5.05.03.59.0--

Mas caso você tenha espiríto aventureiro, existe um shim para que estes novos métodos funcionem (ou parcialmente funcionem) em navegadores antigos.

#33