Comprensione della differenza tra Object.create () e il nuovo SomeFunction ()


392

Di recente mi sono imbattuto nel Object.create()metodo in JavaScript e sto cercando di dedurre in che modo è diverso dalla creazione di una nuova istanza di un oggetto new SomeFunction()e quando vorresti usarne uno sopra l'altro.

Considera il seguente esempio:

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

Si noti che lo stesso comportamento si osserva in entrambi i casi. Mi sembra che le differenze principali tra questi due scenari siano:

  • L'oggetto utilizzato in Object.create()realtà costituisce il prototipo del nuovo oggetto, mentre nelle new Function()proprietà / funzioni dichiarate non si forma il prototipo.
  • Non è possibile creare chiusure con la Object.create()sintassi come si farebbe con la sintassi funzionale. Ciò è logico dato l'ambito lessicale del tipo lessicale (vs block) di JavaScript.

Le affermazioni di cui sopra sono corrette? E mi sto perdendo qualcosa? Quando useresti uno sopra l'altro?

EDIT: collegamento alla versione jsfiddle dell'esempio di codice precedente: http://jsfiddle.net/rZfYL/


Risposte:


246

L'oggetto utilizzato in Object.create in realtà costituisce il prototipo del nuovo oggetto, dove come nella nuova funzione () formano le proprietà / funzioni dichiarate non formano il prototipo.

Sì, Object.createcrea un oggetto che eredita direttamente da quello passato come primo argomento.

Con le funzioni di costruzione, l'oggetto appena creato eredita dal prototipo del costruttore, ad esempio:

var o = new SomeConstructor();

Nell'esempio sopra, oeredita direttamente da SomeConstructor.prototype.

C'è una differenza qui, con Object.createte puoi creare un oggetto che non eredita da nulla Object.create(null);, d'altra parte, se imposti SomeConstructor.prototype = null;l'oggetto appena creato erediterà Object.prototype.

Non è possibile creare chiusure con la sintassi Object.create come si farebbe con la sintassi funzionale. Ciò è logico dato l'ambito lessicale del tipo lessicale (vs block) di JavaScript.

Bene, puoi creare chiusure, ad esempio usando l'argomento descrittori di proprietà:

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

o.foo; // "foobar"

Si noti che sto parlando del Object.createmetodo ECMAScript 5th Edition , non dello spessore di Crockford.

Il metodo sta iniziando a essere implementato in modo nativo sugli ultimi browser, controlla questa tabella di compatibilità .


2
@CMS 2 domande. 1) La catena dell'ambito su Object.create (null) termina ancora nell'ambito globale (come "finestra" in un browser) o termina su se stessa? 2) Non mi è ancora chiaro il motivo per cui Object.create è stato introdotto (ad es. Quale caratteristica mancava che questa fosse indirizzata?) E perché si dovrebbe usarlo al posto della nuova funzione ();
Matt,

9
@Matt, 1) la catena dell'ambito non è in realtà un concetto correlato qui, la catena dell'ambito è correlata alla risoluzione dell'identificatore , ad esempio: come foo;viene risolto nell'attuale ambiente lessicale . 2) Per fornire un modo semplice per implementare l'ereditarietà, è un costrutto davvero potente. IMO lo userei perché è davvero semplice e leggero, ma per il codice di produzione, dobbiamo ancora aspettare un po 'di tempo prima che ES5 sia ampiamente supportato. Sulle caratteristiche mancanti, sul fatto di creare un oggetto "incontaminato", Object.create(null);mancava, è davvero utile implementare oggetti affidabili come una tabella hash ...
CMS

@CMS Grazie. Quindi semplicemente quando crei un oggetto usando 'Object.create', hai la possibilità di selezionare l'oggetto che dovrebbe essere il suo prototipo.
Anshul,

@CMS OK, quindi Object.create(null)significa che non devi usare hasOwnProperty()merda durante l'iterazione perché non eredita nessuno ??? Mi piace, grazie. Certo, lo faranno ancora tutti hasOwnPropertypoiché non tutti lo useranno, Object.create(null)quindi non sono sicuro che sia un vero vantaggio ... Finora ho trovato gli altri "benefici" del Object.create()tutto non convincenti.
user949300

425

Detto in parole povere, new Xè anche Object.create(X.prototype)con l'esecuzione della constructorfunzione. (E dare la constructorpossibilità returnall'oggetto reale che dovrebbe essere il risultato dell'espressione anziché this.)

Questo è tutto. :)

Il resto delle risposte è solo confuso, perché apparentemente nessun altro legge la definizione di newnessuno dei due. ;)


23
+1 Semplicità e chiarezza! (Anche se Object.create (null) sembra una buona opzione, forse dovrei menzionarlo).
user949300

mantieni la strada semplice
Bill

Che lascia solo la questione del "wait, quindi le funzioni hanno prototipi troppo ? Qual è il rapporto tra questi e oggetti prototipi?"
Qwertie,

3
@Qwertie: in JS, tutto è un oggetto. :) Lo copiarono da Java, che lo copiò da SmallTalk, che arrivò fino in fondo con esso. È un bel caso di "emergenza", che semplifica la vita in generale.
Evi1M4chine,

@ Evi1M4chine in realtà in Java, le funzioni non sono oggetti (e nemmeno sono primitivi, del resto) ... e gli oggetti non hanno prototipi, quindi il confronto sembra inadatto. Il fatto che JS funzioni in modo diverso rispetto ad altri linguaggi OO popolari è una fonte di grande confusione (e non aiuta i browser a non fornire un modo semplice per visualizzare la rete di oggetti tra cui funzioni e prototipi). PS Ho trovato utile questo link: davidwalsh.name/javascript-objects-deconstruction
Qwertie

204

Ecco i passaggi che avvengono internamente per entrambe le chiamate:
(Suggerimento: l'unica differenza è nel passaggio 3)


new Test():

  1. creare new Object()obj
  2. impostato obj.__proto__suTest.prototype
  3. return Test.call(obj) || obj; // normally obj is returned but constructors in JS can return a value

Object.create( Test.prototype )

  1. creare new Object()obj
  2. impostato obj.__proto__suTest.prototype
  3. return obj;

Quindi sostanzialmente Object.createnon esegue il costruttore.


@Ray quindi usando object.create abbiamo i caratteri della funzione menzionati nella funzione di costruzione?

@sortednoun fintanto che le proprietà sono private e non specificate sul prototipo, sì, non saranno ereditate e non le avrai nel nuovo oggetto (e, aggiungerei, puoi aspettarti di ottenere eventuali proprietà prototipate dal genitore, proprio quando il costruttore genitore è stato eseguito almeno una volta).
Kamafeather,

Come con la maggior parte delle funzioni di costruzione, i metodi sono definiti all'interno dell'oggetto restituito, newsostanzialmente ha tutte le funzioni duplicate, mentre Object.createnon lo è.
SparK

61

Vorrei provare a spiegare (altro sul Blog ):

  1. Quando si scrive Caril costruttore var Car = function(){}, questo è come stanno le cose internamente: Un diagramma delle catene prototipali durante la creazione di oggetti javascript Abbiamo un {prototype}link nascosto a Function.prototypeche non è accessibile e un prototypelink per Car.prototypeche è accessibile e dispone di un vero e proprio constructordi Car. Sia Function.prototype che Car.prototype hanno collegamenti nascosti a Object.prototype.
  2. Quando vogliamo creare due oggetti equivalenti usando l' newoperatore e il createmetodo, allora dobbiamo farlo in questo modo: Honda = new Car();e Maruti = Object.create(Car.prototype). Un diagramma di catene prototipali per diversi metodi di creazione di oggetti Che cosa sta succedendo?

    Honda = new Car();- Quando si crea un oggetto come questo {prototype}, viene indicata la proprietà nascosta Car.prototype. Quindi qui l' {prototype}oggetto Honda sarà sempre Car.prototype- non abbiamo alcuna opzione per cambiare la {prototype}proprietà dell'oggetto. E se volessi cambiare il prototipo del nostro oggetto appena creato?
    Maruti = Object.create(Car.prototype)- Quando crei un oggetto come questo hai un'opzione extra per scegliere la {prototype}proprietà del tuo oggetto . Se si desidera Car.prototype come il {prototype}quindi passarlo come parametro nella funzione. Se non si desidera alcuna {prototype}per l'oggetto, allora si può passare nullin questo modo: Maruti = Object.create(null).

Conclusione: utilizzando il metodo Object.createsi ha la libertà di scegliere la {prototype}proprietà dell'oggetto . In new Car();, non hai quella libertà.

Modo preferito in OO JavaScript:

Supponiamo di avere due oggetti ae b.

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

Supponiamo ora che aabbia alcuni metodi a cui bdesideri accedere. Per questo, abbiamo bisogno dell'ereditarietà degli oggetti ( adovrebbe essere il prototipo bsolo se vogliamo accedere a questi metodi). Se controlliamo i prototipi di ae bpoi scopriremo che condividono il prototipo Object.prototype.

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

Problema: vogliamo l'oggetto acome prototipo b, ma qui abbiamo creato l'oggetto bcon il prototipo Object.prototype. Soluzione - Introduzione di ECMAScript 5 Object.create(), per ottenere facilmente tale eredità. Se creiamo un oggetto bcome questo:

var b = Object.create(a);

poi,

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

Quindi, se stai eseguendo script orientati agli oggetti, allora Object.create()è molto utile per l'ereditarietà.


Quindi, è in qualche modo simile alla creazione di oggetti senza invocazione del costruttore? Godremo di tutti i vantaggi della classe. Anche l'istanza di obj Class sarà vera. Ma non stiamo invocando la funzione Class tramite new.
Praveen,

@Anshul Hai detto che a.isPrototypeOf(b);tornerà falseche è giusto, perché entrambi gli oggetti sono diversi e puntano a memoria diversa. Il modo corretto per farlo con l' newoperatore è qui. - jsfiddle.net/167onunp .
Sagar Karira,

Perché non dovresti semplicemente impostare la proprietà prototipo di b su a, invece di farlo?
Amnestic,

Mi è piaciuto anche l'articolo sul tuo blog. Mi ha aiutato a capire il concetto molto meglio. Grazie.
steady_daddy,

1
La conclusione dice tutto.
kushalvm,

44

Questo:

var foo = new Foo();

e

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

sono abbastanza simili. Una differenza importante è che new Fooesegue effettivamente il codice del costruttore, mentre Object.createnon eseguirà codice come

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

Nota che se usi la versione a due parametri di Object.create()allora puoi fare cose molto più potenti.


1
Ottima spiegazione Potrei aggiungere, usando Object.createnella sua forma più semplice come questa ti permette di omettere le funzioni di costruzione dal tuo codice sfruttando l'eredità del prototipo.
Ricky Boyce,

23

La differenza è la cosiddetta "eredità pseudoclassica vs. prototipale". Il suggerimento è di usare solo un tipo nel tuo codice, non mescolare i due.

In eredità pseudoclassica (con "nuovo" operatore), immagina di definire prima una pseudo-classe e quindi creare oggetti da quella classe. Ad esempio, definire una "Persona" pseudo-classe, quindi creare "Alice" e "Bob" da "Persona".

In eredità prototipo (usando Object.create), si crea direttamente una persona specifica "Alice", quindi si crea un'altra persona "Bob" usando "Alice" come prototipo. Non esiste una "classe" qui; tutti sono oggetti.

Internamente JavaScript utilizza "eredità prototipale"; il modo "pseudoclassico" è solo un po 'di zucchero.

Vedi questo link per un confronto dei due modi.


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

Sommario:

1) con la newparola chiave ci sono due cose da notare;

a) la funzione viene utilizzata come costruttore

b) l' function.prototypeoggetto viene passato alla __proto__proprietà ... o dove __proto__non è supportato, è il secondo posto in cui il nuovo oggetto cerca di trovare le proprietà

2) con Object.create(obj.prototype)te stai costruendo un oggetto ( obj.prototype) e lo passi all'oggetto desiderato .. con la differenza che ora un nuovo oggetto __proto__punta anche a prototipo obj. (per favore fai riferimento a xj9 per quello)


15

Varianti di creazione di oggetti.


Variante 1 : ' new Object () ' -> Costruttore di oggetti senza argomenti.

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

inserisci qui la descrizione dell'immagine


Variante 2 : ' new Object (person) ' -> Costruttore di oggetti con argomento.

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 }

inserisci qui la descrizione dell'immagine


Variante 3.1 : ' Object.create (person) '. Usa Object.create con l'oggetto semplice 'persona'. 'Object.create (person)' creerà (e restituirà) un nuovo oggetto vuoto e aggiungerà la proprietà '__proto__' allo stesso nuovo oggetto vuoto. Questa proprietà '__proto__' punterà all'oggetto 'persona'.

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!

inserisci qui la descrizione dell'immagine


Variante 3.2 : ' Object.create (Object.prototype) '. Usa Object.create con oggetto incorporato -> 'Object.prototype'. 'Object.create (Object.prototype)' creerà (e restituirà) un nuovo oggetto vuoto e aggiungerà la proprietà '__proto__' allo stesso nuovo oggetto vuoto. Questa proprietà '__proto__' punterà all'oggetto '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

inserisci qui la descrizione dell'immagine


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!

inserisci qui la descrizione dell'immagine


Bel riassunto. Grazie. Mi ha aiutato oggi !!
Anandaraja_Srinivasan,

11

Internamente Object.createfa questo:

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

La sintassi toglie semplicemente l'illusione che JavaScript usi l'ereditarietà classica.


25
Il Object.createmetodo ECMAScript 5 , fa molto di più, puoi definire le proprietà dai descrittori di proprietà e puoi creare un oggetto che non eredita da nulla ( Object.create(null);), questo tipo di shim dovrebbe essere evitato perché non puoi davvero emularlo comportamento su ES3. Ulteriori informazioni
CMS,

Concordo con @CMS ma, in generale, è un semplice polyfill per Object.create.
V. Kovpak,

10

Di conseguenza, questa risposta e questa new parola chiave video fanno le seguenti cose:

  1. Crea un nuovo oggetto.

  2. Collega il nuovo oggetto alla funzione di costruzione ( prototype).

  3. Rende thisvariabile il punto per il nuovo oggetto.

  4. Esegue la funzione di costruzione utilizzando il nuovo oggetto e l'esecuzione implicita return this;

  5. Assegna il nome della funzione del costruttore alla proprietà del nuovo oggetto constructor.

Object.createesegue solo 1ste 2ndpassaggi !!!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.