Javascript 1.9.3/ECMAScript 5 introduit le Object.create
, que Douglas Crockford, entre autres, défend depuis préconise Comment remplacer new
dans le code ci-dessous par Object.create
?
var UserA = function(nameParam) {
this.id = MY_GLOBAL.nextId();
this.name = nameParam;
}
UserA.prototype.sayHello = function() {
console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();
(Supposons que MY_GLOBAL.nextId existe).
Le mieux que je puisse trouver est:
var userB = {
init: function(nameParam) {
this.id = MY_GLOBAL.nextId();
this.name = nameParam;
},
sayHello: function() {
console.log('Hello '+ this.name);
}
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();
Il ne semble y avoir aucun avantage, alors je pense que je ne l’aurai pas. Je suis probablement trop néo-classique. Comment utiliser Object.create
pour créer l'utilisateur 'bob'?
Avec un seul niveau d'héritage, votre exemple risque de ne pas vous permettre de voir les avantages réels de Object.create
.
Cette méthode vous permet d’implémenter facilement Differential Legacy, où les objets peuvent directement hériter d’autres objets.
Sur votre exemple userB
, je ne pense pas que votre méthode init
devrait être publique ou même exister. Si vous appelez à nouveau cette méthode sur une instance d'objet existante, les propriétés id
et name
vont changer.
Object.create
vous permet d’initialiser les propriétés d’un objet à l’aide de son deuxième argument, par exemple:
var userB = {
sayHello: function() {
console.log('Hello '+ this.name);
}
};
var bob = Object.create(userB, {
'id' : {
value: MY_GLOBAL.nextId(),
enumerable:true // writable:false, configurable(deletable):false by default
},
'name': {
value: 'Bob',
enumerable: true
}
});
Comme vous pouvez le constater, les propriétés peuvent être initialisées sur le deuxième argument de Object.create
, avec un littéral d'objet utilisant une syntaxe similaire à celle utilisée par les méthodes Object.defineProperties
et Object.defineProperty
.
Il vous permet de définir les attributs de propriété (enumerable
, writable
ou configurable
), ce qui peut s'avérer très utile.
Il n'y a vraiment aucun avantage à utiliser Object.create(...)
par rapport à new object
.
Ceux qui préconisent cette méthode indiquent généralement des avantages plutôt ambigus: "évolutivité" ou " plus naturel en JavaScript " etc.
Cependant, je n'ai pas encore vu d'exemple concret montrant que Object.create
présente des avantages de any par rapport à l'utilisation de new
. Au contraire, il y a des problèmes connus avec cela. Sam Elsamman décrit ce qui se passe lorsqu'il y a des objets imbriqués et que Object.create(...)
est utilisé :
var Animal = {
traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!
Cela se produit parce que Object.create(...)
préconise une pratique dans laquelle data est utilisé pour créer de nouveaux objets; Ici, la donnée Animal
devient partie intégrante du prototype de lion
et bird
et pose des problèmes lorsqu’elle est partagée. Lors de l'utilisation de new, l'héritage prototype est explicite:
function Animal() {
this.traits = {};
}
function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();
var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4
En ce qui concerne les attributs de propriété facultatifs transmis à Object.create(...)
, ils peuvent être ajoutés à l’aide de Object.defineProperties(...)
.
Object.create n'est pas encore standard sur plusieurs navigateurs, par exemple IE8, Opera v11.5, Konq 4.3 ne l'ont pas. Vous pouvez utiliser la version d'Object.create de Douglas Crockford pour ces navigateurs, mais cela n'inclut pas le second paramètre 'objet d'initialisation' utilisé dans la réponse du CMS.
Pour le code de navigateur croisé, un moyen d'obtenir l'initialisation d'objet entre-temps consiste à personnaliser Object.create de Crockford. Voici une méthode: -
Object.build = function(o) {
var initArgs = Array.prototype.slice.call(arguments,1)
function F() {
if((typeof o.init === 'function') && initArgs.length) {
o.init.apply(this,initArgs)
}
}
F.prototype = o
return new F()
}
Cela conserve l'héritage prototypal de Crockford, vérifie également toute méthode init dans l'objet, puis l'exécute avec vos paramètres, comme par exemple new man ('John', 'Smith'). Votre code devient alors: -
MY_GLOBAL = {i: 1, nextId: function(){return this.i++}} // For example
var userB = {
init: function(nameParam) {
this.id = MY_GLOBAL.nextId();
this.name = nameParam;
},
sayHello: function() {
console.log('Hello '+ this.name);
}
};
var bob = Object.build(userB, 'Bob'); // Different from your code
bob.sayHello();
Donc, Bob hérite de la méthode sayHello et a maintenant ses propres propriétés id = 1 et name = 'Bob'. Ces propriétés sont bien sûr inscriptibles et énumérables. C’est également un moyen beaucoup plus simple d’initialisation que pour ECMA Object.create, en particulier si vous n'êtes pas concerné par les attributs accessible en écriture, énumérables et configurables.
Pour l'initialisation sans méthode init, le mod Crockford suivant peut être utilisé: -
Object.gen = function(o) {
var makeArgs = arguments
function F() {
var prop, i=1, arg, val
for(prop in o) {
if(!o.hasOwnProperty(prop)) continue
val = o[prop]
arg = makeArgs[i++]
if(typeof arg === 'undefined') break
this[prop] = arg
}
}
F.prototype = o
return new F()
}
Cela remplit les propriétés propres à l'utilisateurB, dans l'ordre dans lequel elles ont été définies, à l'aide des paramètres Object.gen de gauche à droite après le paramètre userB. Il utilise la boucle for (prop in o). Ainsi, selon les normes ECMA, l'ordre d'énumération des propriétés ne peut pas être garanti identique à celui de la définition de la propriété. Cependant, plusieurs exemples de code testés sur (4) les principaux navigateurs montrent qu'ils sont identiques, à condition que le filtre hasOwnProperty soit utilisé, et parfois même si ce n'est pas le cas.
MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}; // For example
var userB = {
name: null,
id: null,
sayHello: function() {
console.log('Hello '+ this.name);
}
}
var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());
Je dirais un peu plus simple que Object.build car userB n’a pas besoin de méthode init. De plus, userB n'est pas spécifiquement un constructeur mais ressemble à un objet singleton normal. Donc, avec cette méthode, vous pouvez construire et initialiser à partir d'objets simples ordinaires.
TL; DR:
new Computer()
appellera la fonction constructeur Computer(){}
une fois, alors que Object.create(Computer.prototype)
ne le fera pas.
Tous les avantages sont basés sur ce point.
Cependant, pour certaines raisons d’optimisation interne du moteur, Object.create
peut être plus lent, ce qui n’est pas intuitif.
Vous pouvez faire en sorte que la méthode init
renvoie this
, puis enchaîner les appels, comme suit:
var userB = {
init: function(nameParam) {
this.id = MY_GLOBAL.nextId();
this.name = nameParam;
return this;
},
sayHello: function() {
console.log('Hello '+ this.name);
}
};
var bob = Object.create(userB).init('Bob');
Une autre utilisation possible d’Object.create consiste à cloner des objets immuables d’une manière peu coûteuse et efficace .
var anObj = {
a: "test",
b: "jest"
};
var bObj = Object.create(anObj);
bObj.b = "gone"; // replace an existing (by masking prototype)
bObj.c = "brand"; // add a new to demonstrate it is actually a new obj
// now bObj is {a: test, b: gone, c: brand}
Notes: l'extrait de code ci-dessus crée un clone d'un objet source (autrement dit, pas une référence, comme dans cObj = aObj). Il bénéficie de la méthode copy-properties (voir 1 ), car il ne copie pas les propriétés de membre d'objet. Au lieu de cela, il crée un autre objet -destination- avec son prototype défini sur l'objet source. De plus, lorsque les propriétés sont modifiées sur l'objet dest, elles sont créées "à la volée", en masquant les propriétés du prototype (src). Ceci constitue un moyen rapide et efficace de cloner des objets immuables.
La mise en garde ici est que cela s'applique aux objets source qui ne doivent pas être modifiés après la création (immuable). Si l'objet source est modifié après la création, toutes les propriétés non masquées du clone seront également modifiées.
Fiddle here ( http://jsfiddle.net/y5b5q/1/ ) (nécessite un navigateur compatible Object.create).
Je pense que le point principal en question est de comprendre la différence entre les approches new
et Object.create
. En conséquence, les mots-clés this answer et this videonew
font les choses suivantes:
Crée un nouvel objet.
Lie le nouvel objet à la fonction constructeur (prototype
).
Fait que la variable this
pointe sur le nouvel objet.
Exécute la fonction constructeur à l'aide du nouvel objet et implicite perform return this
;
Attribue le nom de la fonction du constructeur à la propriété constructor
du nouvel objet.
Object.create
effectue uniquement les étapes 1st
et 2nd
!!!
Dans l'exemple de code fourni dans la question, ce n'est pas grave, mais dans l'exemple suivant, il s'agit de:
var onlineUsers = [];
function SiteMember(name) {
this.name = name;
onlineUsers.Push(name);
}
SiteMember.prototype.getName = function() {
return this.name;
}
function Guest(name) {
SiteMember.call(this, name);
}
Guest.prototype = new SiteMember();
var g = new Guest('James');
console.log(onlineUsers);
Comme effet secondaire, le résultat sera:
[ undefined, 'James' ]
en raison de Guest.prototype = new SiteMember();
Mais nous n'avons pas besoin d'exécuter la méthode du constructeur parent, nous avons seulement besoin de rendre la méthode getName
disponible dans Guest . Par conséquent, nous devons utiliser Object.create
.
Si remplacer Guest.prototype = new SiteMember();
à Guest.prototype = Object.create(SiteMember.prototype);
resultat:
[ 'James' ]
Parfois, vous ne pouvez pas créer d'objet avec NEW, mais vous pouvez toujours invoquer la méthode CREATE.
Par exemple, si vous souhaitez définir un élément personnalisé, il doit dériver de HTMLElement.
proto = new HTMLElement //fail :(
proto = Object.create( HTMLElement.prototype ) //OK :)
document.registerElement( "custom-element", { prototype: proto } )
L’avantage est que Object.create
est généralement plus lent que new
sur la plupart des navigateurs.
Dans cet exemple jsperf , dans un chrome, le navigateur new
est 30 fois plus rapide que Object.create(obj)
bien que les deux soient assez rapides. Tout cela est assez étrange car new fait plus de choses (comme invoquer un constructeur) où Object.create devrait simplement créer un nouvel objet avec le prototype passé dans l'objet (lien secret dans Crockford-speak)
Peut-être que les navigateurs ne sont pas prêts à rendre Object.create
plus efficace (peut-être le basent-ils sur new
sous les couvertures ... même en code natif)
Vous devez créer une fonction Object.create()
personnalisée. Celui qui répond aux préoccupations de Crockford et appelle également votre fonction init.
Cela fonctionnera:
var userBPrototype = {
init: function(nameParam) {
this.name = nameParam;
},
sayHello: function() {
console.log('Hello '+ this.name);
}
};
function UserB(name) {
function F() {};
F.prototype = userBPrototype;
var f = new F;
f.init(name);
return f;
}
var bob = UserB('bob');
bob.sayHello();
Ici, UserB est comme Object.create, mais ajusté à nos besoins.
Si vous le souhaitez, vous pouvez également appeler:
var bob = new UserB('bob');
Object.create()
est une fonction Javascript qui prend 2 arguments et retourne un nouvel objet. const proto = {
talk : () => console.log('hi')
}
const props = {
age: {
writable: true,
configurable: true,
value: 26
}
}
let Person = Object.create(proto, props)
console.log(Person.age);
Person.talk();
new
, vous n'avez aucun contrôle sur cela (vous pouvez toutefois les écraser).new
appelle une fonction constructeur. Avec Object.create()
il y a nul besoin d'invoquer ni même de déclarer une fonction constructeur.Alors que Douglas Crockford était un défenseur zélé de Object.create () et qu’il est essentiellement la raison pour laquelle cette construction est réellement en javascript, il n’a plus cette opinion.
Il a cessé d'utiliser Object.create, car il a complètement arrêté d'utiliser this, car il cause trop de problèmes. Par exemple, si vous ne faites pas attention, il peut facilement pointer sur l'objet global, ce qui peut avoir de très graves conséquences. Et il affirme que sans utiliser this Object.create n’a plus de sens.
Vous pouvez voir cette vidéo de 2014 où il parle à Nordic.js:
Je préfère une approche de fermeture.
J'utilise toujours new
. Je n'utilise pas Object.create
. Je n'utilise pas this
.
J'utilise toujours new
car j'aime sa nature déclarative.
Considérez ceci pour un héritage simple.
window.Quad = (function() {
function Quad() {
const wheels = 4;
const drivingWheels = 2;
function getWheelCount() {
return wheels;
}
function getDrivingWheelCount() {
return drivingWheels;
}
return Object.freeze({
getWheelCount,
getDrivingWheelCount
});
}
return Object.freeze(Quad);
})();
window.Car4wd = (function() {
function Car4wd() {
const quad = new Quad();
const spareWheels = 1;
const extraDrivingWheels = 2;
function getSpareWheelCount() {
return spareWheels;
}
function getDrivingWheelCount() {
return quad.getDrivingWheelCount() + extraDrivingWheels;
}
return Object.freeze(Object.assign({}, quad, {
getSpareWheelCount,
getDrivingWheelCount
}));
}
return Object.freeze(Car4wd);
})();
let myQuad = new Quad();
let myCar = new Car4wd();
console.log(myQuad.getWheelCount()); // 4
console.log(myQuad.getDrivingWheelCount()); // 2
console.log(myCar.getWheelCount()); // 4
console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called
console.log(myCar.getSpareWheelCount()); // 1
Commentaires encouragés.
new
et Object.create
servent des objectifs différents. new
est destiné à créer une nouvelle instance d'un type d'objet. Object.create
est conçu pour créer simplement un nouvel objet et définir son prototype. Pourquoi est-ce utile? Pour implémenter l'héritage sans accéder à la propriété __proto__
. Le prototype d'instance d'objet appelé [[Prototype]]
est une propriété interne de la machine virtuelle et n'est pas destiné à être directement accessible. La seule raison pour laquelle il est effectivement possible d'accéder directement à [[Prototype]]
en tant que propriété __proto__
est qu'elle a toujours été un standard de facto de l'implémentation d'ECMAScript de toutes les machines virtuelles majeures. À ce stade, le supprimer entraînerait la perte de nombreux codes existants.
En réponse à la réponse ci-dessus de 7ochem, les objets ne doivent absolument jamais avoir leur prototype défini comme le résultat d'une instruction new
, non seulement parce qu'il ne sert à rien d'appeler plusieurs fois le même constructeur de prototype, mais également parce que deux instances de la même classe peuvent aboutir avec un comportement différent si son prototype est modifié après avoir été créé. Les deux exemples sont simplement du mauvais code résultant d'une incompréhension et d'une rupture du comportement souhaité de la chaîne d'héritage prototype.
Au lieu d'accéder à __proto__
, le prototype d'une instance doit être écrit lorsqu'il est créé avec Object.create
ou après avec Object.setPrototypeOf
, et lu avec Object.getPrototypeOf
ou Object.isPrototypeOf
.
De plus, comme l'indique la la documentation Mozilla d'Object.setPrototypeOf , il est déconseillé de modifier le prototype d'un objet après sa création pour des raisons de performances, en plus du fait de modifier le prototype d'un objet après sa création. créé peut entraîner un comportement indéfini si un morceau de code donné y ayant accès peut être exécuté avant le OR après la modification du prototype, à moins que ce code ne soit très prudent pour vérifier le prototype actuel ou pour ne pas accéder à une propriété différente entre les deux .
Donnéconst X = function (v) { this.v = v };
X.prototype.whatAmI = 'X';
X.prototype.getWhatIAm = () => this.whatAmI;
X.prototype.getV = () => this.v;
le pseudo-code VM suivant est équivalent à l'instruction const x0 = new X(1);
:const x0 = {};
x0.[[Prototype]] = X.prototype;
X.prototype.constructor.call(x0, 1);
Remarque Bien que le constructeur puisse renvoyer n'importe quelle valeur, l'instruction new
ignore toujours sa valeur de retour et renvoie une référence au nouvel objet créé.
Et le pseudo-code suivant est équivalent à l'instruction const x1 = Object.create(X.prototype);
:const x0 = {};
x0.[[Prototype]] = X.prototype;
Comme vous pouvez le constater, la seule différence entre les deux réside dans le fait que Object.create
n'exécute pas le constructeur, qui peut en réalité renvoyer n'importe quelle valeur mais renvoie simplement la nouvelle référence d'objet this
, sauf indication contraire.
Maintenant, si nous voulions créer une sous-classe Y avec la définition suivante:const Y = function(u) { this.u = u; }
Y.prototype.whatAmI = 'Y';
Y.prototype.getU = () => this.u;
Ensuite, nous pouvons le faire hériter de X comme ceci en écrivant dans __proto__
:Y.prototype.__proto__ = X.prototype;
Bien que la même chose puisse être accomplie sans jamais écrire à __proto__
avec:Y.prototype = Object.create(X.prototype);
Y.prototype.constructor = Y;
Dans ce dernier cas, il est nécessaire de définir la propriété constructeur du prototype pour que le constructeur correct soit appelé par l'instruction new Y
, sinon new Y
appellera la fonction X
. Si le programmeur veut que new Y
appelle X
, cela serait plus correctement fait dans le constructeur de Y avec X.call(this, u)