Herança de protótipo JavaScript

Repita depois de mim: JavaScript não tem classes

JavaScript possui objetos. Cada objeto possui um protótipo , que é um nullou outro objeto. Os protótipos podem ser encadeados. Quando solicitamos uma propriedade de um objeto que não está diretamente definida naquele objeto, a cadeia de protótipo é pesquisada.

Em navegadores modernos (basicamente IE9 + e tudo o mais que lhe interessa), você pode criar facilmente um novo objeto com outro objeto como seu protótipo usando:

var someObject = Object.create(someOtherObject);

Também em navegadores modernos, você pode descobrir o protótipo de um objeto com:

// would return someOtherObectj in this case
Object.getPrototypeOf(someObject)

Em navegadores mais antigos (veja http://kangax.github.io/compat-table/es5/ ), você precisaria de uma função construtora vazia (veja abaixo) e a newpalavra-chave para criarsomeObject

JavaScript tem um conceito de classe que envolve funções de construtor. Funciona assim (adaptado de https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create):

// A 'superclass'.
var Shape = function(x, y) {
this.x = x;
this.y = y;
};

Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
};

// A 'subclass'
var Circle = function(x, y, radius, color) {
Shape.call(this, x, y); // call the 'superclass' constructor
this.radius = radius;
this.color = color;
};

Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

Circle.prototype.grow = function(amount) {
this.radius += amount;
};

Agora é possível fazer:

var circle1 = new Circle(10, 10, 100, 'blue');

circle1
.move(5, 5); // calls the function from `Shape.prototype`
circle1
.grow(10); // calls the function form `Circle.prototype`

circle1
instanceof Shape; // true
circle1
instanceof Circle; // true

Aqui está o que está acontecendo:

  • Definimos uma função construtora chamada Shape. Ele usa a thispalavra-chave para definir propriedades em objetos criados por ele.

  • A propriedade é um objeto vazio ( ) por padrão. Ele especifica o protótipo para novos objetos criados com o construtor. Observe o texto cuidadoso aqui: Este objeto não é o protótipo de (uma função de construtor), é o protótipo de todos os objetos criados com .Shape.prototype{}ShapeShape() new Shape()

  • Definimos uma propriedade movecomo . Isso significa que se criarmos um objeto usando , que de outra forma não tem uma propriedade, e depois tentarmos chamar , o JavaScript encontrará esse objeto no protótipo.Shape.prototypeobjnew Shape()moveobj.move()

  • Preferimos definir funções no protótipo em vez de configurá-las com porque isso significa que a função é definida apenas uma vez, não toda vez que a função construtora é chamada!this.move = function () {}

  • Em seguida, definimos outra função construtora chamada Circle.

  • Optamos por chamar a função para nos ajudar a configurar o objeto. Isso é análogo a chamar um “superconstrutor” em uma linguagem orientada a objetos, mas observe que isso é inteiramente acidental. Nem nem é uma classe, são simplesmente funções. Eles não sabem nada um sobre o outro. Tudo o que fazemos é chamar a função vinculada ao mesmo contexto da função.Shape()ShapeCircleShape()thisCircle()

  • Em seguida, configuramos a cadeia de protótipos. Para fazer isso, substituímos a instância de protótipo padrão para objetos criados com , por um novo objeto que tem como seu protótipo.new Circle()Shape.prototype

  • Nota: Se você tiver que oferecer suporte a navegadores mais antigos que não têm , você pode considerar usar em vez de . O problema com isso é que a função construtora pode ter efeitos colaterais que você não deseja necessariamente.Object.create()new Shape()Object.create(Shape.prototype)new Shape()

  • Quando fizermos isso, teremos a constructorpropriedade errada , portanto, consertamos isso configurando-a de volta para a Circleclasse. A constructorpropriedade é simplesmente uma referência à função que foi usada para criar um objeto.

  • Agora podemos adicionar novas propriedades ao protótipo, como a função.grow()

Aqui está uma versão um pouco menos detalhada que usa underscore( http://underscorejs.org/ ) para copiar propriedades:

// A 'superclass'.
var Shape = function(x, y) {
this.x = x;
this.y = y;
};

Shape.prototype = _.extend(Object.create(null), {
constructor: Shape,

move
: function(x, y) {
this.x += x;
this.y += y;
}
});

// A 'subclass'
var Circle = function(x, y, radius, color) {
Shape.call(this, x, y); // call the 'superclass' constructor
this.radius = radius;
this.color = color;
};

Circle.prototype = _.extend(Object.create(Shape.prototype), {
constructor: Circle,

grow
: function(amount) {
this.radius += amount;
}

});

Nesse caso, estamos usando (que simplesmente copia todas as propriedades de seu segundo argumento para o primeiro argumento e, em seguida, retorna o objeto modificado) para configurar quaisquer novas propriedades em um único objeto literal, em vez de repetir as linhas._.extend()<Constructor>.prototype.<name>

Tecnicamente, a Shapeclasse poderia ser ainda mais simplificada (poderíamos apenas definir como um objeto literal), mas usar o padrão de forma consistente provavelmente será menos confuso.Shape.prototype

Existem várias bibliotecas por aí que tentam fornecer açúcar sintático para herança de propriedades. Backbone ( http://backbonejs.org/ ) suporta este padrão:

var MyModel = Backbone.Model.extend({
aProperty
: ...,

constructor: function(...) {}

});

Neste modelo, a constructorpropriedade substitui a definição de uma função construtora).

Ele faz isso definindo essas duas funções bem comentadas, que são instrutivas para entender:

// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var extend = function(protoProps, staticProps) {
var parent = this;
var child;

// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {
child
= protoProps.constructor;
} else {
child
= function(){ return parent.apply(this, arguments); };
}

// Add static properties to the constructor function, if supplied.
_
.extend(child, parent, staticProps);

// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child
.prototype = new Surrogate;

// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);

// Set a convenience property in case the parent's prototype is needed
// later.
child
.__super__ = parent.prototype;

return child;
};

Backbone.Model.extend = extend;

Veja também: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain