76

我试图在 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 会覆盖 this 引用并将其设置为与事件关联的 DOM 对象。

一种替代方法是在构造函数中定义一个 lambda 作为事件处理程序,在这种情况下,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 在基于方法的事件处理程序中访问 this 引用,以克服这种 jQuery 行为?

4

12 回答 12

102

使用箭头函数语法时,范围this被保留() => { ... }- 这是一个来自TypeScript For JavaScript Programmers的示例。

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

请注意,这this.textText from outer function因为箭头函数语法保留了“词法范围”。

于 2012-10-06T14:25:27.987 回答
22

因此,如前所述,没有 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({});
于 2012-10-06T05:28:51.400 回答
21

正如其他一些答案所涵盖的,使用箭头语法定义函数会导致引用this始终引用封闭类。

因此,为了回答您的问题,这里有两个简单的解决方法。

使用箭头语法引用方法

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

使用箭头语法定义方法

onFocusIn = (e: JQueryEventObject) => {
    var height = this.textarea.css('height');
}
于 2013-12-23T04:27:01.790 回答
6

您可以在构造函数中将成员函数绑定到其实例。

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));
于 2014-05-20T14:47:32.533 回答
4

试试这个

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
    }

}
于 2013-05-24T01:47:56.510 回答
3

Steven Ickman 的解决方案很方便,但不完整。Danny Becket 和 Sam 的答案更短且更手动,并且在具有同时需要动态和词法范围“this”的回调的相同一般情况下失败。如果我在下面的解释是 TL;DR,请跳到我的代码...

我需要为动态范围保留“this”以与库回调一起使用,并且我需要一个“this”与类实例的词法范围。我认为将实例传递给回调生成器是最优雅的,有效地让参数关闭类实例。如果您错过了这样做,编译器会告诉您。我使用调用词法范围参数“outerThis”的约定,但“self”或其他名称可能更好。

“this”关键字的使用是从 OO 世界中偷来的,当 TypeScript 采用它时(我推测来自 ECMAScript 6 规范),每当一个方法被不同的实体调用时,他们将词法范围的概念和动态范围的概念混为一谈. 我对此有点恼火;我更喜欢 TypeScript 中的“self”关键字,这样我就可以将词法范围的对象实例从它上面移开。或者,可以将 JS 重新定义为在需要时需要显式的第一位置“调用者”参数(从而一举破坏所有网页)。

这是我的解决方案(从大班中摘录)。特别看一下方法的调用方式,尤其是“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;
        }
    }

}
于 2014-03-21T19:33:55.557 回答
2

TypeScript 没有提供任何额外的方式(除了常规的 JavaScript 手段)来返回到“真实”this引用,而不是this在胖箭头 lambda 语法中提供的重新映射便利(从向后兼容的角度来看这是允许的,因为没有现有的 JS 代码可以使用=>表达式)。

您可以向 CodePlex 站点发布建议,但从语言设计的角度来看,这里可能不会发生太多事情,因为编译器可以提供的任何理智的关键字都可能已经被现存的 JavaScript 代码使用。

于 2012-10-06T03:35:17.010 回答
2

我也遇到过类似的问题。我认为您可以.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');
  });
});

我希望这可以帮助别人。

于 2014-06-16T08:17:29.820 回答
2

您可以使用 js eval 函数:var realThis = eval('this');

于 2014-09-20T12:13:18.113 回答
0

您可以将您的引用存储this在另一个变量中......self也许,并以这种方式访问​​引用。我不使用打字稿,但这是一种过去使用香草 javascript 对我来说很成功的方法。

于 2012-10-06T03:30:40.597 回答
0

查看这篇博文http://lumpofcode.blogspot.com/2012/10/typescript-dart-google-web-toolkit-and.html,它详细讨论了在 TypeScript 类内和跨 TypeScript 类组织调用的技术。

于 2014-12-06T01:49:47.120 回答
0

有比上述所有答案更简单的解决方案。基本上,我们通过使用关键字函数而不是使用 '=>' 构造来回退 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'); 
    }
}
于 2015-05-11T18:30:42.367 回答