Cosa c'è di sbagliato nell'estendere una classe con i metodi del prototipo?

2

Ieri sera ero in un bar con alcuni dei miei colleghi. Hanno detto che è una cattiva idea estendere la funzionalità degli oggetti JavaScript di base con un metodo prototipo.

Ad esempio, supponiamo tu abbia creato un metodo per trovare il fattoriale

Number.prototype.factorial = function(n) {
  return n == 0 ? 1 : factorial(n - 1)

}

Hanno detto che c'era il pericolo di creare prototipi. Perché questa sarebbe una cattiva pratica?

    
posta user439133 25.06.2015 - 15:08
fonte

2 risposte

4

Il problema è ambito globale . In altre parole, ogni modifica apportata influisce sull'intero codice base e il tuo può a sua volta essere interessato in qualsiasi posizione del codice di base.

Immagina di voler scorrere tutti gli elementi di un array; immagina che i browser non lo supportino ancora. Per questo, crei un metodo forEach che viene chiamato in questo modo:

[5, 7, 1, 1, 3].forEach(function (element) {
    // Do something here.
});

Hai implementato questo metodo, hai pensato ad alcuni casi limite (per esempio a un array vuoto, per esempio?) e tutto è a posto, tranne un leggero bug di prestazioni che ti sei perso: in alcuni casi l'enumerazione è molto lenta.

Ora, da qualche altra parte, il tuo collega ha avuto questa fantastica idea di avere un metodo forEach , ma non sa che ne hai già implementato uno; in realtà, ha anche provato a chiamare forEach su un array per assicurarsi che il metodo non esista ancora, ma poiché il codice non viene eseguito su ogni pagina, [].forEach restituisce undefined .

Quindi crea la sua implementazione, ma dimentica di testarlo per un array vuoto, e, in effetti, quando l'array è vuoto, il suo codice fallisce.

Torna al tuo codice, un giorno trovi un bug report che dice che c'è un errore intorno a forEach : quando la matrice è vuota, fallisce. Non sai che il tuo collega ha avuto la sua implementazione di forEach che sovrascrive il tuo su alcune pagine. Pertanto, pensi che sia il tuo codice a preoccuparti, quindi trascorri ore a chiedersi perché non funziona, mentre i tuoi test unitari passano.

Finisci per trovare il colpevole, ovvero il% co_de duplicato, e decidi di rimuovere il codice del tuo collega: il tuo è migliore. Il giorno dopo, un nuovo rapporto sui bug indica che forEach restituisce [].forEach su alcune pagine, ovvero le pagine in cui il codice non viene eseguito. WTF!

Insieme al tuo collega, trascorri altre due ore per risolvere questo problema. Infine, ora sei sicuro che il tuo codice sia eseguito su ogni pagina e hai un codice pulito in cui il prototipo viene aggiunto solo se non esiste già un metodo undefined :

if (![].forEach) {
    Array.prototype.forEach = function (...) {
        ...
    };
}

Nel frattempo, viene scoperto il piccolo bug di prestazioni che hai nel tuo codice, ma sembra che sia più una funzionalità, e molti dei tuoi colleghi si affidano a questo bug: in diverse posizioni, hanno assolutamente bisogno dell'enumerazione per essere lenti per avere abbastanza tempo per mostrare alcune immagini. Dal momento che riscrivere il loro codice sembra essere un incubo, decidi di mantenere il bug.

Un anno dopo, una mattina, scoprirai dozzine di segnalazioni di errori da decine di clienti. Sta succedendo qualcosa. Qualcuno del tuo team scopre che tutte le segnalazioni di bug provengono dagli utenti di Chrome e un'ulteriore analisi rileva che il motore JavaScript della versione appena rilasciata di Chrome ha forEach , con la differenza che non ha il tuo bug / funzionalità i tuoi colleghi facevano affidamento su Ora il tuo team impiegherà due giorni ad adattare il codice alla nuova implementazione senza errori di forEach .

    
risposta data 25.06.2015 - 19:48
fonte
1

Abhi ha spiegato su Stack Overflow perché non vuoi farlo con oggetti comuni, ma per la tua classe, questo è il modo di farlo. Quindi, in breve, la risposta è DEPENDS .

When you extend an object, you change its behaviour.

Changing the behaviour of an object that will only be used by your own code is fine. But when you change the behaviour of something that is also used by other code there is a risk you will break that other code.

When it comes adding methods to the object and array classes in javascript, the risk of breaking something is very high, due to how javascript works. Long years of experience have taught me that this kind of stuff causes all kinds of terrible bugs in javascript.

If you need custom behaviour, it is far better to define your own class (perhaps a subclass) instead of changing a native one. That way you will not break anything at all.

The ability to change how a class works without subclassing it is an important feature of any good programming language, but it is one that must be used rarely and with caution.

Inoltre, c'è anche un aspetto delle prestazioni di questo.

Supponiamo che tu abbia aggiunto una proprietà a un prototipo di qualche classe. Il modo in cui funziona JS, è quando si tenta di accedere a una proprietà su qualsiasi oggetto, si cercherà di trovarlo su questo oggetto, se non trovato, JS procederà a controllare il suo prototipo e il prototipo del prototipo .. e così via, fino a quando raggiunge la cima in cui non viene trovato più un prototipo. Se si aggiunge la proprietà al prototipo immediato della classe, si sta essenzialmente producendo un codice più efficiente rispetto all'aggiunta al prototipo di un oggetto comune in JS.

Ma, ancora, ciò che significa è: usalo attentamente per capire cosa fa. Qualsiasi cosa è male se usata in modo inappropriato.

    
risposta data 25.06.2015 - 18:51
fonte

Leggi altre domande sui tag