Embora muitas pessoas aqui digam que não há melhor maneira de criar objetos, há uma justificativa para o motivo de haver tantas maneiras de criar objetos em JavaScript, a partir de 2019, e isso tem a ver com o progresso do JavaScript nas diferentes iterações dos lançamentos EcmaScript desde 1997.
Antes do ECMAScript 5, havia apenas duas maneiras de criar objetos: a função construtora ou a notação literal (uma alternativa melhor ao novo Object ()). Com a notação da função construtora, você cria um objeto que pode ser instanciado em várias instâncias (com a nova palavra-chave), enquanto a notação literal entrega um único objeto, como um singleton.
// constructor function
function Person() {};
// literal notation
var Person = {};
Independentemente do método usado, os objetos JavaScript são simplesmente propriedades de pares de valores-chave:
// Method 1: dot notation
obj.firstName = 'Bob';
// Method 2: bracket notation. With bracket notation, you can use invalid characters for a javascript identifier.
obj['lastName'] = 'Smith';
// Method 3: Object.defineProperty
Object.defineProperty(obj, 'firstName', {
value: 'Bob',
writable: true,
configurable: true,
enumerable: false
})
// Method 4: Object.defineProperties
Object.defineProperties(obj, {
firstName: {
value: 'Bob',
writable: true
},
lastName: {
value: 'Smith',
writable: false
}
});
Nas versões anteriores do JavaScript, a única maneira real de imitar a herança baseada em classes era usar funções de construtor. a função construtora é uma função especial que é chamada com a palavra-chave 'new'. Por convenção, o identificador da função é maiúsculo, embora não seja necessário. Dentro do construtor, nos referimos à palavra-chave 'this' para adicionar propriedades ao objeto que a função construtora está criando implicitamente. A função construtora retorna implicitamente o novo objeto com as propriedades preenchidas de volta à função de chamada implicitamente, a menos que você use explicitamente a palavra-chave return e retorne outra coisa.
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.sayName = function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var bob = new Person("Bob", "Smith");
bob instanceOf Person // true
Há um problema com o método sayName. Normalmente, nas linguagens de programação baseadas em classes orientadas a objetos, você usa classes como fábricas para criar objetos. Cada objeto terá suas próprias variáveis de instância, mas terá um ponteiro para os métodos definidos no blueprint da classe. Infelizmente, ao usar a função construtora do JavaScript, toda vez que é chamada, ela define uma nova propriedade sayName no objeto recém-criado. Portanto, cada objeto terá sua própria propriedade sayName exclusiva. Isso consumirá mais recursos de memória.
Além do aumento dos recursos de memória, a definição de métodos dentro da função do construtor elimina a possibilidade de herança. Novamente, o método será definido como uma propriedade no objeto recém-criado e em nenhum outro objeto; portanto, a herança não pode funcionar como. Portanto, o JavaScript fornece a cadeia de protótipos como uma forma de herança, tornando o JavaScript uma linguagem prototípica.
Se você tem um pai e um pai compartilha muitas propriedades de um filho, ele deve herdar essas propriedades. Antes do ES5, era realizado da seguinte maneira:
function Parent(eyeColor, hairColor) {
this.eyeColor = eyeColor;
this.hairColor = hairColor;
}
Parent.prototype.getEyeColor = function() {
console.log('has ' + this.eyeColor);
}
Parent.prototype.getHairColor = function() {
console.log('has ' + this.hairColor);
}
function Child(firstName, lastName) {
Parent.call(this, arguments[2], arguments[3]);
this.firstName = firstName;
this.lastName = lastName;
}
Child.prototype = Parent.prototype;
var child = new Child('Bob', 'Smith', 'blue', 'blonde');
child.getEyeColor(); // has blue eyes
child.getHairColor(); // has blonde hair
A maneira como utilizamos a cadeia de protótipos acima tem uma peculiaridade. Como o protótipo é um link ativo, alterando a propriedade de um objeto na cadeia de protótipos, você também alteraria a mesma propriedade de outro objeto. Obviamente, alterar o método herdado de uma criança não deve alterar o método dos pais. O Object.create resolveu esse problema usando um polyfill. Assim, com Object.create, você pode modificar com segurança a propriedade de uma criança na cadeia de protótipos sem afetar a mesma propriedade do pai na cadeia de protótipos.
O ECMAScript 5 introduziu o Object.create para resolver o erro mencionado anteriormente na função construtora para criação de objetos. O método Object.create () cria um novo objeto, usando um objeto existente como o protótipo do objeto recém-criado. Como um novo objeto é criado, você não tem mais o problema de que a modificação da propriedade filho na cadeia de protótipos modificará a referência do pai para essa propriedade na cadeia.
var bobSmith = {
firstName: "Bob",
lastName: "Smith",
sayName: function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var janeSmith = Object.create(bobSmith, {
firstName : { value: "Jane" }
})
console.log(bobSmith.sayName()); // My name is Bob Smith
console.log(janeSmith.sayName()); // My name is Jane Smith
janeSmith.__proto__ == bobSmith; // true
janeSmith instanceof bobSmith; // Uncaught TypeError: Right-hand side of 'instanceof' is not callable. Error occurs because bobSmith is not a constructor function.
Antes do ES6, havia um padrão criacional comum para utilizar construtores de funções e Object.create:
const View = function(element){
this.element = element;
}
View.prototype = {
getElement: function(){
this.element
}
}
const SubView = function(element){
View.call(this, element);
}
SubView.prototype = Object.create(View.prototype);
Agora Object.create, juntamente com funções de construtor, têm sido amplamente utilizados para criação e herança de objetos em JavaScript. No entanto, o ES6 introduziu o conceito de classes, que são principalmente açúcar sintático sobre a herança baseada em protótipo existente do JavaScript. A sintaxe da classe não introduz um novo modelo de herança orientada a objetos ao JavaScript. Assim, o JavaScript continua sendo uma linguagem prototípica.
As classes ES6 tornam a herança muito mais fácil. Não precisamos mais copiar manualmente as funções de protótipo da classe pai e redefinir o construtor da classe filha.
// create parent class
class Person {
constructor (name) {
this.name = name;
}
}
// create child class and extend our parent class
class Boy extends Person {
constructor (name, color) {
// invoke our parent constructor function passing in any required parameters
super(name);
this.favoriteColor = color;
}
}
const boy = new Boy('bob', 'blue')
boy.favoriteColor; // blue
Ao todo, essas 5 estratégias diferentes de Criação de Objetos em JavaScript coincidiram com a evolução do padrão EcmaScript.