Mecanografiado "esto" dentro de un método de clase

81

Sé que esto probablemente sea dolorosamente básico, pero me está costando entenderlo.

class Main
{
     constructor()
     {
         requestAnimationFrame(this.update);  //fine    
     }

     update(): void
     {
         requestAnimationFrame(this.update);  //error, because this is window
     }

}

Parece ser el caso de que necesito un proxy, así que digamos usando Jquery

class Main
{
     constructor()
     {
         this.updateProxy = $.proxy(this.update, this);
         requestAnimationFrame(this.updateProxy);  //fine    
     }

     updateProxy: () => void
     update(): void
     {
         requestAnimationFrame(this.updateProxy);  //fine
     }

}

Pero teniendo en cuenta los antecedentes de Actionscript 3, no estoy seguro de lo que está sucediendo aquí. Lo siento, no estoy seguro de dónde comienza Javascript y termina TypeScript.

updateProxy: () => void

Además, no estoy convencido de que esté haciendo esto bien. Lo último que quiero es que la mayor parte de mi clase tenga una función aa () a la que se debe acceder, aProxy()ya que siento que estoy escribiendo lo mismo dos veces. ¿Es normal?

Clark
fuente
Encontré esta documentación muy útil github.com/Microsoft/TypeScript/wiki/…
rainversion_3

Respuestas:

117

Si desea thiscapturar, la forma de TypeScript de hacerlo es a través de las funciones de flecha. Para citar a Anders:

Las thisfunciones en flecha tienen un ámbito léxico

Esta es la forma en que me gusta usar esto para mi ventaja:

class test{
    // Use arrow functions
    func1=(arg:string)=>{
            return arg+" yeah" + this.prop;
    }
    func2=(arg:number)=>{
            return arg+10 + this.prop;
    }       

    // some property on this
    prop = 10;      
}

Ver esto en TypeScript Playground

Puede ver que en el JavaScript generado thisse captura fuera de la llamada a la función:

var _this = this;
this.prop = 10;
this.func1 = function (arg) {
    return arg + " yeah" + _this.prop;
};

por lo que el thisvalor dentro de la llamada a la función (que podría ser window) no se usaría.

Para obtener más información: "Comprensión thisde TypeScript" (4:05) - YouTube

basarat
fuente
2
Esto no es necesario. Lo que está sugiriendo es JavaScript idiomático, pero TypeScript lo hace innecesario.
Tatiana Racheva
1
@TatianaRacheva usando funciones de flecha en el contexto de miembros de la clase no estaba permitido antes de TS 0.9.1 (y esta respuesta fue antes de eso). Respuesta actualizada a la nueva sintaxis :)
basarat
18
DEBES VER EL VIDEO - MUY ÚTIL. SOLO 5 MINUTOS
Simon_Weaver
1
Gracias, @basarat. La bombilla se encendió para contextualizar esto tan pronto como vi que usabas la función de flecha en la mitad de tu video. Te aprecio.
Simon
1
@AaronLS en el patio de recreo de TypeScript para generar, var _this = this;debes elegir ES5; desde ES2015 - funciones internas - thisse usa en lugar de_this
Stefano Spinucci
18

Si escribe sus métodos de esta manera, "esto" se tratará de la manera que espera.

class Main
{
    constructor()
    {
        requestAnimationFrame(() => this.update());
    }

    update(): void
    {
        requestAnimationFrame(() => this.update());
    }
}

Otra opción sería vincular 'esto' a la llamada a la función:

class Main
{
    constructor()
    {
        requestAnimationFrame(this.update.bind(this));
    }

    update(): void
    {
        requestAnimationFrame(this.update.bind(this));
    }
}
joelnet
fuente
2
En mi experiencia, la función de actualización se define mejor así: update = () => {...}
Shaun Rowan
He estado usando mucho mecanografiado y he cambiado de un lado a otro muchas veces. Por el momento, solo incluyo las llaves si es más que una simple llamada a un método. también cuando se usa mecanografiado + linq (que es divino), el formato es mejor. ejemplo: Enumerable.From (arr) .Where (o => o.id == 123);
joelnet
Creo que si miras el javascript que se genera, verás una diferencia significativa. No es cuestión de gustos. update = () => {} creará un alcance léxico a través de la compilación "var _this = this", su sintaxis no lo hará.
Shaun Rowan
1
Es posible que deba actualizar su biblioteca de TypeScript porque incluye absolutamente el contexto "_este". Usando "() => código ()" o () => {código de retorno (); } "generará un código javascript 100% idéntico. Aquí está el resultado: i.imgur.com/I5J12GE.png . También puede verlo usted mismo pegando el código en typescriptlang.org/Playground
joelnet
aparentemente, enlazar (esto) puede ser malo porque pierde seguridad de tipo en los argumentos de función originales
robert king
6

Consulte la página 72 de la especificación del lenguaje mecanografiado https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.pdf?raw=true

Expresiones de función de flecha

En el ejemplo

class Messenger {
 message = "Hello World";
 start() {
 setTimeout(() => alert(this.message), 3000);
 }
};
var messenger = new Messenger();
messenger.start();

el uso de una expresión de función de flecha hace que la devolución de llamada tenga lo mismo que el método 'start' circundante. Al escribir la devolución de llamada como una expresión de función estándar, se hace necesario organizar manualmente el acceso al entorno, por ejemplo, copiándolo en una variable local:

Este es el Javascript generado real:

class Messenger {
 message = "Hello World";
 start() {
 var _this = this;
 setTimeout(function() { alert(_this.message); }, 3000);
 }
};
Simon_Weaver
fuente
Desafortunadamente, el enlace está roto
Jimmy Kane
1
@JimmyKane Actualicé el enlace. Curiosamente, este documento tiene más de un año y todavía se menciona en su página de inicio, pero el contenido importante que incluí en la respuesta.
Simon_Weaver
4

El problema surge cuando pasa una función como devolución de llamada. Para cuando la devolución de llamada se haya ejecutado, el valor de "this" podría haber cambiado a la ventana, el control que invoca la devolución de llamada o algo más.

Asegúrese de usar siempre una expresión lambda en el punto en que pasa una referencia a la función que se llamará de nuevo. Por ejemplo

public addFile(file) {
  this.files.push(file);
}
//Not like this
someObject.doSomething(addFile);
//but instead, like this
someObject.doSomething( (file) => addFile(file) );

Esto se compila en algo como

this.addFile(file) {
  this.files.push(file);
}
var _this = this;
someObject.doSomething(_this.addFile);

Debido a que se llama a la función addFile en una referencia de objeto específica (_this), no usa el "this" del invocador sino el valor de _this.

Peter Morris
fuente
Cuando dices lo que compila, ¿cuál estás mostrando? (¿La instancia de flecha o la que solo pasa el objeto de método?)
Vaccano
La lambda. Simplemente cree un TS con ese código y observe lo que compila.
Peter Morris
2

En resumen, la palabra clave this siempre tiene una referencia al objeto que llamó a la función.

En Javascript, dado que las funciones son solo variables, puede pasarlas.

Ejemplo:

var x = {
   localvar: 5, 
   test: function(){
      alert(this.localvar);
   }
};

x.test() // outputs 5

var y;
y.somemethod = x.test; // assign the function test from x to the 'property' somemethod on y
y.test();              // outputs undefined, this now points to y and y has no localvar

y.localvar = "super dooper string";
y.test();              // outputs super dooper string

Cuando haces lo siguiente con jQuery:

$.proxy(this.update, this);

Lo que está haciendo es anular ese contexto. Detrás de escena, jQuery te guiará esto:

$.proxy = function(fnc, scope){
  return function(){
     return fnc.apply(scope);  // apply is a method on a function that calls that function with a given this value
  }
};
Kenneth
fuente
2

Muy tarde para la fiesta, pero creo que es muy importante que los futuros visitantes de esta pregunta consideren lo siguiente:

Las otras respuestas, incluida la aceptada, pierden un punto crucial:

myFunction() { ... }

y

myFunction = () => { ... }

no son lo mismo "con la salvedad de que este último captura this".

La primera sintaxis crea un método en el prototipo, mientras que la segunda sintaxis crea una propiedad en el objeto cuyo valor es una función (que también pasa a capturar this ). Puede ver esto claramente en el JavaScript transpilado.

Estar Completo:

myFunction = function() { ... }

sería la misma que la segunda sintaxis, pero sin la captura.

Entonces, usar la sintaxis de flecha en la mayoría de los casos solucionará su problema de vinculación al objeto, pero no es lo mismo y hay muchas situaciones en las que desea tener una función adecuada en el prototipo en lugar de una propiedad.

En estos casos utilizar un proxy o en .bind()realidad es la solución correcta. (Aunque sufre legibilidad).

Más lectura aquí (no principalmente sobre TypeScript, pero los principios se mantienen):

https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1

https://ponyfoo.com/articles/binding-methods-to-class-instance-objects

Karim Ayachi
fuente
0

¿Qué tal hacerlo de esta manera? Declare una variable global de tipo "myClass" e inicialícela en el constructor de la clase:

var _self: myClass;

class myClass {
    classScopeVar: string = "hello";

    constructor() {
        _self = this;
    }

    alerter() {
        setTimeout(function () {
            alert(_self.classScopeVar)
        }, 500);
    }
}

var classInstance = new myClass();
classInstance.alerter();

Nota: Es importante NO usar "self", ya que ya tiene un significado especial.

Maxter
fuente