web-dev-qa-db-fra.com

Utiliser "Object.create" au lieu de "new"

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'?

346
Graham King

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.

236
CMS

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(...) .

46
Noel Abrahams

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.

41
John

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.

19
Nami WANG

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');
13
samfrances

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).

7
basos

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:

  1. Crée un nouvel objet.

  2. Lie le nouvel objet à la fonction constructeur (prototype).

  3. Fait que la variable this pointe sur le nouvel objet.

  4. Exécute la fonction constructeur à l'aide du nouvel objet et implicite perform return this;

  5. 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' ]
6
Vladimir Kovpak

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 } )
5
Supersharp

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)

3
frooble

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');
2
edwin

Résumé:

  • Object.create() est une fonction Javascript qui prend 2 arguments et retourne un nouvel objet. 
  • Le premier argument est un objet qui sera le prototype de l'objet nouvellement créé
  • Le second argument est un objet qui sera les propriétés du nouvel objet créé

Exemple:

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();

Applications pratiques:

  1. Le principal avantage de créer un objet de cette manière est que le prototype peut être défini explicitement. Lorsque vous utilisez un littéral d'objet ou le mot clé new, vous n'avez aucun contrôle sur cela (vous pouvez toutefois les écraser).
  2. Si nous voulons avoir un prototype, le mot clé new appelle une fonction constructeur. Avec Object.create() il y a nul besoin d'invoquer ni même de déclarer une fonction constructeur.
  3. Cela peut fondamentalement être un outil utile lorsque vous souhaitez créer des objets de manière très dynamique. Nous pouvons créer une fonction de fabrique d'objets qui crée des objets avec différents prototypes en fonction des arguments reçus.
1
Willem van der Veen

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:

https://www.youtube.com/watch?v=PSGEjv3Tqo0

 enter image description here

0
Vojtech Ruzicka

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.

0
p0wdr.com

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)

0
paulyc