2013-02-24 12 views
27

Diciamo che ricevo alcuni oggetti JSON dal mio server, ad es. alcuni dati per un oggetto Person:Come aggiungere metodi a un prototipo di un oggetto (JSON)?

{firstName: "Bjarne", lastName: "Fisk"} 

Ora, voglio alcuni metodi in cima a tali dati, per esempio per il calcolo del fullName:

fullName: function() { return this.firstName + " " + this.lastName; } 

In modo che posso

var personData = {firstName: "Bjarne", lastName: "Fisk"}; 
var person = PROFIT(personData); 
person.fullName(); // => "Bjarne Fisk" 

Quello che fondamentalmente vorrei fare qui, è quello di aggiungere un metodo per il prototipo dell'oggetto. Il metodo fullName() è generale, quindi non deve essere aggiunto all'oggetto dati stesso. Come ..:

personData.fullName = function() { return this.firstName + " " + this.lastName; } 

... causerebbe un sacco di ridondanza; e probabilmente "inquinano" l'oggetto dati.

Qual è l'attuale modo migliore di aggiungere tali metodi a un semplice oggetto dati?

EDIT:

Leggermente fuori tema, ma se il problema di cui sopra può essere risolto, sarebbe possibile fare qualche bella pseudo pattern matching come questo:

if (p = Person(data)) { 
    console.log(p.fullName()); 
} else if (d = Dog(data)) { 
    console.log("I'm a dog lol. Hear me bark: "+d.bark()); 
} else { 
    throw new Exception("Shitty object"); 
} 

Person e Dog sarà aggiungere i metodi se l'oggetto data ha gli attributi corretti. In caso contrario, restituire false (ad esempio, i dati non corrispondono a non corrispondono a corrispondenza/conformità).

DOMANDA BONUS: Qualcuno sa di una libreria che utilizza o abilita questo (cioè lo rende facile)? È già uno schema javascript? Se è così, come è chiamato; e hai un link che elabora? Grazie :)

+2

Non dovresti già sapere che tipo di oggetto è quando stai recuperando i dati JSON? Sarebbe certamente possibile creare una libreria di base per la costruzione di oggetti che faccia ciò che stai suggerendo, sembra proprio come rilevare il tipo di oggetto basato sulle proprietà non è il modo migliore per farlo ... soprattutto perché possono avere diversi tipi proprietà con nomi simili. Perché non avere solo un metodo '' JSON' o una funzione di costruzione che ha semplicemente ignorato le proprietà che non erano state definite dal costruttore? –

+0

@MattB: Posso immaginare la funzione parseJson che accetta la stringa e la mappa percorso/protoclasse per assegnare classi agli oggetti su costruzioni. Il _path_ qui potrebbe essere ad esempio l'espressione JsonPath: http://goessner.net/articles/JsonPath/ –

+0

Hmm, i percorsi vengono comunemente utilizzati dai framework di associazione dei dati Javascript per associare una proprietà dell'oggetto a qualcosa nell'HTML, ma io Non sono sicuro del motivo per cui avresti bisogno di usarli per la costruzione di oggetti poiché la maggior parte dei linguaggi sul lato server ora ha la possibilità di serializzare su JSON. –

risposta

18

Supponendo che il tuo oggetto provenga da una libreria JSON che analizza l'output del server per generare un oggetto, in generale non avrà nulla di particolare nel suo prototipo; e due oggetti generati per diverse risposte del server non condivideranno una catena di prototipi (oltre a Object.prototype, ovviamente;))

Se si controllano tutti i luoghi in cui una "Persona" viene creata da JSON, si potrebbero fare le cose viceversa: creare un oggetto Person "vuoto" (con un metodo come fullName nel suo prototipo) ed estenderlo con l'oggetto generato dal JSON (usando $ .extend, _.extend, o qualcosa di simile).

var p = { first : "John", last : "Doe"}; 

function Person(data) { 
    _.extend(this, data); 
} 

Person.prototype.fullName = function() { 
    return this.first + " " + this.last; 
} 

console.debug(new Person(p).fullName()); 
+0

Supponendo che lo stesso script JavaScript recuperi dati da server diversi, allora sì, tutti gli oggetti erediteranno dallo stesso prototipo ('Object.prototype'). –

+0

@FelixKling: va da sé, meglio quando viene detto ... – phtrivier

0

Non penso che sia comune il trasporto di metodi con i dati, ma sembra una grande idea.

Questo progetto consente di codificare le funzioni insieme ai dati, ma non è considerato standard e richiede la decodifica con la stessa libreria, naturalmente.

https://github.com/josipk/json-plus 
5

Se hai a che fare con i dati JSON pianura poi il prototipo di ogni oggetto persona sarebbe semplicemente Object.prototype.Al fine di farne un oggetto con un prototipo di Person.prototype si sarebbe prima di tutto bisogno di un Person costruttore e prototipo (supponendo che si sta facendo Javascript OOP in modo tradizionale):

function Person() { 
    this.firstName = null; 
    this.lastName = null; 
} 
Person.prototype.fullName = function() { return this.firstName + " " + this.lastName; } 

allora si sarebbe bisogno di un modo per trasformare un oggetto semplice in un oggetto Person, ad es se si ha una funzione chiamata mixin che semplicemente copiato tutte le proprietà da un oggetto all'altro, si potrebbe fare questo:

//example JSON object 
var jsonPerson = {firstName: "Bjarne", lastName: "Fisk"}; 

var person = new Person(); 
mixin(person, jsonPerson); 

Questo è solo un modo di risolvere il problema, ma dovrebbe spera darvi alcune idee.


Aggiornamento: Ora che Object.assign() è disponibile nei browser moderni, è possibile utilizzare che invece di scrivere la propria funzione mixin. C'è anche uno shim per far funzionare Object.assign() sui browser più vecchi; vedi https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill.

+0

Questo fa esattamente quello che voglio. L'unico problema è che è un po 'ingombrante. Immagino che ci sia un modo ancora più semplice per farlo. Questo ha ** ottenuto ** per essere un problema comune. Questa è una delle (molte) mancanze di javascript? – kornfridge

+1

Potrebbe essere reso meno complicato se si disponesse di un modello di base o di una classe di entità di cui Person e tutte le altre classi di modelli di dominio ereditate. Quella classe Model di base poteva avere un metodo factory (ad esempio 'Model.prototype.new') che accettava un oggetto JSON e faceva la stessa cosa del mio esempio' mixin'. Oppure potresti utilizzare un framework che include già questo tipo di comportamento, ad es. il metodo 'Model.init' di Can.js: http://canjs.us/#can_model-init –

+1

Inoltre, il costruttore' Person' potrebbe essere reso leggermente più conciso usando la funzione 'mixin', ad es.: 'funzione di persona() { \t \t mixin (questo, { \t \t \t firstName: null, \t \t \t lastName: null \t \t}); \t} ' –

0

Gli oggetti anonimi non hanno un prototipo. Perché non basta avere questo:

function fullName(obj) { 
    return obj.firstName + ' ' + obj.lastName; 
} 

fullName(person); 

Se è assolutamente necessario utilizzare una chiamata di metodo, invece di una chiamata di funzione, si può sempre fare qualcosa di simile, ma con un oggetto.

var Person = function (person) { this.person = person; } 
Person.prototype.fullName = function() { 
    return this.person.firstName + ' ' + this.person.lastName; 
} 
var person = new Person(personData); 
person.fullName(); 
+0

'function fullName (obj) {return obj.firstName + '' + obj.lastName; } 'è molto un-OOP. La tua seconda soluzione farà la pausa '.firstName'. – kornfridge

+0

Solo perché qualcosa usa oggetti non lo rende orientato agli oggetti. Qualcosa di "non-OOP" è inferiore? "OOP" ha distrutto una generazione di programmatori ... Non so cosa intendi facendo .firstName break –

+1

Significa che nel tuo secondo esempio, dopo la riga 'var person = new Person (personData);' 'person .firstName' non funzionerebbe più. È meglio impostare tutte le proprietà come proprietà di 'this' all'interno del costruttore Person, in questo modo si comporta come un oggetto normale .... l'approccio proxy complica l'intera cosa. –

4

Probabilmente non dovresti farlo.

JSON consente di serializzare uno stato, non un tipo. Quindi nel tuo caso d'uso, si dovrebbe fare qualcosa di simile:

var Person = function (data) { 
    if (data) { 
     this.firstName = data.firstName; 
     this.lastName = data.lastName; 
    } 
}; 

Person.prototype.fullName = function () { 
    return this.firstName + ' ' + this.lastName; 
}; 

// 

var input = '{"firstName":"john", "lastName":"Doe"}'; 
var myData = JSON.parse(input); 
var person = new Person(myData); 
+0

Questa è una bella variazione sulla mia risposta, la nostra risposta i rs potrebbero anche essere combinati in modo da utilizzare una funzione 'mixin' dal costruttore di Person. –

3

In altre parole si vuole cambiare prototipo (classe pseudonimo) di oggetto esistente. Tecnicamente si può fare in questo modo:

var Person = { 
    function fullName() { return this.firstName + " " + this.lastName; } 
}; 

// that is your PROFIT function body: 
personData.__proto__ = Person ; 

Dopo di che, se si ottengono true su personData instanceof Person

+0

Dovresti menzionare che '__proto__' è non standard e deprecato nella maggior parte dei browser. –

+0

'__proto__' è non standard e non funzionerà in alcuni vecchi browser; tuttavia, è l'unico modo per cambiare il prototipo di un oggetto esistente. Questa è una risposta interessante ... e potrebbe essere resa compatibile con tutti i browser avendo una funzione 'setPrototype' che ricade semplicemente copiando tutti i metodi nei browser che non supportano' __proto__'. Tuttavia questo approccio potrebbe sembrare un po 'strano per chiunque lavori sul tuo codice senza un forte background Javascript. –

+0

@Felix Kling: Sì, '__proto__' potrebbe non essere supportato da IE (non è stato controllato di recente). Con la mia risposta ho appena provato a formalizzare il requisito - _tecnicamente_ (letto idealmente) che è esattamente ciò di cui il richiedente ha bisogno. Un'implementazione particolare può variare. –

9

C'è un'altra possibilità qui. JSON.parse accetta un secondo parametro, che è una funzione utilizzata per rianimare gli oggetti rilevati, dai nodi foglia al nodo radice. Quindi, se riesci a riconoscere i tuoi tipi in base alle loro proprietà intrinseche, puoi costruirli in una funzione di reviver. Ecco un esempio molto semplice di farlo:

var MultiReviver = function(types) { 
    // todo: error checking: types must be an array, and each element 
    //  must have appropriate `test` and `deserialize` functions 
    return function(key, value) { 
     var type; 
     for (var i = 0; i < types.length; i++) { 
      type = types[i]; 
      if (type.test(value)) { 
       return type.deserialize(value); 
      } 
     } 
     return value; 
    }; 
}; 

var Person = function(first, last) { 
    this.firstName = first; 
    this.lastName = last; 
}; 
Person.prototype.fullName = function() { 
    return this.firstName + " " + this.lastName; 
}; 
Person.prototype.toString = function() {return "Person: " + this.fullName();}; 
Person.test = function(value) { 
    return typeof value.firstName == "string" && 
      typeof value.lastName == "string"; 
}; 
Person.deserialize = function(obj) { 
    return new Person(obj.firstName, obj.lastName); 
}; 

var Dog = function(breed, name) { 
    this.breed = breed; 
    this.name = name; 
} 
Dog.prototype.species = "canine"; 
Dog.prototype.toString = function() { 
    return this.breed + " named " + this.name; 
}; 
Dog.test = function(value) {return value.species === "canine";}; 
Dog.deserialize = function(obj) {return new Dog(obj.breed, obj.name);}; 


var reviver = new MultiReviver([Person, Dog]); 

var text = '[{"firstName": "John", "lastName": "Doe"},' + 
      '{"firstName": "Jane", "lastName": "Doe"},' + 
      '{"firstName": "Junior", "lastName": "Doe"},' + 
      '{"species": "canine", "breed": "Poodle", "name": "Puzzle"},' + 
      '{"species": "canine", "breed": "Wolfhound", "name": "BJ"}]'; 

var family = JSON.parse(text, reviver) 
family.join("\n"); 

// Person: John Doe 
// Person: Jane Doe 
// Person: Junior Doe 
// Poodle named Puzzle 
// Wolfhound named BJ 

Questo dipende da voi essere in grado di riconoscere in modo inequivocabile i tipi di. Ad esempio, se ci fosse un altro tipo, anche un sottotipo di Persona, che aveva anche proprietà firstName e lastName, questo non avrebbe funzionato. Ma potrebbe coprire alcuni bisogni.

0

Non è necessario utilizzare i prototipi per associare un metodo personalizzato nell'oggetto barebone.

Ecco un esempio elegante che non inquinano il codice evitando codice ridondante

var myobj = { 
    title: 'example', 
    assets: 
    { 
    resources: ['zero', 'one', 'two'] 
    } 
} 

var myfunc = function(index) 
{ 
    console.log(this.resources[index]); 
} 

myobj.assets.giveme = myfunc 

myobj.assets.giveme(1); 

Esempio disponibile in https://jsfiddle.net/bmde6L0r/

1

Utilizzare il nuovo-ish Object.setPrototypeOf(). (È supportato da IE11 e da tutti gli altri browser ora.)

Si potrebbe creare una classe/prototipo che comprendeva i metodi che si desidera, come il tuo fullName(), e poi

Object.setPrototypeOf(personData, Person.prototype); 

Come l'avviso (a pagina MDN linkato sopra) suggerisce, questa funzione è non essere usato alla leggera, ma ha senso quando si cambia il prototipo di un oggetto esistente, e questo è ciò che sembra essere dopo.

Problemi correlati