Non ci sono modi per avere oggetti basati su classi in javascript?

Lo stile di programmazione orientato agli oggetti basato su prototipi javascript è interessante, ma ci sono molte situazioni in cui è necessario poter creare oggetti da una class.

Ad esempio in un’applicazione di disegno vettoriale, lo spazio di lavoro sarà solitamente vuoto all’inizio del disegno: non riesco a creare una nuova “linea” da una esistente. Più in generale, ogni situazione in cui gli oggetti vengono creati dynamicmente richiede l’uso di classi.

Ho letto molti tutorial e il libro “Javascript: le parti buone”, ma tuttavia mi sembra che non ci sia modo di definire classi che rispettano 1) l’incapsulamento e 2) dichiarazione efficiente dei metodi membro (intendo: membro metodi che vengono definiti una volta e condivisi tra tutte le istanze di class).

Per definire variabili private, vengono utilizzate le chiusure:

function ClassA() { var value = 1; this.getValue = function() { return value; } } 

Il problema qui è che ogni istanza di “ClassA” avrà la sua copia della funzione membro “getValue”, che non è efficiente.

Per definire le funzioni membro in modo efficiente, viene utilizzato il prototipo:

 function ClassB() { this.value = 1; } ClassB.prototype.getValue = function() { return this.value; } 

Il problema qui è che la variabile membro “valore” è pubblica.

Non penso che questo problema possa essere risolto facilmente, dal momento che le variabili “private” devono essere definite DURANTE la creazione dell’object (in modo che l’object possa avere accesso al suo contesto di creazione, senza esporre i valori) mentre le funzioni membro basate su prototipo la definizione deve essere eseguita DOPO la creazione dell’object, in modo che il prototipo abbia senso (“questo.prototipo” non esiste, ho controllato).

O mi sta sfuggendo qualcosa ?


MODIFICARE :

Prima di tutto, grazie per le vostre risposte interessanti.

Volevo solo aggiungere un po ‘di precisione al mio messaggio iniziale:

Quello che voglio veramente fare è avere 1) variabili private (l’incapsulamento è buono, perché le persone hanno solo accesso a ciò di cui hanno bisogno) e 2) dichiarazione dei metodi membro efficiente (evitare le copie).

Sembra che la semplice dichiarazione di variabili private possa davvero essere raggiunta solo tramite la chiusura in javascript, che è essenzialmente il motivo per cui mi sono concentrato sull’approccio basato sulla class. Se esiste un modo per ottenere una semplice dichiarazione di variabili private con un approccio basato sul prototipo, va bene per me, non sono un feroce proponente di approccio basato sulla class.

Dopo aver letto le risposte, sembra che la soluzione più semplice sia quella di dimenticare i privati ​​e utilizzare speciali convenzioni di codifica per impedire ad altri programmatori di accedere direttamente alle variabili “private” …

E sono d’accordo, il mio titolo / prima frase era fuorviante per quanto riguarda la questione che volevo discutere qui.

Shh, vieni qui! Vuoi sentire un segreto?

L’ereditarietà classica è un approccio collaudato e provato.

È utile implementarlo spesso in JavaScript. Le classi sono un bel concetto da avere e avere modelli per modellare il nostro mondo dopo che gli oggetti sono fantastici.

L’ereditarietà classica è solo uno schema. È perfettamente OK implementare l’ereditarietà classica in JavaScript se è lo schema necessario per il tuo caso d’uso.

L’ereditarietà prototipale si concentra sulla condivisione delle funzionalità e ciò è fantastico ( dinasaur drumstick awesome), ma in alcuni casi si desidera condividere uno schema di dati e non funzionalità. Questo è un problema che l’ereditarietà prototipica non affronta affatto.

Allora, mi stai dicendo che le lezioni non sono malvagie come tutti mi dicono?

No non lo sono. Ciò che la comunità JS disapprovante non è il concetto di classi, è limitarsi a solo classi per il riutilizzo del codice. Proprio come il linguaggio non impone la tipizzazione forte o statica, non impone schemi sulla struttura dell’object.

Di fatto, dietro le quinte implementazioni intelligenti della lingua possono trasformare i tuoi oggetti normali in qualcosa che assomiglia alle classiche classi di ereditarietà.

Quindi, come funzionano le classi in JavaScript

Beh, hai solo bisogno di un costruttore:

 function getVehicle(engine){ return { engine : engine }; } var v = getVehicle("V6"); v.engine;//v6 

Ora abbiamo una class di veicoli. Non è stato necessario definire esplicitamente una class Vehicle utilizzando una parola chiave speciale. Ora, alcune persone non amano fare le cose in questo modo e sono abituate al modo più classico. Per questo JS fornisce (silly imho) zucchero sintattico facendo:

 function Vehicle(engine){ this.engine = engine; } var v = new Vehicle("V6"); v.engine;//v6 

Questa è la stessa cosa dell’esempio sopra per la maggior parte.

Allora, cosa ci manca ancora?

Eredità e membri privati.

E se ti dicessi che la sottotipizzazione di base è molto semplice in JavaScript?

La nozione di digitazione di JavaScript è diversa da quella a cui siamo abituati in altre lingue. Cosa significa essere un sottotipo di un certo tipo in JS?

 var a = {x:5}; var b = {x:3,y:3}; 

Il tipo di b un sottotipo del tipo di a ? Diciamo se è in base al sottotipo comportamentale (forte) (l’LSP):

<<<< Inizia la parte tecnica

  • Contravarianza degli argomenti del metodo nel sottotipo: è completamente conservata in questo tipo di ereditarietà.
  • Covarianza dei tipi di ritorno nel sottotipo: è completamente conservata in questo tipo di eredità.
  • Nessuna nuova eccezione dovrebbe essere generata dai metodi del sottotipo, eccetto laddove tali eccezioni siano esse stesse sottotipi di eccezioni generate dai metodi del supertipo. – È completamente conservato in questo tipo di eredità.

Anche,

  • Le precondizioni non possono essere rafforzate in un sottotipo.
  • Le postcondizioni non possono essere indebolite in un sottotipo.
  • Gli invarianti del supertipo devono essere conservati in un sottotipo.
  • La regola della storia

Tutto questo è di nuovo, dipende da noi da mantenere. Possiamo tenerli così strettamente o liberamente come vogliamo, non dobbiamo, ma sicuramente possiamo.

Quindi, di fatto, finché rispettiamo queste regole sopra quando implementiamo la nostra ereditarietà, stiamo implementando completamente un sottotipo comportamentale forte, che è una forma molto potente di sottotipizzazione (vedi nota *).

>>>>> Fine della parte tecnica

Banalmente, si può anche vedere che la sottotipizzazione strutturale regge.

Come si potrebbe applicare al nostro esempio di Car ?

 function getCar(typeOfCar){ var v = getVehicle("CarEngine"); v.typeOfCar = typeOfCar; return v; } v = getCar("Honda"); v.typeOfCar;//Honda; v.engine;//CarEngine 

Non troppo difficile, vero? E i membri privati?

 function getVehicle(engine){ var secret = "Hello" return { engine : engine, getSecret : function() { return secret; } }; } 

Vedi, il secret è una variabile di chiusura . È perfettamente “privato”, funziona in modo diverso rispetto ai privati ​​in linguaggi come Java, ma è imansible accedere dall’esterno.

Che ne dici di avere dei privilegiati nelle funzioni?

Ah! Questa è una grande domanda.

Se vogliamo usare una variabile privata in una funzione che condividiamo sul prototipo, dobbiamo prima capire come funzionano le chiusure e le funzioni di JS.

In JavaScript le funzioni sono di prima class. Ciò significa che puoi passare le funzioni in giro.

 function getPerson(name){ var greeting = "Hello " + name; return { greet : function() { return greeting; } }; } var a = getPerson("thomasc"); a.greet(); //Hello thomasc 

Fin qui tutto bene, ma possiamo passare quella funzione limitata ad un intorno ad altri oggetti! Questo ti permette di fare un disaccoppiamento molto allentato che è fantastico.

 var b = a.greet; b(); //Hello thomasc 

Aspettare! Come ha fatto a sapere che il nome della persona è thomasc? Questa è solo la magia delle chiusure. Piuttosto fantastico eh?

Potresti essere preoccupato per le prestazioni. Lascia che ti dica come ho imparato a smettere di preoccuparmi e ho iniziato ad amare l’ottimista JIT.

In pratica, avere copie di funzioni del genere non è un grosso problema. Le funzioni in javascript riguardano tutto, funzionalità! Le chiusure sono un concetto fantastico, una volta che le hai afferrate e le hai padroneggiate vedi che ne vale la pena, e il successo in termini di prestazioni non è così significativo. JS sta diventando sempre più veloce ogni giorno, non preoccuparti di questo tipo di problemi di prestazioni.

Se pensi che sia complicato, anche il seguente è molto legittimo. Un contratto comune con altri sviluppatori dice semplicemente “Se la mia variabile inizia con _ non toccarla, siamo entrambi adulti consenzienti”. Questo sembrerebbe qualcosa di simile:

 function getPerson(name){ var greeter = { greet : function() { return "Hello" +greeter._name; } }; greeter._name = name; return greeter; } 

O in stile classico

 function Person(name){ this._name = name; this.greet = function(){ return "Hello "+this._name; } } 

Oppure se desideri memorizzare nella cache la funzione sul prototipo invece di creare copie di istanze:

 function Person(name){ this._name = name; } Person.prototype.greet = function(){ return "Hello "+this._name; } 

Quindi, per riassumere:

  • Puoi utilizzare modelli di ereditarietà classici, sono utili per la condivisione di tipi di dati

  • Dovresti anche usare l’ereditarietà prototipale, è altrettanto potente e molto di più nel caso in cui desideri condividere funzionalità.

  • TheifMaster praticamente lo ha inchiodato. Avere privati ​​privati ​​non è un grosso problema come si potrebbe pensare in JavaScript, a patto che il codice definisca un’interfaccia chiara e non dovrebbe essere affatto problematico. Stiamo tutti concentrando gli adulti qui 🙂

* Il lettore intelligente potrebbe pensare: Eh? Non mi stavi ingannando lì con la regola della storia? Voglio dire, l’accesso alla proprietà non è incapsulato.

Io dico di no, non lo ero. Anche se non incapsuli esplicitamente i campi come privati, puoi semplicemente definire il tuo contratto in un modo che non li acceda. Spesso come suggerito da TheifMaster con _ . Inoltre, penso che la regola della cronologia non sia un grosso problema in molti di questi scenari, purché non cambiamo il modo in cui l’accesso alla proprietà tratta le proprietà dell’object genitore. Di nuovo, tocca a noi.

Non voglio essere scoraggiante dal momento che sembra essere un nuovo membro di StackOverflow, tuttavia dovrò essere un po ‘in faccia e dire che è una pessima idea provare ad implementare l’ereditarietà classica in JavaScript .

Nota: quando dico che è una ctriggers idea implementare l’ereditarietà classica in JavaScript, voglio dire che provare a simulare classi, interfacce, modificatori di accesso, ecc. In JavaScript è una ctriggers idea. Tuttavia, l’ereditarietà classica come modello di progettazione in JavaScript è utile in quanto è solo zucchero sintattico per l’eredità prototipale (ad esempio classi massimamente minimali ). Uso sempre questo modello di progettazione nel mio codice ( a laumenta ).

JavaScript è un prototipo di linguaggio di programmazione orientato agli oggetti. Non un classico linguaggio di programmazione orientato agli oggetti. Certo, puoi implementare l’ereditarietà classica su JavaScript, ma prima di tutto tieni presente quanto segue:

  1. Stai andando contro lo spirito della lingua, il che significa che ti troverai di fronte a problemi. Un sacco di problemi: prestazioni, leggibilità, manutenibilità, ecc.
  2. Non hai bisogno di lezioni. Thomas, so che credi davvero di aver bisogno di lezioni ma credimi su questo. Non lo fai.

Per il tuo bene fornirò due risposte a questa domanda. Il primo ti mostrerà come eseguire l’ereditarietà classica in JavaScript. Il secondo (che vi raccomando) vi insegnerà ad abbracciare l’eredità prototipale.

Eredità classica in JavaScript

La maggior parte dei programmatori inizia con il tentativo di implementare l’ereditarietà classica in JavaScript. Perfino i Guru di JavaScript come Douglas Crockford hanno cercato di implementare l’ereditarietà classica in JavaScript. Anch’io ho cercato di implementare l’ereditarietà classica in JavaScript.

Per prima cosa ho creato una libreria chiamata Clockwork e poi aumentata . Tuttavia non ti consiglierei di usare una di queste librerie perché vanno contro lo spirito di JavaScript. La verità è che ero ancora un programmatore JavaScript amatoriale quando ho scritto queste librerie di ereditarietà classica.

L’unica ragione per cui dico questo è perché tutti sono dilettanti a un certo punto del tempo, e anche se preferirei che non si usassero modelli classici di ereditarietà in JavaScript, non posso aspettarti che tu capisca perché l’eredità prototipale conta ancora.

Non puoi imparare a pedalare senza cadere alcune volte. Credo che tu sia ancora nella fase di apprendimento rispetto all’eredità prototipale. Il tuo bisogno di ereditarietà classica è come le ruote di addestramento sui cicli.

Tuttavia, le ruote di addestramento sono importanti. Se vuoi, ci sono alcune librerie di ereditarietà classiche che dovrebbero renderti più comodo scrivere codice in JavaScript. Una tale libreria è jTypes . Ricordati di togliere le rotelle di allenamento quando sei sicuro delle tue capacità come programmatore JavaScript.

Nota: Personalmente non mi piace jTypes un po ‘.

Ereditarietà prototipale in JavaScript

Sto scrivendo questa sezione come una pietra miliare per te in modo che tu possa tornare più tardi e sapere cosa fare dopo quando sei pronto per conoscere la vera eredità prototipale .

Prima di tutto la seguente riga è sbagliata:

Lo stile di programmazione orientato agli oggetti basato su prototipi javascript è interessante, ma ci sono molte situazioni in cui è necessario poter creare oggetti da una class.

Questo è sbagliato perché:

  1. Non avrai mai bisogno di creare oggetti da una class in JavaScript.
  2. Non c’è modo di creare una class in JavaScript.

Sì, è ansible simulare l’ereditarietà classica in JavaScript. Tuttavia, stai ancora ereditando le proprietà dagli oggetti e non dalle classi. Ad esempio, le classi di ECMAScript Harmony sono solo zucchero sintattico per il modello classico dell’ereditarietà prototipale.

Nello stesso contesto anche l’esempio che hai dato è sbagliato:

Ad esempio in un’applicazione di disegno vettoriale, lo spazio di lavoro sarà solitamente vuoto all’inizio del disegno: non riesco a creare una nuova “linea” da una esistente. Più in generale, ogni situazione in cui gli oggetti vengono creati dynamicmente richiede l’uso di classi.

Sì, puoi creare una nuova linea da una esistente anche se all’inizio lo spazio di lavoro è vuoto. Quello che devi capire è che la linea non è in realtà disegnata però.

 var line = { x1: 0, y1: 0, x2: 0, y2: 0, draw: function () { // drawing logic }, create: function (x1, y1, x2, y2) { var line = Object.create(this); line.x1 = x1; line.y1 = y1; line.x2 = x2; line.y2 = y2; return line; } }; 

Ora puoi disegnare la tua linea semplicemente chiamando line.draw oppure puoi creare una nuova linea da essa:

 var line2 = line.create(0, 0, 0, 100); var line3 = line.create(0, 100, 100, 100); var line4 = line.create(100, 100, 100, 0); var line5 = line.create(100, 0, 0, 0); line2.draw(); line3.draw(); line4.draw(); line5.draw(); 

Le linee line2 , line3 , line4 e line5 formano un quadrato 100x100 quando vengono disegnate.

Conclusione

Quindi vedi che non hai davvero bisogno di classi in JavaScript. Gli oggetti sono abbastanza L’incapsulamento può essere facilmente raggiunto utilizzando le funzioni.

Detto questo, non è ansible avere funzioni pubbliche di ogni istanza per accedere allo stato privato dell’object senza che ciascuna istanza abbia il proprio insieme di funzioni pubbliche.

Questo non è un problema perché:

  1. Non hai davvero bisogno di uno stato privato. Potresti pensare che lo fai, ma davvero no.
  2. Se vuoi veramente rendere privata una variabile allora come menzionato da ThiefMaster, prefissa il nome della variabile con un trattino basso e chiedi ai tuoi utenti di non scherzare .

Aight, ecco il mio tentativo di risolvere questo particolare problema, anche se penso che seguendo le convenzioni sia un approccio migliore, ad es. prefisso le tue variabili con _ . Qui tengo semplicemente traccia delle istanze in un array, quindi possono essere rimosse con un metodo _destroy . Sono sicuro che questo può essere migliorato, ma spero che vi darà alcune idee:

 var Class = (function ClassModule() { var private = []; // array of objects of private variables function Class(value) { this._init(); this._set('value', value); } Class.prototype = { // create new instance _init: function() { this.instance = private.length; private.push({ instance: this.instance }); }, // get private variable _get: function(prop) { return private[this.instance][prop]; }, // set private variable _set: function(prop, value) { return private[this.instance][prop] = value; }, // remove private variables _destroy: function() { delete private[this.instance]; }, getValue: function() { return this._get('value'); } }; return Class; }()); var a = new Class('foo'); var b = new Class('baz'); console.log(a.getValue()); //=> foo console.log(b.getValue()); //=> baz a._destroy(); console.log(b.getValue()); //=> baz 

Non hai bisogno di privati ​​/ pubblici in fase di runtime . Questi sono applicabili in modo statico. Qualsiasi progetto complesso abbastanza da imporre proprietà private non viene utilizzato all’esterno avrà una fase di build / pre-processo, che è ansible utilizzare per verificare il fatto. Anche le lingue con syntax per privato / pubblico hanno un modo per accedere al privato in fase di runtime .

Per quanto riguarda la definizione di oggetti basati su classi, il costruttore + prototipo che si sta utilizzando è il modo più semplice ed efficiente. Qualsiasi tipo di magia aggiuntiva sarà più complessa e meno performante.

Sebbene sia ansible memorizzare nella cache il prototype modo da non dover ripetere ClassB.prototype. tutto il tempo:

 //in node.js you can leave the wrapper function out var ClassB = (function() { var method = ClassB.prototype; function ClassB( value ) { this._value = value; } method.getValue = function() { return this._value; }; method.setValue = function( value ) { this._value = value; }; return ClassB; })(); 

Quanto sopra non richiede alcuna libreria e puoi facilmente creare una macro per esso.

Inoltre, in questo caso è sufficiente una regex per verificare che le proprietà “private” siano utilizzate correttamente. Esegui /([a-zA-Z$_-][a-zA-Z0-9$_-]*)\._.+/g attraverso il file e vedi che la prima corrispondenza è sempre this . http://jsfiddle.net/7gumy/

È imansible per quanto ne so senza che altre istanze influenzino il valore, quindi se è una costante sei comunque bravo avvolgendolo in una funzione come questa:

 (function( context ) { 'use strict'; var SOME_CONSTANT = 'Hello World'; var SomeObject = function() {}; SomeObject.prototype.sayHello = function() { console.log(SOME_CONSTANT); }; context.SomeObject = SomeObject; })( this ); var someInstance = new SomeObject(); someInstance.sayHello(); 

Il meglio che puoi fare è annotare che una proprietà non deve essere toccata usando un underscore come this._value invece di this.value .

Nota che le funzioni private sono possibili nascondendole in una funzione:

 (function( context ) { 'use strict'; var SomeObject = function() {}; var getMessage = function() { return 'Hello World'; }; SomeObject.prototype.sayHello = function() { console.log(getMessage()); }; context.SomeObject = SomeObject; })( this ); var someInstance = new SomeObject(); someInstance.sayHello(); 

Ecco un esempio di 2 “Classi” che si estendono e interagiscono tra loro: http://jsfiddle.net/TV3H3/

Se vuoi davvero entity framework private per ogni istanza, ma vuoi comunque ereditare i tuoi metodi, puoi usare il seguente set-up:

 var Bundle = (function(){ var local = {}, constructor = function(){ if ( this instanceof constructor ) { local[(this.id = constructor.id++)] = { data: 123 }; } }; constructor.id = 0; constructor.prototype.exampleMethod = function(){ return local[this.id].data; } return constructor; })(); 

Ora se crei un new Bundle , il valore dei data viene bloccato all’interno di:

 var a = new Bundle(); console.log( a.exampleMethod() ); /// 123 

Tuttavia ora entri nel dibattito se devi davvero avere valori privati ​​in JavaScript. Per quanto ho scoperto, è sempre meglio per quelli che potrebbero aver bisogno di estendere il tuo codice, anche tu stesso, per avere accesso aperto a tutto.

Ci sono anche aspetti negativi nascosti al modello sopra, oltre a non essere così leggibili, o essere clunky per accedere ai valori “privati”. Un fatto è che ogni singola istanza di Bundle manterrà un riferimento all’object local . Ciò potrebbe significare, ad esempio, se hai creato migliaia di pacchetti e ne hai cancellato tutti tranne uno, i dati conservati in local non sarebbero stati raccolti per tutti i pacchetti mai creati. Dovresti includere un codice di decostruzione per risolverlo … fondamentalmente rendendo le cose più complicate.

Quindi suggerirei di abbandonare l’idea di entity framework / proprietà private, a prescindere dal modello che decidi di adottare per … basato su oggetti o costruttore. Il vantaggio di JavaScript è che tutti questi approcci diversi sono possibili – non è affatto chiaro come i linguaggi basati sulle classi – che alcuni potrebbero obiettare rende le cose confuse, ma mi piace che la libertà JavaScript prenda per essere veloce ed espressiva.

per quanto riguarda questa affermazione nella tua domanda:

Ad esempio in un’applicazione di disegno vettoriale, lo spazio di lavoro sarà solitamente vuoto all’inizio del disegno: non riesco a creare una nuova “linea” da una esistente. Più in generale, ogni situazione in cui gli oggetti vengono creati dynamicmente richiede l’uso di classi.

Sembra che tu sia sotto l’equivoco che gli oggetti in Javascript possono essere creati solo clonando oggetti esistenti, che potrebbero tornare indietro al problema di “okay ma per quanto riguarda il primissimo object?” Che non può essere fatto clonando un object esistente perché lì non sono oggetti esistenti. ”

Tuttavia puoi creare oggetti da zero, e la syntax è semplice come var object = {}

Voglio dire, questo è l’object più semplice ansible, un object vuoto. Più utile ovviamente sarebbe un object con cose in esso:

 var object = { name: "Thing", value: 0, method: function() { return true; } } 

e così via, e ora sei fuori per le gare!

Ci sono persone più intelligenti di me che rispondo a questa domanda, ma ho voluto notare una parte in particolare che hai appena modificato, la parte variabile privata .

Puoi simulare questo usando chiusure; un fantastico costrutto che consente a una funzione di avere il proprio ambiente. Potresti fare questo:

 var line = (function() { var length = 0; return { setLen : function (newLen) { length = newLen; }, getLen : function () { return length; } }; }()); 

Questo imposterà la linea come un object con i metodi setLen e getLen , ma non avrai modo di accedere manualmente alla length senza usare quei metodi.