Comprendre la différence entre Object.create () et le nouveau SomeFunction ()

392

J'ai récemment découvert la Object.create()méthode en JavaScript, et j'essaie de déduire en quoi elle est différente de la création d'une nouvelle instance d'un objet avec new SomeFunction(), et quand vous souhaitez utiliser l'une par rapport à l'autre.

Prenons l'exemple suivant:

var test = {
  val: 1,
  func: function() {
    return this.val;
  }
};
var testA = Object.create(test);

testA.val = 2;
console.log(test.func()); // 1
console.log(testA.func()); // 2

console.log('other test');
var otherTest = function() {
  this.val = 1;
  this.func = function() {
    return this.val;
  };
};

var otherTestA = new otherTest();
var otherTestB = new otherTest();
otherTestB.val = 2;
console.log(otherTestA.val); // 1 
console.log(otherTestB.val); // 2

console.log(otherTestA.func()); // 1
console.log(otherTestB.func()); // 2

Notez que le même comportement est observé dans les deux cas. Il me semble que les principales différences entre ces deux scénarios sont:

  • L'objet utilisé dans Object.create()en fait le prototype du nouvel objet, tandis que dans les new Function()propriétés / fonctions déclarées ne forment pas le prototype.
  • Vous ne pouvez pas créer de fermetures avec la Object.create()syntaxe comme vous le feriez avec la syntaxe fonctionnelle. Ceci est logique étant donné la portée de type lexical (vs bloc) de JavaScript.

Les déclarations ci-dessus sont-elles correctes? Et est-ce que je manque quelque chose? Quand utiliseriez-vous l'un sur l'autre?

EDIT: lien vers la version jsfiddle de l'exemple de code ci-dessus: http://jsfiddle.net/rZfYL/

Mat
la source

Réponses:

246

L'objet utilisé dans Object.create forme en fait le prototype du nouvel objet, où comme dans le nouveau formulaire Function () les propriétés / fonctions déclarées ne forment pas le prototype.

Oui, Object.createconstruit un objet qui hérite directement de celui passé comme premier argument.

Avec les fonctions constructeur, l'objet nouvellement créé hérite du prototype du constructeur, par exemple:

var o = new SomeConstructor();

Dans l'exemple ci-dessus, ohérite directement de SomeConstructor.prototype.

Il y a une différence ici, avec Object.createvous pouvez créer un objet qui n'hérite de rien, d' Object.create(null);autre part, si vous définissez SomeConstructor.prototype = null;l'objet nouvellement créé héritera de Object.prototype.

Vous ne pouvez pas créer de fermetures avec la syntaxe Object.create comme vous le feriez avec la syntaxe fonctionnelle. Ceci est logique étant donné la portée de type lexical (vs bloc) de JavaScript.

Eh bien, vous pouvez créer des fermetures, par exemple en utilisant l'argument des descripteurs de propriété:

var o = Object.create({inherited: 1}, {
  foo: {
    get: (function () { // a closure
      var closured = 'foo';
      return function () {
        return closured+'bar';
      };
    })()
  }
});

o.foo; // "foobar"

Notez que je parle de la Object.createméthode ECMAScript 5th Edition , pas de la cale de Crockford.

La méthode commence à être implémentée nativement sur les derniers navigateurs, consultez ce tableau de compatibilité .

CMS
la source
2
@CMS 2 questions. 1) La chaîne de portée sur Object.create (null) se termine-t-elle toujours à la portée globale (telle que «fenêtre» dans un navigateur), ou se termine-t-elle sur elle-même? 2) Il n'est toujours pas clair pour moi pourquoi Object.create a été introduit (par exemple, quelle fonction manquait que cela corrigeait?) Et pourquoi on l'utiliserait à la place de new Function ();
Matt
9
@Matt, 1) la chaîne de portée n'est pas vraiment un concept connexe ici, la chaîne de portée est liée à la résolution de l'identifiant , par exemple: comment foo;est résolu dans l' environnement lexical actuel . 2) Pour fournir un moyen facile d'implémenter l'héritage, c'est une construction vraiment puissante. IMO Je l'utiliserais parce qu'il est vraiment simple et léger, mais pour le code de production, nous devons encore attendre un certain temps avant que ES5 ne soit largement pris en charge. A propos des fonctionnalités manquantes, le fait de créer un objet "vierge", Object.create(null);manquait, il est vraiment utile d'implémenter des objets fiables de type table de hachage ...
CMS
@CMS Merci. Donc, simplement lorsque vous créez un objet en utilisant 'Object.create', vous avez la possibilité de sélectionner l'objet qui devrait être son prototype.
Anshul
@CMS OK, cela Object.create(null)signifie donc que vous n'avez pas à utiliser de hasOwnProperty()conneries lors de l'itération car il n'en hérite pas ??? J'aime ça - merci. Bien sûr, tout le monde va encore le faire hasOwnPropertycar tout le monde n'utilisera pas, Object.create(null)donc je ne suis pas sûr que ce soit un réel avantage ... Jusqu'à présent, j'ai trouvé les autres "avantages" de Object.create()complètement peu convaincants.
user949300
425

Très simplement, new Xc'est Object.create(X.prototype)en plus d'exécuter la constructorfonction. (Et donner la constructorchance à returnl'objet réel qui devrait être le résultat de l'expression au lieu de this.)

C'est ça. :)

Les autres réponses sont tout simplement déroutantes, car apparemment personne d'autre ne lit la définition de l'une newou l'autre. ;)

Evi1M4chine
la source
23
+1 Simplicité et clarté! (Bien que l'Object.create (null) semble une bonne option - devrait peut-être le mentionner).
user949300
rester simple c'est la voie à suivre
Projet de loi
Cela laisse juste la question de "attendre, donc les fonctions ont aussi des prototypes ? Quelle est la relation entre celles-ci et les prototypes d' objets ?"
Qwertie
3
@Qwertie: Dans JS, tout est un objet. :) Ils l'ont copié de Java, qui l'a copié de SmallTalk, qui est allé jusqu'au bout avec. C'est un joli cas «d'émergence», qui facilite la vie en général.
Evi1M4chine
@ Evi1M4chine en fait en Java, les fonctions ne sont pas des objets (et non plus des primitives, d'ailleurs) ... et les objets n'ont pas de prototypes, donc la comparaison semble peu appropriée. Le fait que JS fonctionne différemment des autres langages OO populaires est une source majeure de confusion (et cela n'aide pas que les navigateurs ne fournissent pas un moyen facile de visualiser le réseau d'objets, y compris les fonctions et les prototypes). PS J'ai trouvé ce lien utile: davidwalsh.name/javascript-objects-deconstruction
Qwertie
204

Voici les étapes qui se produisent en interne pour les deux appels:
(indice: la seule différence est à l'étape 3)


new Test():

  1. créer new Object()obj
  2. réglé obj.__proto__surTest.prototype
  3. return Test.call(obj) || obj; // normally obj is returned but constructors in JS can return a value

Object.create( Test.prototype )

  1. créer new Object()obj
  2. réglé obj.__proto__surTest.prototype
  3. return obj;

Donc, en gros, Object.createn'exécute pas le constructeur.

Ray Hulha
la source
@Ray donc en utilisant object.create nous avons la police les propriétés de fonction mentionnées dans la fonction constructeur?
@sortednoun tant que les propriétés sont privées et non spécifiées sur le prototype, oui, elles ne seront pas héritées et vous ne les aurez pas dans le nouvel objet (et, j'ajouterais, vous pouvez vous attendre à obtenir des propriétés prototypées éventuelles du parent, juste au moment où le constructeur parent a été exécuté au moins une fois).
Kamafeather
Comme avec la plupart des fonctions constructeurs, les méthodes sont définies dans l'objet renvoyé, a newessentiellement toutes les fonctions dupliquées, tandis Object.createque non.
SparK
61

Permettez-moi d'essayer d'expliquer (plus sur Blog ):

  1. Lorsque vous écrivez Carconstructeur var Car = function(){}, voici comment les choses se passent en interne: Un diagramme des chaînes prototypiques lors de la création d'objets javascript nous avons un {prototype}lien caché vers Function.prototypelequel n'est pas accessible et un prototypelien vers Car.prototypelequel est accessible et a un réel constructorde Car. Function.prototype et Car.prototype ont des liens cachés vers Object.prototype.
  2. Lorsque nous voulons créer deux objets équivalents en utilisant l' newopérateur et la createméthode, nous devons le faire comme ceci: Honda = new Car();et Maruti = Object.create(Car.prototype). Un diagramme de chaînes prototypiques pour différentes méthodes de création d'objets Qu'est-ce qui se passe?

    Honda = new Car();- Lorsque vous créez un objet comme celui-ci, la {prototype}propriété masquée est pointée Car.prototype. Donc, ici, l' {prototype}objet de Honda sera toujours Car.prototype- nous n'avons aucune option pour changer la {prototype}propriété de l'objet. Et si je veux changer le prototype de notre objet nouvellement créé?
    Maruti = Object.create(Car.prototype)- Lorsque vous créez un objet comme celui-ci, vous avez une option supplémentaire pour choisir la {prototype}propriété de votre objet . Si vous souhaitez que Car.prototype soit le, {prototype}passez-le en tant que paramètre dans la fonction. Si vous ne voulez pas {prototype}alors pour votre objet , vous pouvez passer nullcomme ceci: Maruti = Object.create(null).

Conclusion - En utilisant la méthode, Object.createvous avez la liberté de choisir la {prototype}propriété de votre objet . En new Car();, vous n'avez pas cette liberté.

Manière préférée dans OO JavaScript:

Supposons que nous ayons deux objets aet b.

var a = new Object();
var b = new Object();

Supposons maintenant que acertaines méthodes bsouhaitent également accéder. Pour cela, nous avons besoin de l'héritage des objets ( ane devrait être le prototype bque si nous voulons accéder à ces méthodes). Si nous vérifions les prototypes aet bnous découvrirons qu'ils partagent le prototype Object.prototype.

Object.prototype.isPrototypeOf(b); //true
a.isPrototypeOf(b); //false (the problem comes into the picture here).

Problème - nous voulons aque l' objet soit le prototype de b, mais ici nous avons créé un objet bavec le prototype Object.prototype. Solution - ECMAScript 5 introduit Object.create(), pour réaliser facilement un tel héritage. Si nous créons un objet bcomme celui-ci:

var b = Object.create(a);

puis,

a.isPrototypeOf(b);// true (problem solved, you included object a in the prototype chain of object b.)

Donc, si vous faites des scripts orientés objet, cela Object.create()est très utile pour l'héritage.

Anshul
la source
Donc, c'est quelque peu similaire à la création d'objets sans invocation de constructeur? Nous profiterons de tous les avantages de la classe. La classe obj instanceof sera également vraie. Mais nous n'invoquons pas la fonction Class via new.
Praveen
@Anshul Vous avez dit que a.isPrototypeOf(b);cela reviendra, falsece qui est vrai, car les deux objets sont différents et pointent vers une mémoire différente. La bonne façon de le faire avec l' newopérateur est ici. - jsfiddle.net/167onunp .
Sagar Karira
Pourquoi ne définiriez-vous pas simplement la propriété prototype de b sur a, au lieu de le faire?
Amnestic
J'ai aussi aimé l'article sur votre blog. M'a aidé à mieux comprendre le concept. Je vous remercie.
regular_daddy
1
La conclusion en dit long.
kushalvm
44

Cette:

var foo = new Foo();

et

var foo = Object.create(Foo.prototype);

sont assez similaires. Une différence importante est que new Foole code constructeur s'exécute réellement, alors Object.createqu'il n'exécutera pas de code tel que

function Foo() {
    alert("This constructor does not run with Object.create");
}

Notez que si vous utilisez la version à deux paramètres de, Object.create()vous pouvez faire des choses beaucoup plus puissantes.

Leopd
la source
1
Grande explication. Puis-je ajouter que l'utilisation Object.createdans sa forme la plus simple comme celle-ci vous permet d'omettre les fonctions constructeur de votre code tout en profitant de l'héritage du prototype.
Ricky Boyce
23

La différence est ce que l'on appelle «l'hérédité pseudoclassique vs prototypique». La suggestion est d'utiliser un seul type dans votre code, sans mélanger les deux.

Dans l'héritage pseudoclassique (avec "nouvel" opérateur), imaginez que vous définissez d'abord une pseudo-classe, puis créez des objets à partir de cette classe. Par exemple, définissez une pseudo-classe "Personne", puis créez "Alice" et "Bob" à partir de "Personne".

Dans l'héritage prototypique (en utilisant Object.create), vous créez directement une personne spécifique "Alice", puis créez une autre personne "Bob" en utilisant "Alice" comme prototype. Il n'y a pas de "classe" ici; tous sont des objets.

En interne, JavaScript utilise "l'héritage prototypique"; la manière "pseudoclassique" est juste du sucre.

Voir ce lien pour une comparaison des deux façons.

user1931858
la source
21
function Test(){
    this.prop1 = 'prop1';
    this.prop2 = 'prop2';
    this.func1 = function(){
        return this.prop1 + this.prop2;
    }
};

Test.prototype.protoProp1 = 'protoProp1';
Test.prototype.protoProp2 = 'protoProp2';
var newKeywordTest = new Test();
var objectCreateTest = Object.create(Test.prototype);

/* Object.create   */
console.log(objectCreateTest.prop1); // undefined
console.log(objectCreateTest.protoProp1); // protoProp1 
console.log(objectCreateTest.__proto__.protoProp1); // protoProp1

/* new    */
console.log(newKeywordTest.prop1); // prop1
console.log(newKeywordTest.__proto__.protoProp1); // protoProp1

Sommaire:

1) avec le newmot-clé il y a deux choses à noter;

a) la fonction est utilisée comme constructeur

b) l' function.prototypeobjet est passé à la __proto__propriété ... ou là où il __proto__n'est pas pris en charge, c'est le deuxième endroit où le nouvel objet cherche à trouver des propriétés

2) avec Object.create(obj.prototype)vous construisez un objet ( obj.prototype) et passez-le à l'objet prévu .. avec la différence que maintenant le nouvel objet __proto__pointe également vers obj.prototype (veuillez vous reporter à xj9 pour cela)

user3124360
la source
15

Variantes de création d'objets.


Variante 1 : ' new Object () ' -> Constructeur d'objet sans arguments.

var p1 = new Object(); // 'new Object()' create and return empty object -> {}

var p2 = new Object(); // 'new Object()' create and return empty object -> {}

console.log(p1); // empty object -> {}

console.log(p2); // empty object -> {}

// p1 and p2 are pointers to different objects
console.log(p1 === p2); // false

console.log(p1.prototype); // undefined

// empty object which is in fact Object.prototype
console.log(p1.__proto__); // {}

// empty object to which p1.__proto__ points
console.log(Object.prototype); // {}

console.log(p1.__proto__ === Object.prototype); // true

// null, which is in fact Object.prototype.__proto__
console.log(p1.__proto__.__proto__); // null

console.log(Object.prototype.__proto__); // null

entrez la description de l'image ici


Variante 2 : ' nouvel objet (personne) ' -> Constructeur d'objet avec argument.

const person = {
    name: 'no name',
    lastName: 'no lastName',
    age: -1
}

// 'new Object(person)' return 'person', which is pointer to the object ->
//  -> { name: 'no name', lastName: 'no lastName', age: -1 }
var p1 = new Object(person);

// 'new Object(person)' return 'person', which is pointer to the object ->
//  -> { name: 'no name', lastName: 'no lastName', age: -1 }
var p2 = new Object(person);

// person, p1 and p2 are pointers to the same object
console.log(p1 === p2); // true
console.log(p1 === person); // true
console.log(p2 === person); // true

p1.name = 'John'; // change 'name' by 'p1'
p2.lastName = 'Doe'; // change 'lastName' by 'p2'
person.age = 25; // change 'age' by 'person'

// when print 'p1', 'p2' and 'person', it's the same result,
// because the object they points is the same
console.log(p1); // { name: 'John', lastName: 'Doe', age: 25 }
console.log(p2); // { name: 'John', lastName: 'Doe', age: 25 }
console.log(person); // { name: 'John', lastName: 'Doe', age: 25 }

entrez la description de l'image ici


Variante 3.1 : « Object.create (person) ». Utilisez Object.create avec un simple objet 'personne'. 'Object.create (person)' va créer (et retourner) un nouvel objet vide et ajouter la propriété '__proto__' au même nouvel objet vide. Cette propriété '__proto__' pointera vers l'objet 'personne'.

const person = {
        name: 'no name',
        lastName: 'no lastName',
        age: -1,
        getInfo: function getName() {
           return `${this.name} ${this.lastName}, ${this.age}!`;
    }
}

var p1 = Object.create(person);

var p2 = Object.create(person);

// 'p1.__proto__' and 'p2.__proto__' points to
// the same object -> 'person'
// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(p1.__proto__);
console.log(p2.__proto__);
console.log(p1.__proto__ === p2.__proto__); // true

console.log(person.__proto__); // {}(which is the Object.prototype)

// 'person', 'p1' and 'p2' are different
console.log(p1 === person); // false
console.log(p1 === p2); // false
console.log(p2 === person); // false

// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(person);

console.log(p1); // empty object - {}

console.log(p2); // empty object - {}

// add properties to object 'p1'
// (properties with the same names like in object 'person')
p1.name = 'John';
p1.lastName = 'Doe';
p1.age = 25;

// add properties to object 'p2'
// (properties with the same names like in object 'person')
p2.name = 'Tom';
p2.lastName = 'Harrison';
p2.age = 38;

// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(person);

// { name: 'John', lastName: 'Doe', age: 25 }
console.log(p1);

// { name: 'Tom', lastName: 'Harrison', age: 38 }
console.log(p2);

// use by '__proto__'(link from 'p1' to 'person'),
// person's function 'getInfo'
console.log(p1.getInfo()); // John Doe, 25!

// use by '__proto__'(link from 'p2' to 'person'),
// person's function 'getInfo'
console.log(p2.getInfo()); // Tom Harrison, 38!

entrez la description de l'image ici


Variante 3.2 : « Object.create (Object.prototype) ». Utilisez Object.create avec un objet intégré -> 'Object.prototype'. 'Object.create (Object.prototype)' va créer (et retourner) un nouvel objet vide et ajouter la propriété '__proto__' au même nouvel objet vide. Cette propriété '__proto__' pointera vers l'objet 'Object.prototype'.

// 'Object.create(Object.prototype)' :
// 1. create and return empty object -> {}.
// 2. add to 'p1' property '__proto__', which is link to 'Object.prototype'
var p1 = Object.create(Object.prototype);

// 'Object.create(Object.prototype)' :
// 1. create and return empty object -> {}.
// 2. add to 'p2' property '__proto__', which is link to 'Object.prototype'
var p2 = Object.create(Object.prototype);

console.log(p1); // {}

console.log(p2); // {}

console.log(p1 === p2); // false

console.log(p1.prototype); // undefined

console.log(p2.prototype); // undefined

console.log(p1.__proto__ === Object.prototype); // true

console.log(p2.__proto__ === Object.prototype); // true

entrez la description de l'image ici


Variante 4 : ' new SomeFunction () '

// 'this' in constructor-function 'Person'
// represents a new instace,
// that will be created by 'new Person(...)'
// and returned implicitly
function Person(name, lastName, age) {

    this.name = name;
    this.lastName = lastName;
    this.age = age;

    //-----------------------------------------------------------------
    // !--- only for demonstration ---
    // if add function 'getInfo' into
    // constructor-function 'Person',
    // then all instances will have a copy of the function 'getInfo'!
    //
    // this.getInfo: function getInfo() {
    //  return this.name + " " + this.lastName + ", " + this.age + "!";
    // }
    //-----------------------------------------------------------------
}

// 'Person.prototype' is an empty object
// (before add function 'getInfo')
console.log(Person.prototype); // Person {}

// With 'getInfo' added to 'Person.prototype',
// instances by their properties '__proto__',
// will have access to the function 'getInfo'.
// With this approach, instances not need
// a copy of the function 'getInfo' for every instance.
Person.prototype.getInfo = function getInfo() {
    return this.name + " " + this.lastName + ", " + this.age + "!";
}

// after function 'getInfo' is added to 'Person.prototype'
console.log(Person.prototype); // Person { getInfo: [Function: getInfo] }

// create instance 'p1'
var p1 = new Person('John', 'Doe', 25);

// create instance 'p2'
var p2 = new Person('Tom', 'Harrison', 38);

// Person { name: 'John', lastName: 'Doe', age: 25 }
console.log(p1);

// Person { name: 'Tom', lastName: 'Harrison', age: 38 }
console.log(p2);

// 'p1.__proto__' points to 'Person.prototype'
console.log(p1.__proto__); // Person { getInfo: [Function: getInfo] }

// 'p2.__proto__' points to 'Person.prototype'
console.log(p2.__proto__); // Person { getInfo: [Function: getInfo] }

console.log(p1.__proto__ === p2.__proto__); // true

// 'p1' and 'p2' points to different objects(instaces of 'Person')
console.log(p1 === p2); // false

// 'p1' by its property '__proto__' reaches 'Person.prototype.getInfo' 
// and use 'getInfo' with 'p1'-instance's data
console.log(p1.getInfo()); // John Doe, 25!

// 'p2' by its property '__proto__' reaches 'Person.prototype.getInfo' 
// and use 'getInfo' with 'p2'-instance's data
console.log(p2.getInfo()); // Tom Harrison, 38!

entrez la description de l'image ici

Ted
la source
Beau résumé. Merci. Cela m'a aidé aujourd'hui !!
Anandaraja_Srinivasan
11

En interne, Object.createcela:

Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
};

La syntaxe enlève juste l'illusion que JavaScript utilise l'héritage classique.

xj9
la source
25
La Object.createméthode ECMAScript 5 fait beaucoup plus que cela, vous pouvez définir des propriétés par des descripteurs de propriétés et vous pouvez créer un objet qui n'hérite de rien ( Object.create(null);), ce type de shims doit être évité car vous ne pouvez pas vraiment émuler cela comportement sur ES3. Plus d'infos
CMS
D'accord avec @CMS mais en général, c'est du polyfill simple pour Object.create.
V.Kovpak
10

En conséquence, cette réponse et ce new mot clé vidéo font les choses suivantes:

  1. Crée un nouvel objet.

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

  3. Fait thispointer la variable vers le nouvel objet.

  4. Exécute la fonction constructeur en utilisant le nouvel objet et la performance implicite return this;

  5. Attribue le nom de la fonction constructeur à la propriété du nouvel objet constructor.

Object.createeffectue seulement 1stet 2ndétapes !!!

V. Kovpak
la source