Ich habe versucht, eine Klasse in TypeScript zu schreiben, für die eine Methode definiert ist, die als Ereignishandler-Rückruf für ein jQuery-Ereignis fungiert.
class Editor {
textarea: JQuery;
constructor(public id: string) {
this.textarea = $(id);
this.textarea.focusin(onFocusIn);
}
onFocusIn(e: JQueryEventObject) {
var height = this.textarea.css('height'); // <-- This is not good.
}
}
In der Ereignisbehandlungsroutine onFocusIn sieht TypeScript 'this' als 'this' der Klasse. JQuery überschreibt diese Referenz jedoch und setzt sie auf das dem Ereignis zugeordnete DOM-Objekt.
Eine Alternative besteht darin, ein Lambda innerhalb des Konstruktors als Ereignishandler zu definieren. In diesem Fall erstellt TypeScript eine Art Abschluss mit einem versteckten _this-Alias.
class Editor {
textarea: JQuery;
constructor(public id: string) {
this.textarea = $(id);
this.textarea.focusin((e) => {
var height = this.textarea.css('height'); // <-- This is good.
});
}
}
Meine Frage ist, gibt es eine andere Möglichkeit, mit TypeScript auf diese Referenz im methodenbasierten Ereignishandler zuzugreifen, um dieses jQuery-Verhalten zu überwinden?
jquery
typescript
Todd
quelle
quelle
Antworten:
Wie bereits erwähnt, gibt es keinen TypeScript-Mechanismus, mit dem sichergestellt werden kann, dass eine Methode immer an ihren
this
Zeiger gebunden ist (und dies ist nicht nur ein jQuery-Problem). Dies bedeutet nicht, dass es keinen einigermaßen einfachen Weg gibt, dieses Problem zu beheben. Sie müssen einen Proxy für Ihre Methode generieren, der denthis
Zeiger wiederherstellt, bevor Sie Ihren Rückruf aufrufen. Sie müssen dann Ihren Rückruf mit diesem Proxy umschließen, bevor Sie ihn an das Ereignis übergeben. jQuery hat einen eingebauten Mechanismus dafürjQuery.proxy()
. Hier ist ein Beispiel für Ihren obigen Code mit dieser Methode (beachten Sie den hinzugefügten$.proxy()
Aufruf.)class Editor { textarea: JQuery; constructor(public id: string) { this.textarea = $(id); this.textarea.focusin($.proxy(onFocusIn, this)); } onFocusIn(e: JQueryEventObject) { var height = this.textarea.css('height'); // <-- This is not good. } }
Das ist eine vernünftige Lösung, aber ich persönlich habe festgestellt, dass Entwickler häufig vergessen, den Proxy-Aufruf einzuschließen, und habe daher eine alternative TypeScript-basierte Lösung für dieses Problem gefunden. Wenn Sie die
HasCallbacks
Klasse verwenden, müssen Sie Ihre Klasse nur von ableiten. BeiHasCallbacks
allen Methoden, denen ein Präfix vorangestellt ist,'cb_'
wird derthis
Zeiger dauerhaft gebunden. Sie können diese Methode einfach nicht mit einem anderenthis
Zeiger aufrufen, was in den meisten Fällen vorzuziehen ist. Beide Mechanismen funktionieren so, dass sie einfach zu bedienen sind.class HasCallbacks { constructor() { var _this = this, _constructor = (<any>this).constructor; if (!_constructor.__cb__) { _constructor.__cb__ = {}; for (var m in this) { var fn = this[m]; if (typeof fn === 'function' && m.indexOf('cb_') == 0) { _constructor.__cb__[m] = fn; } } } for (var m in _constructor.__cb__) { (function (m, fn) { _this[m] = function () { return fn.apply(_this, Array.prototype.slice.call(arguments)); }; })(m, _constructor.__cb__[m]); } } } class Foo extends HasCallbacks { private label = 'test'; constructor() { super(); } public cb_Bar() { alert(this.label); } } var x = new Foo(); x.cb_Bar.call({});
quelle
$.proxy()
Anruf ist eine großartige Lösung für meine Situation. Einthis
Alias würde die TS komplizieren und, wie andere betont haben, nur schwer abwärtskompatibel sein.Der Umfang von
this
bleibt erhalten, wenn die Pfeilfunktionssyntax verwendet wird.() => { ... }
Hier ist ein Beispiel aus TypeScript für JavaScript-Programmierer .var ScopeExample = { text: "Text from outer function", run: function() { setTimeout( () => { alert(this.text); }, 1000); } };
Beachten Sie, dass
this.text
Sie dadurch erhalten,Text from outer function
dass die Pfeilfunktionssyntax den "lexikalischen Bereich" beibehält.quelle
setTimeout( function () { alert(this.text); }, 1000);
Sieundefined
.var _this = this
oben in der Methode erstellt und anschließend_this
in der anonymen Funktion referenziert .Wie in einigen anderen Antworten beschrieben, führt die Verwendung der Pfeilsyntax zum Definieren einer Funktion dazu, dass Verweise
this
immer auf die einschließende Klasse verweisen.Um Ihre Frage zu beantworten, finden Sie hier zwei einfache Problemumgehungen.
Verweisen Sie mit der Pfeilsyntax auf die Methode
constructor(public id: string) { this.textarea = $(id); this.textarea.focusin(e => this.onFocusIn(e)); }
Definieren Sie die Methode mithilfe der Pfeilsyntax
onFocusIn = (e: JQueryEventObject) => { var height = this.textarea.css('height'); }
quelle
Sie können eine Member-Funktion im Konstruktor an ihre Instanz binden.
class Editor { textarea: JQuery; constructor(public id: string) { this.textarea = $(id); this.textarea.focusin(onFocusIn); this.onFocusIn = this.onFocusIn.bind(this); // <-- Bind to 'this' forever } onFocusIn(e: JQueryEventObject) { var height = this.textarea.css('height'); // <-- This is now fine } }
Alternativ binden Sie es einfach, wenn Sie den Handler hinzufügen.
this.textarea.focusin(onFocusIn.bind(this));
quelle
bind
für ältere Browser benötigen Sie leider eine Polyfüllung .Versuche dies
class Editor { textarea: JQuery; constructor(public id: string) { this.textarea = $(id); this.textarea.focusin((e)=> { this.onFocusIn(e); }); } onFocusIn(e: JQueryEventObject) { var height = this.textarea.css('height'); // <-- This will work } }
quelle
Die Lösung von Steven Ickman ist praktisch, aber unvollständig. Die Antworten von Danny Becket und Sam sind kürzer und manueller und scheitern im gleichen allgemeinen Fall, wenn ein Rückruf vorliegt, der gleichzeitig dynamisch und lexikalisch "dies" erfordert. Springe zu meinem Code, wenn meine Erklärung unten TL ist; DR ...
Ich muss "this" für dynamisches Scoping für die Verwendung mit Bibliotheksrückrufen beibehalten, und ich muss ein "this" mit lexikalischem Scoping für die Klasseninstanz haben. Ich behaupte, dass es am elegantesten ist, die Instanz an einen Rückrufgenerator zu übergeben und den Parameter effektiv über die Klasseninstanz schließen zu lassen. Der Compiler teilt Ihnen mit, ob Sie dies verpasst haben. Ich verwende eine Konvention zum Aufrufen des Parameters mit lexikalischem Gültigkeitsbereich "OuterThis", aber "self" oder ein anderer Name könnte besser sein.
Die Verwendung des Schlüsselworts "this" wird aus der OO-Welt gestohlen, und als TypeScript es übernahm (aus den ECMAScript 6-Spezifikationen, nehme ich an), haben sie ein Konzept mit lexikalischem Gültigkeitsbereich und ein Konzept mit dynamischem Gültigkeitsbereich zusammengeführt, wenn eine Methode von einer anderen Entität aufgerufen wird . Ich bin ein wenig verärgert darüber; Ich würde ein "self" -Schlüsselwort in TypeScript bevorzugen, damit ich die lexikalisch begrenzte Objektinstanz davon übergeben kann. Alternativ könnte JS neu definiert werden, um bei Bedarf einen expliziten "Aufrufer" -Parameter an erster Stelle zu benötigen (und somit alle Webseiten auf einen Schlag zu unterbrechen).
Hier ist meine Lösung (aus einer großen Klasse herausgeschnitten). Sehen Sie sich insbesondere die Art und Weise an, wie die Methoden aufgerufen werden, und insbesondere den Körper von "dragmoveLambda":
export class OntologyMappingOverview { initGraph(){ ... // Using D3, have to provide a container of mouse-drag behavior functions // to a force layout graph this.nodeDragBehavior = d3.behavior.drag() .on("dragstart", this.dragstartLambda(this)) .on("drag", this.dragmoveLambda(this)) .on("dragend", this.dragendLambda(this)); ... } dragmoveLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} { console.log("redefine this for dragmove"); return function(d, i){ console.log("dragmove"); d.px += d3.event.dx; d.py += d3.event.dy; d.x += d3.event.dx; d.y += d3.event.dy; // Referring to "this" in dynamic scoping context d3.select(this).attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); outerThis.vis.selectAll("line") .filter(function(e, i){ return e.source == d || e.target == d; }) .attr("x1", function(e) { return e.source.x; }) .attr("y1", function(e) { return e.source.y; }) .attr("x2", function(e) { return e.target.x; }) .attr("y2", function(e) { return e.target.y; }); } } dragging: boolean =false; // *Call* these callback Lambda methods rather than passing directly to the callback caller. dragstartLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} { console.log("redefine this for dragstart"); return function(d, i) { console.log("dragstart"); outerThis.dragging = true; outerThis.forceLayout.stop(); } } dragendLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} { console.log("redefine this for dragend"); return function(d, i) { console.log("dragend"); outerThis.dragging = false; d.fixed = true; } } }
quelle
TypeScript bietet keine zusätzliche Möglichkeit (über die regulären JavaScript-Mittel hinaus), um zur "echten"
this
Referenz zurückzukehren, außerthis
der in der Lambda-Syntax für fette Pfeile (die aus rückenkompatibler Sicht zulässig ist, da kein vorhandener JS-Code vorhanden ist) könnte einen=>
Ausdruck verwenden).Sie könnten einen Vorschlag auf der CodePlex-Site veröffentlichen, aber aus Sicht des Sprachdesigns kann hier wahrscheinlich nicht viel passieren, da jedes vernünftige Schlüsselwort, das der Compiler bereitstellen könnte, möglicherweise bereits von vorhandenem JavaScript-Code verwendet wird.
quelle
Sie können die js eval-Funktion verwenden:
var realThis = eval('this');
quelle
Ich habe ein ähnliches Problem gehabt. Ich denke, Sie können
.each()
in vielen Fällenthis
einen anderen Wert für spätere Ereignisse verwenden.Der JavaScript-Weg:
$(':input').on('focus', function() { $(this).css('background-color', 'green'); }).on('blur', function() { $(this).css('background-color', 'transparent'); });
Der TypeScript-Weg:
$(':input').each((i, input) => { var $input = $(input); $input.on('focus', () => { $input.css('background-color', 'green'); }).on('blur', () => { $input.css('background-color', 'transparent'); }); });
Ich hoffe das hilft jemandem.
quelle
Sie können Ihre Referenz auf
this
in einer anderen Variablen speichern .self
Vielleicht können Sie auf diese Weise auf die Referenz zugreifen. Ich verwende kein Typoskript, aber das ist eine Methode, die mir in der Vergangenheit mit Vanille-Javascript erfolgreich war.quelle
this
Referenz innerhalb der Rückrufmethode diethis
Referenz des zugrunde liegenden Objekts maskiert , sodass keine Möglichkeit besteht, über die richtige Eigenschaft auf die neue Eigenschaft zuzugreifenthis
.In diesem Blogbeitrag http://lumpofcode.blogspot.com/2012/10/typescript-dart-google-web-toolkit-and.html finden Sie eine ausführliche Beschreibung der Techniken zum Organisieren von Anrufen innerhalb und zwischen TypeScript-Klassen.
quelle
Es gibt eine viel einfachere Lösung als alle oben genannten Antworten. Grundsätzlich greifen wir auf JavaScript zurück, indem wir die Schlüsselwortfunktion anstelle des Konstrukts '=>' verwenden, das das 'this' in die Klasse 'this' übersetzt.
class Editor { textarea: JQuery; constructor(public id: string) { var self = this; // <-- This is save the reference this.textarea = $(id); this.textarea.focusin(function() { // <-- using javascript function semantics here self.onFocusIn(this); // <-- 'this' is as same as in javascript }); } onFocusIn(jqueryObject : JQuery) { var height = jqueryObject.css('height'); } }
quelle