TypeScript中是否有“ this”的别名?


75

我试图用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对象。

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


3
使用()=> {}的答案看起来不错
Daniel Little

Answers:


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

1
$.proxy()电话对于我的情况是一个很好的解决方案。拥有this别名将使TS复杂化,并且正如其他人指出的那样,以向后兼容的方式很难做到。
2012年

1
以为我要补充一点,我知道HasCallbacks的代码看起来有些吓人,但是其所有工作都是通过您的类成员并使用代理预连接它们。如果您的项目很小,最好使用$ .proxy()。但是,如果您的项目很大,则HasCallbacks类将导致更少的代码下载到客户端(您没有所有那些额外的$ .proxy()调用),并且出错的可能性也较小。在执行方面,两种方法的性能大致相同(HasCallbacks对每个类都有一次枚举的开销),因此,这实际上是您的选择。
史蒂文·艾克曼

3
我认为您的答案可能会引起误解,因为它给我的印象是TypeScript不支持此功能,而如果您仅使用箭头语法定义方法,则实际上可以。
2013年

这个答案可能提供了一个解决方案,但它是不正确的,因为TypeScript确实提供了一种用于对此进行词汇范围界定的机制。有关使用箭头函数解决此问题的2个选项,请参见Sam的答案。
bingles 2015年

100

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

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

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


您的示例不会修改“ this”的上下文,因此您显示的内容实际上并不适用。
Paul Mendoza

@PaulMendoza实际上,如果将其更改为setTimeout( function () { alert(this.text); }, 1000);,则会得到undefined
Fenton

你是对的。我的错。()=>以某种方式告诉编译器,这与执行function(){}有所不同
Paul Mendoza

9
太好了 道具!幕后操作是var _this = this在方法顶部创建一个,然后_this在匿名函数中进行引用。
理查德·罗特

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

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

这是我最喜欢的方法,但不幸的是,bind对于较旧的浏览器,您需要使用polyfill 。
antoine129

3

尝试这个

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

史蒂文·艾克曼(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;
        }
    }

}

尽管此实现很难看,但它是超越范围劫持的好方法。+1
凯莉·肯德尔

2

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

你可以发表建议,CodePlex上的网站,但是从语言设计的角度来看有可能不会太多,可以在这里发生的,因为任何理智的关键字,编译器可以提供可能已在由现存的JavaScript代码使用。


2

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


1

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

我希望这可以帮助别人。


0

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


2
此解决方案的唯一问题是,this回调方法中的this引用会掩盖基础对象的引用,因此无法通过正确的访问新属性this
2012年

1
尽管这是一种有效的方法,但我认为最好使用TypeScript提供的内置功能来实现这一目标。
2013年

2
Todd:这就是为什么在输入新作用域(因此要输入新的“ this”)之前将正确的“ this”存储在变量中的原因。这是一个非常常见的javascript问题,而不仅仅是打字稿(@Sam)。不知道为什么要投票反对,但一切都很好。
Sheridan Bulger 2014年

@SheridanBulger,不好意思是因为这是一个TypeScript问题,而TypeScript已经提供了自己的方式来实现。我认为使用语言功能而不是自己重新实现它们通常是一个好主意。
2014年

@SheridanBulger Downvoted ...这不是TypeScript的答案。
BenjaminPaul


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'); 
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.