web-dev-qa-db-fra.com

Existe-t-il un alias pour «ceci» dans TypeScript?

J'ai essayé d'écrire une classe en TypeScript qui a une méthode définie qui agit comme un rappel de gestionnaire d'événements à un événement jQuery.

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.
    }
}

Dans le gestionnaire d'événement onFocusIn, TypeScript voit "ceci" comme étant le "ceci" de la classe. Cependant, jQuery remplace la référence this et la définit sur l'objet DOM associé à l'événement.

Une alternative consiste à définir un lambda dans le constructeur comme gestionnaire d'événements, auquel cas TypeScript crée une sorte de fermeture avec un alias _this masqué.

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin((e) => {
            var height = this.textarea.css('height'); // <-- This is good.
        });
    }
}

Ma question est, existe-t-il un autre moyen d'accéder à cette référence dans le gestionnaire d'événements basé sur une méthode à l'aide de TypeScript, pour surmonter ce comportement jQuery?

73
Todd

Ainsi, comme indiqué, il n'y a pas de mécanisme TypeScript pour garantir qu'une méthode est toujours liée à son pointeur this (et ce n'est pas seulement un problème jQuery.) Cela ne signifie pas qu'il n'y a pas de moyen raisonnablement simple de résoudre ce problème. Ce dont vous avez besoin est de générer un proxy pour votre méthode qui restaure le pointeur this avant d'appeler votre rappel. Vous devez ensuite envelopper votre rappel avec ce proxy avant de le transmettre à l'événement. jQuery a un mécanisme intégré pour cela appelé jQuery.proxy(). Voici un exemple de votre code ci-dessus utilisant cette méthode (notez l'appel $.proxy() ajouté.)

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. 
    } 
} 

C'est une solution raisonnable, mais j'ai personnellement constaté que les développeurs oublient souvent d'inclure l'appel proxy, j'ai donc trouvé une solution basée sur TypeScript alternative à ce problème. À l'aide de la classe HasCallbacks ci-dessous, tout ce que vous devez faire est de dériver votre classe de HasCallbacks puis de toutes les méthodes précédées de 'cb_' aura son pointeur this lié de façon permanente. Vous ne pouvez tout simplement pas appeler cette méthode avec un pointeur this différent, ce qui est préférable dans la plupart des cas. L'un ou l'autre mécanisme fonctionne de sorte que ce soit celui que vous trouvez le plus facile à utiliser.

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({});
21
Steven Ickman

La portée de this est préservée lors de l'utilisation de la syntaxe de la fonction flèche () => { ... } - voici un exemple tiré de TypeScript pour les programmeurs JavaScript .

var ScopeExample = { 
  text: "Text from outer function", 
  run: function() { 
    setTimeout( () => { 
      alert(this.text); 
    }, 1000); 
  } 
};

Notez que this.text vous donne Text from outer function car la syntaxe de la fonction flèche préserve la "portée lexicale".

97
Fenton

Comme couvert par certaines des autres réponses, l'utilisation de la syntaxe de flèche pour définir une fonction fait que les références à this font toujours référence à la classe englobante.

Pour répondre à votre question, voici deux solutions simples.

Référencez la méthode à l'aide de la syntaxe de la flèche

constructor(public id: string) {
    this.textarea = $(id);
    this.textarea.focusin(e => this.onFocusIn(e));
}

Définissez la méthode à l'aide de la syntaxe des flèches

onFocusIn = (e: JQueryEventObject) => {
    var height = this.textarea.css('height');
}
20
Sam

Vous pouvez lier une fonction membre à son instance dans le constructeur.

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
    }
}

Vous pouvez également le lier lorsque vous ajoutez le gestionnaire.

        this.textarea.focusin(onFocusIn.bind(this));
6
Drew Noakes

Essaye ça

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
    }

}
3
Gabriel C.

La solution de Steven Ickman est pratique, mais incomplète. Les réponses de Danny Becket et Sam sont plus courtes et plus manuelles, et échouent dans le même cas général d'avoir un rappel qui nécessite à la fois une portée dynamique et lexicale "ceci" en même temps. Passer à mon code si mon explication ci-dessous est TL; DR ...

J'ai besoin de conserver "ceci" pour la portée dynamique pour une utilisation avec les rappels de bibliothèque, et J'ai besoin d'avoir un "ceci" avec une portée lexicale à l'instance de classe. Je soutiens qu'il est plus élégant de passer l'instance dans un générateur de rappel, laissant efficacement la fermeture des paramètres sur l'instance de classe. Le compilateur vous indique si vous l'avez manqué. J'utilise une convention d'appeler le paramètre de portée lexicale "externalThis", mais "self" ou un autre nom pourrait être mieux.

L'utilisation du mot-clé "this" est dérobée au monde OO, et lorsque TypeScript l'a adopté (d'après les spécifications ECMAScript 6 je présume), ils ont confondu un concept à portée lexicale et un concept à portée dynamique, chaque fois qu'une méthode est appelée par une entité différente. Cela me dérange un peu; je préférerais un mot-clé "self" dans TypeScript afin de pouvoir en retirer l'instance d'objet à portée lexicale. Alternativement, JS pourrait être redéfini en exiger un paramètre "appelant" explicite en première position quand il est nécessaire (et ainsi casser toutes les pages Web d'un seul coup).

Voici ma solution (excisée d'une grande classe). Jetez un coup d'œil en particulier à la façon dont les méthodes sont appelées, et au corps de "dragmoveLambda" en particulier:

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;
        }
    }

}
3
Eric

TypeScript ne fournit aucun moyen supplémentaire (au-delà des moyens normaux de JavaScript) pour revenir à la référence "réelle" this autre que this commodité de remappage fournie dans la syntaxe lambda de la grosse flèche (qui est autorisé du point de vue de la rétrocompatibilité, car aucun code JS existant ne peut utiliser un => expression).

Vous pouvez publier une suggestion sur le site CodePlex, mais du point de vue de la conception du langage, il n'y a probablement pas grand-chose qui se produise ici peut, car tout mot clé sensé que le compilateur pourrait fournir pourrait déjà être utilisé par du code JavaScript existant.

2
Ryan Cavanaugh

Vous pouvez utiliser la fonction js eval: var realThis = eval('this');

2
reptile

J'ai rencontré un problème similaire. Je pense que vous pouvez utiliser .each() dans de nombreux cas pour conserver this comme valeur différente pour les événements ultérieurs.

La manière JavaScript:

$(':input').on('focus', function() {
  $(this).css('background-color', 'green');
}).on('blur', function() {
  $(this).css('background-color', 'transparent');
});

La manière TypeScript:

$(':input').each((i, input) => {
  var $input = $(input);
  $input.on('focus', () => {
    $input.css('background-color', 'green');
  }).on('blur', () => {
    $input.css('background-color', 'transparent');
  });
});

J'espère que ça aidera quelqu'un.

1
obrys

Il existe une solution beaucoup plus simple que toutes les réponses ci-dessus. Fondamentalement, nous retombons en JavaScript en utilisant la fonction clé Word au lieu d'utiliser la construction '=>' qui traduira le 'ceci' en classe 'ceci'

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'); 
    }
}
0
Derek Liang

Consultez cet article de blog http://lumpofcode.blogspot.com/2012/10/TypeScript-Dart-google-web-toolkit-and.html , il présente une discussion détaillée des techniques d'organisation des appels dans et entre les classes TypeScript.

0
Ezward

Vous pouvez stocker votre référence à this dans une autre variable .. self peut-être, et accéder à la référence de cette façon. Je n'utilise pas TypeScript, mais c'est une méthode qui a réussi pour moi avec javascript Vanilla dans le passé.

0
Sheridan Bulger