【问题标题】:Is there an alias for 'this' in TypeScript?TypeScript 中是否有“this”的别名?
【发布时间】:2012-10-06 03:26:15
【问题描述】:

我尝试在 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 行为?

【问题讨论】:

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

标签: jquery typescript


【解决方案1】:

this 的范围在使用箭头函数语法 () =&gt; { ... } 时被保留 - 这是一个取自 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”的上下文,因此您显示的内容并不真正适用。
  • @PaulMendoza 实际上如果你把它改成setTimeout( function () { alert(this.text); }, 1000); 你会得到undefined
  • 你是对的。我的错。不知何故 ()=> 告诉编译器这意味着与执行 function() { } 不同
  • 这太棒了。道具!这在幕后所做的是在方法顶部创建一个var _this = this,然后在匿名函数中引用_this
【解决方案2】:

如前所述,没有 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({});

【讨论】:

  • $.proxy() 电话对我的情况来说是一个很好的解决方案。拥有this 别名会使 TS 复杂化,并且正如其他人指出的那样,很难以向后兼容的方式实现。
  • 我想补充一点,我知道 HasCallbacks 的代码看起来有点吓人,但它所做的只是通过你的类成员并使用代理预先连接它们。如果您的项目很小,最好使用 $.proxy()。但是,如果您的项目很大,则 HasCallbacks 类将导致下载到客户端的代码更少(您没有所有这些额外的 $.proxy() 调用)并且更不容易出错。执行方面,这两种方法的性能大致相同(HasCallbacks 对每个类都有一次性枚举开销),所以这真的是你的选择。
  • 我认为您的回答可能会产生误导,因为它给我的印象是 TypeScript 不支持这一点,而如果您只是使用箭头语法定义您的方法,它实际上确实支持。
  • 这个答案可能提供了一个解决方案但不正确,因为 TypeScript 确实提供了一种机制来维护它的词法范围。有关使用箭头函数解决此问题的 2 个选项,请参阅 Sam 的答案。
【解决方案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));
    

    【讨论】:

    • 这是我最喜欢的方法,但不幸的是,对于旧版浏览器,您需要一个用于 bind 的 polyfill。
    【解决方案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】:

      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
      【解决方案7】:

      TypeScript 没有提供任何额外的方法(除了常规的 JavaScript 方法)来返回“真正的”this 引用,而不是 this 胖箭头 lambda 语法中提供的重新映射便利(这是允许的back-compat 视角,因为没有现有的 JS 代码可以使用 =&gt; 表达式)。

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

      【讨论】:

        【解决方案8】:

        我也遇到过类似的问题。我认为您可以在许多情况下使用.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');
          });
        });
        

        我希望这对某人有所帮助。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 此解决方案的唯一问题是回调方法中的this 引用掩盖了底层对象的this 引用,因此无法通过正确的@987654325 访问新属性@.
            • 虽然这是一种有效的方法,但我认为最好使用 TypeScript 提供的内置功能来实现这一点。
            • Todd:这正是您在输入新范围(以及因此新的“this”)之前将正确的“this”存储在变量中的原因。这是一个非常常见的 javascript 问题,而不仅仅是打字稿 (@Sam)。不知道为什么投反对票,但一切都很好。
            • @SheridanBulger,否决票是因为这是一个 TypeScript 问题,TypeScript 已经提供了自己的方法来实现这一点。我认为利用语言特性而不是自己重新实现它们通常是一个好主意。
            • @SheridanBulger Downvoted...这不是 TypeScript 答案。
            【解决方案11】:

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

            【讨论】:

              【解决方案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'); 
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-02-16
                • 2022-07-07
                • 2012-08-15
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-11-29
                相关资源
                最近更新 更多