Alors que beaucoup de gens ici disent qu'il n'y a pas de meilleur moyen de créer des objets, il y a une raison pour laquelle il y a tant de façons de créer des objets en JavaScript, à partir de 2019, et cela a à voir avec la progression de JavaScript au cours des différentes itérations. des versions d'EcmaScript datant de 1997.
Avant ECMAScript 5, il n'y avait que deux façons de créer des objets: la fonction constructeur ou la notation littérale (une meilleure alternative à new Object ()). Avec la notation de fonction constructeur, vous créez un objet qui peut être instancié dans plusieurs instances (avec le nouveau mot-clé), tandis que la notation littérale délivre un seul objet, comme un singleton.
// constructor function
function Person() {};
// literal notation
var Person = {};
Quelle que soit la méthode que vous utilisez, les objets JavaScript sont simplement des propriétés de paires valeur / clé:
// 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
}
});
Dans les premières versions de JavaScript, le seul véritable moyen d'imiter l'héritage basé sur les classes était d'utiliser des fonctions de constructeur. la fonction constructeur est une fonction spéciale qui est appelée avec le mot-clé 'new'. Par convention, l'identifiant de la fonction est en majuscule, albiet il n'est pas obligatoire. À l'intérieur du constructeur, nous nous référons au mot-clé «this» pour ajouter des propriétés à l'objet que la fonction constructeur crée implicitement. La fonction constructeur renvoie implicitement le nouvel objet avec les propriétés remplies à la fonction appelante implicitement, sauf si vous utilisez explicitement le mot-clé return et retournez autre chose.
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
Il y a un problème avec la méthode sayName. En règle générale, dans les langages de programmation basés sur des classes orientées objet, vous utilisez des classes comme usines pour créer des objets. Chaque objet aura ses propres variables d'instance, mais il aura un pointeur vers les méthodes définies dans le plan de classe. Malheureusement, lors de l'utilisation de la fonction constructeur de JavaScript, chaque fois qu'elle est appelée, elle définira une nouvelle propriété sayName sur l'objet nouvellement créé. Ainsi, chaque objet aura sa propre propriété sayName unique. Cela consommera plus de ressources mémoire.
Outre l'augmentation des ressources de mémoire, la définition de méthodes à l'intérieur de la fonction constructeur élimine la possibilité d'héritage. Encore une fois, la méthode sera définie comme une propriété sur l'objet nouvellement créé et aucun autre objet, donc l'héritage ne peut pas fonctionner comme. Par conséquent, JavaScript fournit la chaîne de prototypes comme une forme d'héritage, faisant de JavaScript un langage prototypique.
Si vous avez un parent et qu'un parent partage de nombreuses propriétés d'un enfant, l'enfant doit hériter de ces propriétés. Avant ES5, il était accompli comme suit:
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 façon dont nous avons utilisé la chaîne de prototypes ci-dessus a une bizarrerie. Étant donné que le prototype est un lien actif, en modifiant la propriété d'un objet dans la chaîne de prototypes, vous changeriez également la même propriété d'un autre objet. Évidemment, changer la méthode héritée d'un enfant ne devrait pas changer la méthode du parent. Object.create a résolu ce problème en utilisant un polyfill. Ainsi, avec Object.create, vous pouvez modifier en toute sécurité la propriété d'un enfant dans la chaîne de prototypes sans affecter la même propriété du parent dans la chaîne de prototypes.
ECMAScript 5 a introduit Object.create pour résoudre le bogue susmentionné dans la fonction constructeur pour la création d'objets. La méthode Object.create () CRÉE un nouvel objet, en utilisant un objet existant comme prototype de l'objet nouvellement créé. Puisqu'un nouvel objet est créé, vous n'avez plus le problème où la modification de la propriété enfant dans la chaîne prototype modifiera la référence du parent à cette propriété dans la chaîne.
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.
Avant ES6, voici un modèle de création commun pour utiliser les constructeurs de fonctions et 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);
Désormais, Object.create, associé à des fonctions de constructeur, a été largement utilisé pour la création et l'héritage d'objets en JavaScript. Cependant, ES6 a introduit le concept de classes, qui sont principalement du sucre syntaxique par rapport à l'héritage basé sur prototype existant de JavaScript. La syntaxe de classe n'introduit pas de nouveau modèle d'héritage orienté objet dans JavaScript. Ainsi, JavaScript reste un langage prototypique.
Les classes ES6 rendent l'héritage beaucoup plus facile. Nous n'avons plus besoin de copier manuellement les fonctions prototypes de la classe parent et de réinitialiser le constructeur de la classe enfant.
// 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
Au total, ces 5 stratégies différentes de création d'objets en JavaScript ont coïncidé avec l'évolution du standard EcmaScript.