Есть ли псевдоним для 'this' в TypeScript?

Я попытался написать класс в TypeScript, который имеет определенный метод, который действует как обратный обработчик обработчика события 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.
    }
}

В обработчике событий onFocusIn TypeScript видит 'this' как 'this' класса. Однако jQuery переопределяет эту ссылку и устанавливает ее в объект DOM, связанный с событием.

Один из вариантов заключается в определении лямбда внутри конструктора как обработчика события, и в этом случае TypeScript создает своего рода закрытие со скрытым _this псевдонимом.

class Editor {
    textarea: JQuery;

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

Мой вопрос в том, есть ли другой способ получить доступ к этой ссылке в обработчике событий на основе метода с помощью TypeScript, чтобы преодолеть это поведение jQuery?

Ответ 1

Итак, как указано, нет механизма TypeScript для обеспечения того, что метод всегда связан с ним указателем this (и это не просто проблема jQuery). Это не значит, что нет достаточно простого способ решения этой проблемы. Вам нужно создать прокси для вашего метода, который восстанавливает указатель this перед вызовом вашего обратного вызова. Затем вам необходимо обернуть свой обратный вызов этим прокси-сервером, прежде чем передавать его в событие. jQuery имеет встроенный механизм для этого, называемый jQuery.proxy(). Вот пример вашего вышеуказанного кода с использованием этого метода (обратите внимание на добавленный вызов $.proxy().)

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

Это разумное решение, но я лично обнаружил, что разработчики часто забывают включить прокси-вызов, поэтому я придумал альтернативное решение на основе TypeScript для этой проблемы. Использование класса HasCallbacks ниже всего, что вам нужно, это вывести свой класс из HasCallbacks, а затем любые методы с префиксом 'cb_' будут иметь постоянный указатель this. Вы просто не можете вызвать этот метод с другим указателем this, который в большинстве случаев является предпочтительным. Любой из этих механизмов работает так, как вам удобнее.

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

Ответ 2

Объем this сохраняется при использовании синтаксиса функции () => { ... } - здесь приведен пример из TypeScript Для программистов JavaScript.

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

Обратите внимание, что this.text дает вам Text from outer function, потому что синтаксис функции стрелки сохраняет "лексическую область".

Ответ 3

Как описано в некоторых других ответах, использование синтаксиса стрелки для определения функции приводит к тому, что ссылки на this всегда ссылаются на охватывающий класс.

Итак, чтобы ответить на ваш вопрос, вот два простых обходных пути.

Ссылка на метод с использованием синтаксиса стрелок

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

Определите метод, используя синтаксис стрелки

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

Ответ 4

Вы можете связать функцию-член с его экземпляром в конструкторе.

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

Кроме того, просто привяжите его, когда вы добавите обработчик.

        this.textarea.focusin(onFocusIn.bind(this));

Ответ 5

Попробуйте это

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
    }

}

Ответ 6

Решение Стивена Икмана удобно, но неполно. Ответы Дэнни Беккета и Сэма более короткие и более ручные и в том же общем случае не имеют обратного вызова, который требует как динамического, так и лексического охвата "this" одновременно. Перейдите к моему коду, если мое объяснение ниже: TL, DR...

Мне нужно сохранить "this" для динамического охвата для использования с обратными вызовами библиотеки, и мне нужно иметь "this" с лексической областью для экземпляра класса. Я утверждаю, что наиболее элегантно передавать экземпляр в генератор обратного вызова, эффективно позволяя закрывать параметр над экземпляром класса. Компилятор говорит вам, если вы пропустили это. Я использую соглашение о вызове параметра лексической области "externalThis", но "я" или другое имя может быть лучше.

Использование ключевого слова "this" украдено из мира OO, и когда TypeScript принял его (по моим предположениям из ECMAScript 6), они объединили концепцию с лексической областью и концепцию с динамической областью, когда метод вызываемый другим объектом. Я немного рассердился на это; Я бы предпочел ключевое слово "self" в TypeScript, чтобы я мог передать экземпляр объекта с лексико-ориентированным объектом. В качестве альтернативы, JS можно переопределить, чтобы потребовать явный параметр "вызывающего" первого положения, когда это необходимо (и, таким образом, сломать все веб-страницы одним махом).

Здесь мое решение (вырезано из большого класса). Возьмите gander, в частности, в том, как называются методы, и тело "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;
        }
    }

}

Ответ 7

TypeScript не предоставляет никакого дополнительного способа (за пределами обычного JavaScript-средства), чтобы вернуться к справочной информации this, отличной от this удобства переназначения, содержащегося в синтаксисе лямбда жирной стрелки (что допустимо с точки зрения обратной совместимости, поскольку никакой существующий JS-код не может использовать выражение =>).

Вы можете опубликовать предложение на сайте CodePlex, но с точки зрения дизайна языка, вероятно, не так много, что может произойти здесь, поскольку любое подходящее ключевое слово, которое может предоставить компилятор, уже может быть использовано с помощью кода существующего JavaScript.

Ответ 8

Вы можете использовать функцию js eval: var realThis = eval('this');

Ответ 9

У меня возникла аналогичная проблема. Я думаю, вы можете использовать .each() во многих случаях, чтобы сохранить this как другое значение для последующих событий.

Способ JavaScript:

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

Способ TypeScript:

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

Я надеюсь, что это поможет кому-то.

Ответ 10

Вы можете сохранить свою ссылку на this в другой переменной.. self возможно и получить доступ к ссылке таким образом. Я не использую typescript, но это метод, который был успешным для меня с javascript в прошлом.

Ответ 12

Существует гораздо более простое решение, чем все приведенные выше ответы. В основном мы возвращаемся к JavaScript с помощью ключевой словарной функции вместо использования конструкции "= > ", которая переведет 'this' в класс 'this'

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