Mentre molte persone qui affermano che non esiste un modo migliore per la creazione di oggetti, c'è una logica del perché ci sono così tanti modi per creare oggetti in JavaScript, a partire dal 2019, e questo ha a che fare con l'avanzamento di JavaScript sulle diverse iterazioni delle versioni EcmaScript risalenti al 1997.
Prima di ECMAScript 5, c'erano solo due modi per creare oggetti: la funzione di costruzione o la notazione letterale (una migliore alternativa a new Object ()). Con la notazione della funzione di costruzione si crea un oggetto che può essere istanziato in più istanze (con la nuova parola chiave), mentre la notazione letterale fornisce un singolo oggetto, come un singleton.
// constructor function
function Person() {};
// literal notation
var Person = {};
Indipendentemente dal metodo utilizzato, gli oggetti JavaScript sono semplicemente proprietà delle coppie chiave-valore:
// 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
}
});
Nelle prime versioni di JavaScript, l'unico vero modo per imitare l'eredità basata su classi era usare le funzioni di costruzione. la funzione di costruzione è una funzione speciale che viene invocata con la parola chiave "new". Per convenzione, l'identificatore di funzione è in maiuscolo, albiet non è richiesto. All'interno del costruttore, ci riferiamo alla parola chiave "this" per aggiungere proprietà all'oggetto che la funzione di costruzione sta implicitamente creando. La funzione di costruzione restituisce implicitamente il nuovo oggetto con le proprietà popolate in modo implicito alla funzione di chiamata, a meno che non si usi esplicitamente la parola chiave return e si restituisca qualcos'altro.
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
Si è verificato un problema con il metodo sayName. In genere, nei linguaggi di programmazione basati su classi orientate agli oggetti, le classi vengono utilizzate come fabbriche per creare oggetti. Ogni oggetto avrà le sue variabili di istanza, ma avrà un puntatore ai metodi definiti nel modello di classe. Sfortunatamente, quando si utilizza la funzione di costruzione JavaScript, ogni volta che viene chiamato, definirà una nuova proprietà sayName sull'oggetto appena creato. Quindi ogni oggetto avrà la sua proprietà unica sayName. Ciò consumerà più risorse di memoria.
Oltre all'aumento delle risorse di memoria, la definizione di metodi all'interno della funzione di costruzione elimina la possibilità di ereditarietà. Ancora una volta, il metodo verrà definito come una proprietà sull'oggetto appena creato e nessun altro oggetto, quindi l'ereditarietà non può funzionare come. Pertanto, JavaScript fornisce la catena di prototipi come una forma di eredità, rendendo JavaScript un linguaggio prototipo.
Se hai un genitore e un genitore condivide molte proprietà di un figlio, il figlio dovrebbe ereditare tali proprietà. Prima di ES5, è stato realizzato come segue:
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
Il modo in cui abbiamo utilizzato la catena di prototipi sopra ha una stranezza. Poiché il prototipo è un collegamento attivo, modificando la proprietà di un oggetto nella catena del prototipo, cambieresti anche la stessa proprietà di un altro oggetto. Ovviamente, cambiare il metodo ereditato da un bambino non dovrebbe cambiare il metodo del genitore. Object.create ha risolto questo problema utilizzando un polyfill. Pertanto, con Object.create, è possibile modificare in modo sicuro la proprietà di un bambino nella catena del prototipo senza influire sulla stessa proprietà del genitore nella catena del prototipo.
ECMAScript 5 ha introdotto Object.create per risolvere il suddetto errore nella funzione di costruzione per la creazione di oggetti. Il metodo Object.create () CREA un nuovo oggetto, utilizzando un oggetto esistente come prototipo dell'oggetto appena creato. Poiché viene creato un nuovo oggetto, non si verifica più il problema in cui la modifica della proprietà figlio nella catena di prototipi modificherà il riferimento del genitore a quella proprietà nella catena.
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.
Prima di ES6, ecco un modello di creazione comune per utilizzare i costruttori di funzioni 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);
Ora Object.create associato a funzioni di costruzione sono stati ampiamente utilizzati per la creazione e l'ereditarietà di oggetti in JavaScript. Tuttavia, ES6 ha introdotto il concetto di classi, che sono principalmente zucchero sintattico sull'eredità esistente basata su prototipo di JavaScript. La sintassi della classe non introduce un nuovo modello di ereditarietà orientato agli oggetti in JavaScript. Pertanto, JavaScript rimane un linguaggio prototipo.
Le classi ES6 rendono l'ereditarietà molto più semplice. Non è più necessario copiare manualmente le funzioni prototipo della classe genitore e ripristinare il costruttore della classe figlio.
// 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
Tutto sommato, queste 5 diverse strategie di creazione di oggetti in JavaScript hanno coinciso con l'evoluzione dello standard EcmaScript.