Si bien muchas personas aquí dicen que no hay una mejor manera para la creación de objetos, existe una justificación de por qué hay tantas formas de crear objetos en JavaScript, a partir de 2019, y esto tiene que ver con el progreso de JavaScript en las diferentes iteraciones de los lanzamientos de EcmaScript que datan de 1997.
Antes de ECMAScript 5, solo había dos formas de crear objetos: la función constructora o la notación literal (una mejor alternativa al nuevo Object ()). Con la notación de función de constructor, crea un objeto que se puede instanciar en varias instancias (con la nueva palabra clave), mientras que la notación literal entrega un solo objeto, como un singleton.
// constructor function
function Person() {};
// literal notation
var Person = {};
Independientemente del método que utilice, los objetos de JavaScript son simplemente propiedades de pares de valores clave:
// 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
}
});
En las primeras versiones de JavaScript, la única forma real de imitar la herencia basada en clases era usar funciones de constructor. la función constructora es una función especial que se invoca con la palabra clave 'new'. Por convención, el identificador de la función está en mayúscula, aunque no es obligatorio. Dentro del constructor, nos referimos a la palabra clave 'this' para agregar propiedades al objeto que la función constructora está creando implícitamente. La función constructora devuelve implícitamente el nuevo objeto con las propiedades pobladas a la función de llamada implícitamente, a menos que use explícitamente la palabra clave return y devuelva otra cosa.
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
Hay un problema con el método sayName. Por lo general, en los lenguajes de programación basados en clases orientadas a objetos, se utilizan clases como fábricas para crear objetos. Cada objeto tendrá sus propias variables de instancia, pero tendrá un puntero a los métodos definidos en el plano de la clase. Desafortunadamente, cuando se usa la función constructora de JavaScript, cada vez que se llama, definirá una nueva propiedad sayName en el objeto recién creado. Por lo tanto, cada objeto tendrá su propia propiedad única sayName. Esto consumirá más recursos de memoria.
Además del aumento de los recursos de memoria, la definición de métodos dentro de la función constructora elimina la posibilidad de herencia. Nuevamente, el método se definirá como una propiedad en el objeto recién creado y ningún otro objeto, por lo que la herencia no puede funcionar como. Por lo tanto, JavaScript proporciona la cadena de prototipos como una forma de herencia, haciendo de JavaScript un lenguaje prototípico.
Si tiene un padre y un padre comparte muchas propiedades de un hijo, entonces el hijo debe heredar esas propiedades. Antes de ES5, se logró de la siguiente manera:
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
La forma en que utilizamos la cadena de prototipos anterior tiene una peculiaridad. Dado que el prototipo es un enlace en vivo, al cambiar la propiedad de un objeto en la cadena del prototipo, también cambiaría la misma propiedad de otro objeto. Obviamente, cambiar el método heredado de un niño no debería cambiar el método de los padres. Object.create resolvió este problema utilizando un polyfill. Por lo tanto, con Object.create, puede modificar de forma segura la propiedad de un niño en la cadena del prototipo sin afectar la misma propiedad del padre en la cadena del prototipo.
ECMAScript 5 introdujo Object.create para resolver el error mencionado anteriormente en la función de constructor para la creación de objetos. El método Object.create () CREA un nuevo objeto, utilizando un objeto existente como prototipo del objeto recién creado. Como se crea un nuevo objeto, ya no tiene el problema de que la modificación de la propiedad secundaria en la cadena prototipo modificará la referencia del padre a esa propiedad en la cadena.
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 de ES6, aquí había un patrón de creación común para utilizar constructores de funciones y 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);
Ahora Object.create junto con las funciones de constructor se han usado ampliamente para la creación y herencia de objetos en JavaScript. Sin embargo, ES6 introdujo el concepto de clases, que son principalmente azúcar sintáctica sobre la herencia basada en prototipos existente de JavaScript. La sintaxis de clase no introduce un nuevo modelo de herencia orientado a objetos a JavaScript. Por lo tanto, JavaScript sigue siendo un lenguaje prototípico.
Las clases de ES6 hacen la herencia mucho más fácil. Ya no tenemos que copiar manualmente las funciones prototipo de la clase principal y restablecer el constructor de la clase secundaria.
// 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
Con todo, estas 5 estrategias diferentes de creación de objetos en JavaScript coincidieron con la evolución del estándar EcmaScript.