Repita depois de mim: JavaScript não tem classes
JavaScript possui objetos. Cada objeto possui um protótipo , que é um null
ou 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 new
palavra-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 athis
palavra-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
{}
Shape
Shape()
new Shape()
Definimos uma propriedade
move
como . 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.prototype
obj
new Shape()
move
obj.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()
Shape
Circle
Shape()
this
Circle()
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
constructor
propriedade errada , portanto, consertamos isso configurando-a de volta para aCircle
classe. Aconstructor
propriedade é 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 Shape
classe 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 constructor
propriedade 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