Digita “questo” problema di scoping quando chiamato in callback jquery

Non sono sicuro dell’approccio migliore per la gestione dell’ambito di “this” in TypeScript.

Ecco un esempio di uno schema comune nel codice che sto convertendo in TypeScript:

class DemonstrateScopingProblems { private status = "blah"; public run() { alert(this.status); } } var thisTest = new DemonstrateScopingProblems(); // works as expected, displays "blah": thisTest.run(); // doesn't work; this is scoped to be the document so this.status is undefined: $(document).ready(thisTest.run); 

Ora, potrei cambiare la chiamata a …

 $(document).ready(thisTest.run.bind(thisTest)); 

… che funziona. Ma è un po ‘orribile. Significa che il codice può essere compilato e funzionare correttamente in alcune circostanze, ma se ci dimentichiamo di vincolare lo scope si romperà.

Mi piacerebbe un modo per farlo all’interno della class, in modo che quando si usa la class non ci si debba preoccupare di cosa “questo” è ambito.

Eventuali suggerimenti?

Aggiornare

Un altro approccio che funziona è l’uso della freccia grassa:

 class DemonstrateScopingProblems { private status = "blah"; public run = () => { alert(this.status); } } 

È un approccio valido?

Hai alcune opzioni qui, ognuna con i suoi compromessi. Sfortunatamente non esiste una soluzione ottimale e dipenderà davvero dall’applicazione.

Associazione automatica di class
Come mostrato nella tua domanda:

 class DemonstrateScopingProblems { private status = "blah"; public run = () => { alert(this.status); } } 
  • Buono / cattivo: crea una chiusura aggiuntiva per metodo per istanza della class. Se questo metodo viene solitamente utilizzato solo nelle normali chiamate di metodo, questo è eccessivo. Tuttavia, se viene utilizzato molto nelle posizioni di callback, è più efficiente per l’istanza della class acquisire il contesto, invece di ogni sito di chiamata che crea una nuova chiusura su invoke.
  • Buono: imansible per i chiamanti esterni dimenticare di gestire this contesto
  • Buono: Typesafe in TypeScript
  • Buono: nessun lavoro aggiuntivo se la funzione ha parametri
  • Cattivo: le classi derivate non possono chiamare i metodi della class base scritti in questo modo usando super.
  • Cattivo: la semantica esatta di quali metodi sono “pre-associati” e che non creano un contratto aggiuntivo non tipicamente tra la tua class e i suoi consumatori.

Function.bind
Anche come mostrato:

 $(document).ready(thisTest.run.bind(thisTest)); 
  • Buono / cattivo: opposto memoria / prestazioni rispetto al primo metodo
  • Buono: nessun lavoro aggiuntivo se la funzione ha parametri
  • Scorretto: in TypeScript, al momento questo non ha alcun tipo di sicurezza
  • Cattivo: disponibile solo in ECMAScript 5, se questo è importante per te
  • Cattivo: devi digitare il nome dell’istanza due volte

Freccia grassa
In TypeScript (mostrato qui con alcuni parametri fittizi per ragioni esplicative):

 $(document).ready((n, m) => thisTest.run(n, m)); 
  • Buono / cattivo: opposto memoria / prestazioni rispetto al primo metodo
  • Buono: in TypeScript, questo ha il 100% di sicurezza del tipo
  • Buono: funziona in ECMAScript 3
  • Bene: devi solo digitare il nome dell’istanza una volta
  • Cattivo: dovrai digitare i parametri due volte
  • Cattivo: non funziona con i parametri variadici

Necromancing.
C’è una soluzione semplice e ovvia che non richiede le funzioni della freccia (le funzioni della freccia sono il 30% più lente) o i metodi JIT attraverso i getter.
Questa soluzione è quella di associare il contesto-questo nel costruttore.

 class DemonstrateScopingProblems { constructor() { this.run = this.run.bind(this); } private status = "blah"; public run() { alert(this.status); } } 

È ansible utilizzare questo metodo per associare automaticamente tutte le funzioni nella class nel costruttore:

 class DemonstrateScopingProblems { constructor() { this.autoBind(this); } [...] } export function autoBind(self: any) { for (const key of Object.getOwnPropertyNames(self.constructor.prototype)) { const val = self[key]; if (key !== 'constructor' && typeof val === 'function') { // console.log(key); self[key] = val.bind(self); } // End if (key !== 'constructor' && typeof val === 'function') } // Next key return self; } // End Function autoBind 

Un’altra soluzione che richiede alcune impostazioni iniziali, ma che paga con la sua luce invincibilmente leggera, la syntax letterale di una sola parola sta usando Method Decorator per i metodi JIT-bind attraverso i getter.

Ho creato un repository su GitHub per mostrare un’implementazione di questa idea (è un po ‘lungo inserire una risposta con le sue 40 righe di codice, inclusi i commenti) , che useresti semplicemente come:

 class DemonstrateScopingProblems { private status = "blah"; @bound public run() { alert(this.status); } } 

Non ho ancora visto questo accennato, ma funziona in modo impeccabile. Inoltre, non vi è uno svantaggio notevole di questo approccio: l’implementazione di questo decoratore, incluso un controllo dei tipi per la sicurezza del tipo runtime , è banale e semplice, ed è sostanzialmente privo di overhead dopo la chiamata al metodo iniziale.

La parte essenziale è la definizione del seguente getter sul prototipo di class, che viene eseguito immediatamente prima della prima chiamata:

 get: function () { // Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the // instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance. var instance = this; Object.defineProperty(instance, propKey.toString(), { value: function () { // This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations. return originalMethod.apply(instance, arguments); } }); // The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way // JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it. return instance[propKey]; } 

Fonte completa


L’idea può anche essere fatta un passo in più, facendo questo in un decoratore di class, invece, ripetendo i metodi e definendo il descrittore di proprietà sopra per ognuno di essi in un unico passaggio.

Nel tuo codice, hai provato a cambiare l’ultima riga come segue?

 $(document).ready(() => thisTest.run());