Машинопис "this" всередині методу класу


85

Я знаю, що це, мабуть, болісно базово, але мені важко обмотати це головою.

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

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

}

Схоже, мені потрібен проксі, тож скажімо, використовуючи Jquery

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

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

}

Але, виходячи з фону ActionScript 3, я не дуже впевнений, що тут відбувається. На жаль, я не впевнений, де починається Javascript, а закінчується TypeScript.

updateProxy: () => void

А також, я не впевнений, що роблю це правильно. Останнє, що я хочу, це те, що більшість мого класу мають функцію aa (), до якої потрібно отримати доступ, aProxy()оскільки я відчуваю, що пишу одне і те ж двічі? Це нормально?


Я знайшов цю документацію дуже корисною github.com/Microsoft/TypeScript/wiki/…
rainversion_3

Відповіді:


119

Якщо ви хочете thisзахопити TypeScript, це можна зробити за допомогою функцій стрілок. Процитувавши Андерса:

Функції thisin arrow мають лексичний масштаб

Ось як я люблю використовувати це на свою користь:

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

Перегляньте це на майданчику TypeScript

Ви можете бачити, що в створеному JavaScript thisзахоплюється поза викликом функції:

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

тому thisзначення всередині виклику функції (яке може бути window) не використовуватиметься.

Щоб дізнатись більше: “Розуміння thisв TypeScript” (4:05) - YouTube


2
Це не потрібно. Ви пропонуєте ідіоматичний JavaScript, але TypeScript робить це непотрібним.
Тетяна Рачева

1
@TatianaRacheva з використанням функцій зі стрілками в контексті учасників класу не дозволялася до TS 0.9.1 (і ця відповідь була до цього). Оновлена ​​відповідь на новий синтаксис :)
basarat

19
ПОВИНЕН ПЕРЕГЛЯДАТИ ВІДЕО - ДУЖЕ КОРИСНИЙ. ТІЛЬКИ 5 ХВИЛИН
Simon_Weaver

1
Дякую, @basarat. Лампочка продовжила контекст для цього, як тільки я побачив, що ви використовуєте функцію стрілки наполовину через ваше відео. Я ціную вас.
Саймон

1
@AaronLS на майданчику TypeScript, щоб створити його, var _this = this;вам слід вибрати ES5; оскільки ES2015 - внутрішні функції - thisвикористовується замість_this
Stefano Spinucci

19

Якщо ви пишете свої методи таким чином, до "цього" будуть ставитись так, як ви очікуєте.

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

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

Іншим варіантом було б прив'язати 'this' до виклику функції:

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

    update(): void
    {
        requestAnimationFrame(this.update.bind(this));
    }
}

2
На моєму досвіді функцію оновлення краще визначити так: update = () => {...}
Шон Роуан,

Я багато використовував машинопис і багато разів змінювався вперед і назад. на даний момент я включаю фігурні дужки, лише якщо це більше, ніж просто виклик методу. також, коли використовується машинопис + linq (що є побожним), формат є приємнішим. приклад: Enumerable.From (arr) .Where (o => o.id == 123);
joelnet

Думаю, якщо ви подивитесь на створений javascript, ви побачите істотну різницю. Справа не в смаці. update = () => {} створить лексичний масштаб за допомогою компіляції "var _this = this", ваш синтаксис не буде.
Шон Роуан,

1
Можливо, вам доведеться оновити вашу бібліотеку TypeScript, оскільки вона абсолютно містить контекст "_this". Використовуючи "() => код ()" або () => {код повернення (); .} »Буде виводити 100% код ідентичний Javascript Ось результат: i.imgur.com/I5J12GE.png Ви також можете бачити самі, скопіювавши код в. Typescriptlang.org/Playground
joelnet

мабуть, прив'язка (це) може бути поганою, оскільки втрачає безпеку типу на оригінальних аргументах функції
Роберт Кінг

6

Дивіться сторінку 72 специфікації мови машинопису https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.pdf?raw=true

Вираз функції стрілки

У прикладі

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

використання виразу функції-стрілки призводить до того, що зворотний виклик має такий самий, як і оточуючий метод 'start'. Записуючи зворотний виклик як стандартний вираз функції, стає необхідним вручну організувати доступ до оточуючого, наприклад, скопіювавши його в локальну змінну:

Це фактично створений Javascript:

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

На жаль, посилання розірвано
Джиммі Кейн,

1
@JimmyKane Я оновив посилання. Дивно, що цьому документу більше року і на нього все ще посилається його домашня сторінка, але важливий зміст, який я включив у відповідь.
Simon_Weaver

4

Проблема виникає, коли ви передаєте функцію як зворотний дзвінок. На час виконання зворотного виклику значення "this" могло змінитися на Window, елемент керування, що викликає зворотний виклик, або щось інше.

Переконайтеся, що ви завжди використовуєте лямбда-вираз у точці передачі посилання на функцію, яку потрібно викликати назад. Наприклад

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

Це компілюється приблизно так

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

Оскільки функція addFile викликається за певним посиланням на об'єкт (_this), вона не використовує "це" засобу, що викликає, а значення _this.


Коли ви говорите, до чого він компілюється, який із них ви показуєте? (Екземпляр стрілки або той, який просто передає об'єкт методу?)
Ваккано

Лямбда. Просто створіть TS з цим кодом і подивіться, що він компілює.
Пітер Морріс

3

Дуже пізно на вечірку, але я думаю, що для майбутніх відвідувачів цього питання дуже важливо врахувати наступне:

Інші відповіді, включаючи прийняту, пропускають важливий момент:

myFunction() { ... }

і

myFunction = () => { ... }

це НЕ те ж саме «за винятком , що останні захоплення this».

Перший синтаксис створює метод на прототипі, тоді як другий синтаксис створює властивість для об'єкта, значення якого є функцією (що також відбувається для захоплення this). Ви можете це чітко побачити в перекладеному JavaScript.

Щоб бути повним:

myFunction = function() { ... }

буде таким самим, як і другий синтаксис, але без захоплення.

Отже, використання синтаксису стрілки в більшості випадків вирішить проблему прив’язки до об’єкта, але це не однаково, і є багато ситуацій, коли ви хочете мати належну функцію на прототипі замість властивості.

У цих випадках використання проксі-сервера або .bind()насправді є правильним рішенням. (Хоча страждає від читабельності.)

Більше читайте тут (не насамперед про TypeScript, але принципи стоять):

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


2

Коротше кажучи, ключове слово this завжди має посилання на об’єкт, який викликав функцію.

У Javascript, оскільки функції - це просто змінні, ви можете їх передавати.

Приклад:

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

Коли ви робите наступне з jQuery:

$.proxy(this.update, this);

Те, що ви робите, перекриває цей контекст. За лаштунками jQuery дасть вам таке:

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

1

Як щодо того, щоб зробити це таким чином? Оголосіть глобальну змінну типу "myClass" та ініціалізуйте її в конструкторі класу:

var _self: myClass;

class myClass {
    classScopeVar: string = "hello";

    constructor() {
        _self = this;
    }

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

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

Примітка: Важливо НЕ вживати "self", оскільки це вже є особливим значенням.


Велика проблема: усі екземпляри класу мають однаковий _self, тому він не працює.
Astra Bear
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.