2012-04-26 13 views
10

So che questo non è il metodo consigliato per farlo, ma se dichiaro le seguenti funzioni e poi le invoco come costruttori, quale sarà la differenza (se esiste) tra gli oggetti risultanti?Che differenza c'è in JavaScript tra una funzione di costruzione e una funzione che restituisce un oggetto invocato come costruttore?

function Something() { 
    this.foo = "bar"; 
} 

function something2() { 
    var that = {}; 
    that.foo = "bar"; 
    return that; 
} 

var x = new Something(); 
var y = new something2(); 
var z = something2(); 

I.e. cosa differirà tra x, e z qui?

Non sarebbe something2 essere un modo molto migliore di scrivere il costruttore, dal momento che se si utilizza new o non influenzerà il risultato della funzione?

BTW dovrebbe something2 essere capitalizzato qui? (Presumo non da quando Crockford è così irremovibile sulla capitalizzazione, poiché le funzioni comprimeranno il namespace globale ...)

+2

Essere un po 'diffidenti di Mr Crockford. Sebbene abbia molte cose buone da dire, ha delle opinioni che non sono d'accordo. – staticsan

risposta

14

In breve:

new something2() instanceof something2 === false 

relazione a ciò, se si estende il vostro esempio di utilizzare la proprietà prototipo

Something.prototype.method = function() { }; 
something2.prototype.method = function() { }; 

troverete che il prototipo non è ereditato in quest'ultimo caso:

typeof (new Something()).method === "function" 
type (new something2()).method === "undefined" 

Il rea La risposta è che stai attingendo a meccanismi di fondo completamente diversi.La chiamata con new richiama il meccanismo [[Construct]], che implica l'impostazione della proprietà [[Prototipo]] in base alla proprietà .prototype del costruttore.

Ma una cosa divertente accade nei passaggi 8--10 dell'algoritmo [[Costruisci]]: dopo aver impostato un nuovo oggetto vuoto e quindi collegato il suo [[Prototipo]], fa un [[Call] ] al costruttore effettivo, utilizzando questo nuovo oggetto empty-plus-prototype come this. E poi, al punto 9, se si scopre che quel costruttore ha restituito qualcosa --- getta via quell'oggetto prototipally, passato-come- this che ha trascorso tutto quel tempo a settare!

Nota: è possibile accedere di un oggetto [[Prototype]] (che è diversa da quella di un costruttore .prototype) con Object.getPrototypeOf:

Object.getPrototypeOf(new Something()) === Something.prototype // steps 5 & 6 
Object.getPrototypeOf(new something2()) === Object.prototype // default 

di rispondere ad alcune meta-domande:

  • No, non utilizzare maiuscole something2, poiché è una funzione di fabbrica e non un costruttore. Se qualcosa è in maiuscolo, ci si aspetta che abbia la semantica del costruttore, ad es. new A() instanceof A.
  • Se si è preoccupati del pericolo di oscuramento dello spazio dei nomi globale, è necessario iniziare a utilizzare strict mode, inserendo "use strict"; nella parte superiore dei file. Una delle tante piacevoli operazioni di pulizia della modalità rigorosa è che il valore predefinito di this è undefined, non l'oggetto globale, quindi ad es. chiamare un costruttore senza new causerà errori nel momento in cui il costruttore tenta di collegare le proprietà a undefined.
  • Le funzioni di fabbrica (dette anche "schema di chiusura") sono in generale un ragionevole sostituto per costruttori e classi, purché non si usi (a) l'ereditarietà; (b) non costruire troppe istanze di quell'oggetto. Quest'ultimo è perché, nel modello di chiusura, si allega una nuova istanza di ogni metodo per ogni oggetto appena creato, il che non è ottimale per l'utilizzo della memoria. Il payoff più grande, IMO, del pattern di chiusura è la possibilità di utilizzare "private" variables (che sono uno good thing e non consentire a nessuno di dirti diversamente: P).
+0

Il 'nuovo' è anche opzionale (o non è necessario) sul secondo metodo, ma avvita le cose se lasciato fuori nel primo (questa finestra ===) – Matt

+0

@ Matt-puoi difenderti un po 'dall'interno del costruttore vedendo se "this" è l'oggetto globale e procedendo di conseguenza. – RobG

+1

@Matt, @RobG --- oppure, semplicemente metti '" use strict ";' nella parte superiore del tuo file! Quindi 'questo' sarà 'indefinito'. – Domenic

2

Nel secondo caso, l'oggetto restituito non eredita nulla dal costruttore, quindi non ha molto senso in usandolo come tale.

> var x = new Something(); 
> var y = new something2(); 
> var z = something2(); 

Vale a dire cosa differirà tra x, y e z qui?

x eredita da Something, wheres né y o z ereditare da something2.

Non sarebbe something2 essere un modo molto migliore di scrivere il costruttore, dal fatto che si utilizzi nuovi o no, non influenzerà il risultato della funzione ?

v'è alcun punto nel chiamare something2 come costruttore perché l'oggetto restituisce non è l'oggetto di nuova costruzione assegnato alla sua this che eredita da something2.prototype, che è ciò che altri potrebbero aspettarsi di ottenere quando si chiama new something2().

BTW dovrebbe qualcosa2 essere capitalizzato qui? (Io non presumo dal Crockford è così irremovibile sulla capitalizzazione, per le funzioni saranno massacravano namespace globale ...)

No, perché definendolo come un costruttore è un po 'inutile, in modo che lo caratterizzano come uno sarebbe fuorviante

1

Invocare una funzione come un costruttore (cioè con il nuovo keyword) esegue i seguenti passi:

  1. creare un nuovo oggetto
  2. impostare il prototipo di tale oggetto all'oggetto nella proprietà prototype della funzione
  3. eseguire la funzione di costruzione nel contesto di tale oggetto (ovvero è il nuovo oggetto)
  4. restituire quell'oggetto (se il costrutto o non ha una dichiarazione return)

Quindi, la seconda soluzione restituirà semplicemente un oggetto semplice con una proprietà "pippo". Ma né né z sono instanceof Something2 e non ereditano da quel prototipo. Ci sono funzioni come questa, sì, ma non dovrebbero essere chiamate costruttori (nessuna denominazione maiuscola, nessuna chiamata con new). Appartengono al modello di fabbrica.

Se si desidera un costruttore che può essere eseguito senza nuovo, utilizzare quel codice:

function Something(params) { 
    if (! this instanceof Something) 
     return new Something(params); 
    // else use "this" as usual 
    this.foo = "bar"; 
    ... 
} 
1

Direi che la cosa più importante sarebbe il prototipo degli oggetti restituiti.

function Something() { 
     this.foo = "bar"; 
    } 

    Something.prototype = { 
    // Something prototype code 
    hello: function(){ 
    //... 
    } 
    } 

    function something2() { 
    var that = {}; 
    that.foo = "bar"; 
    return that; 
    } 

    something2.prototype = { 
     // something2 prototype code 
     greetings : function() { 
     //... 
     } 
    } 

    var x = new Something(); 
    var y = new something2(); 
    var z = something2(); 

    typeof x.hello === function // should be true 
    typeof y.greetings === undefined // should be true 
    typeof z.greetings === undefined // should be true 

In altre parole, direi che non stai istanziare oggetti withe something2, si sta creando puramente nuovi oggetti che ereditano da Object.

  1. Qualcosa() vi darà nuovi oggetti di tipo "Qualcosa" quando si utilizza la nuova parola chiave.
  2. something2() vi darà nuovi oggetti di tipo "Oggetto", che restituiranno immediatamente un nuovo oggetto vuoto.
  3. nuova something2 è inefficiente, perché si sta creando un ambito in bianco, da cui si crea un nuovo oggetto

    var that = {}; 
    

    che equivale a

    var that = new Object(); 
    
Problemi correlati