Javascript 在“setTimeout”中使用成员函数时,将babel类保留为“this”`

Javascript 在“setTimeout”中使用成员函数时,将babel类保留为“this”`,javascript,ecmascript-6,this,settimeout,babeljs,Javascript,Ecmascript 6,This,Settimeout,Babeljs,我有一个ES2015类,叫做Foo,它至少有两个成员函数,bar和baz。在bar中,有一个对setTimeout的调用,其第一个参数是this.baz。在这里工作正常,我在调试器中检查了它,this确实引用了我的类的实例。(事实上,由于我使用的是babel,因此我会事先用一个\u this=this替换,但无论如何,正确的东西会被传递到setTimeout,已确认。) 问题是当setTimeout回调启动时,它调用正确的函数baz,但是baz内部的this的值指的是窗口。巴贝尔试图在baz的开

我有一个ES2015类,叫做
Foo
,它至少有两个成员函数,
bar
baz
。在
bar
中,有一个对
setTimeout
的调用,其第一个参数是
this.baz
。在这里工作正常,我在调试器中检查了它,
this
确实引用了我的类的实例。(事实上,由于我使用的是babel,因此我会事先用一个
\u this=this
替换,但无论如何,正确的东西会被传递到
setTimeout
,已确认。)

问题是当
setTimeout
回调启动时,它调用正确的函数
baz
,但是
baz
内部的
this
的值指的是
窗口。巴贝尔试图在
baz
的开头做一个
\u this2=this
,但似乎已经太晚了

所以我的问题出现了,在传递函数
baz
和调用它的时间之间的某个地方,它失去了它的
this
作用域。我的问题是,我在ES2015或babel方面是否做错了什么?我觉得这是一个足够普遍的用例,不需要太多的压力。就我个人而言,我希望通过
Promise
完成这一切,但由于业务需求,我不能一次添加太多新的JS内容

或者,是否有一个标准的惯用语来传递我在这里需要的
this
的范围?必须将成员函数的调用对象作为其参数之一传入,这看起来非常混乱和违反直觉

以下是一个供参考的最小工作示例:

class Foo{
    bar(){
        setTimeout(this.baz, 1000);
    }
    baz(){
        console.log("this message should repeat roughly once per second");
        this.bar();
    }
}
下面是我在一个非常简单的页面上使用它的屏幕截图,以及错误消息:

编辑:我必须反对将我的问题标记为重复问题。当然,在问这个问题之前,我看过
setTimeout
问题。然而,我的问题中基于ES2015和
类的方面是相关和重要的,因为babel中类的ES2015语法转换改变了
这个
的明显行为。我的问题是,是否有另一个ES2015设计模式来处理这个问题,以及为什么通过将成员函数作为一个第一类值传递给外部调用而打破了直观的
抽象/封装。我从@FelixKing下面的评论中获得了最有意义的见解,我将在这里为子孙后代重复这一点(以防其他人感到疑惑):

是否讨论了自动绑定类方法(如Python中的方法) 但最终决定反对,可能会与 语言的其余部分。还有一个问题是,这是否会成为现实 可以在不影响内存/性能的情况下自动绑定方法

我的问题是,我在ES2015或babel方面是否做错了什么

实际上,这是一种预期的JavaScript行为,并且与该语言中如何分配
有关

考虑以下代码(无ES6,无巴别塔…):

如您所见,
是在调用函数时定义的,而不是在声明函数时定义的,它取决于调用函数的方式

这正是
setTimeout
内部或任何接收函数作为参数的函数中发生的情况:

/* fake */
function setTimeout(fnCallback, time) {
    /* wait and when the time comes, call your callback like this: */
    fnCallback(); //'this' will be Window/global
}
“变通办法”: 为了传递所需的上下文(在上面的示例中),我们可以强制上下文:

  • 使用
    .bind

    var callback = obj.key2.bind(obj);
    callback(); //will print obj
    
  • 或使用
    。调用

    var callback = obj.key2;
    callback.call(obj); //will print obj
    
  • 或者我们可以通过任意函数从内部调用对象:

    setTimeout(function() {
       //here, 'this' is Window/global, because the anonymous function is being called from a callback assignment
       obj.key2(); //will print obj
    }, 3000);
    
    在你的例子中 因此,在您的示例中,为了正确设置
    setTimeout
    回调并确保
    baz()
    将接收类上下文,您可以:

  • 将函数设置为回调时绑定该函数:

    setTimeout(this.baz.bind(this), 1000);
    
  • 在类构造函数中,
    bind
    baz
    方法绑定一次;因此,每次调用它时,都会为其分配类上下文。像这样:

    class Foo{
        constructor() {
            this.baz = this.baz.bind(this)
        }
        bar(){
            setTimeout(this.baz, 1000);
        }
        baz(){
            console.log("this message should repeat roughly once per second");
            this.bar();
        }
    }     
    
  • 使用
    箭头功能
    。另一种指定
    上下文的方法是使用
    箭头函数
    ,实际上,确保
    赋值通过词法范围完成(不再在函数调用中,而是在函数声明中)

    不同于:

    setTimeout(function() { this.baz(); }, 1000);
    //                      ^^^^
    //                      'this' here is Window/global, will thrown undefined method
    

  • 尝试设置超时(this.baz.bind(this),1000)这是一个有趣的解决方法,看起来效果不错。因此,问题似乎只在于我对
    setTimeout
    的使用,这是一个尊重
    this
    动态范围的函数,而我只是试图在
    Foo
    的上下文中使用它。所以,也许
    setTimeout
    只是打破了我假定的抽象?根据您的经验,这是一个常见问题吗?为了澄清,如果我将成员函数作为一级值传递给并希望稍后调用它,我需要手动绑定
    ?我只想确认这是一个通用的设计模式,而不是我代码中的侥幸。@AndyRay上下文很重要,因为babel进行语法转换,从而改变成员函数中的
    this
    的行为。显然,当您将成员函数作为第一类值调用时,此行为将被重写。我的问题不是你标记的问题的重复。请更仔细地标记重复项。当然,我大体上理解了动态作用域在传统javascript中的工作原理(参见“You't Know JS:Scope&Closes”一书)。但是我要做的区别是,类内部的
    这个
    感觉它应该在词汇范围内引用相关类的实例,而巴贝尔在很大程度上很好地处理了这一点。ES6和Babel是m的重要组成部分
    setTimeout(() => this.baz(), 1000);
    //               ^^^^
    //               'this' here is your class, will pass your class as 'this'
    //                to the baz() method, due to the dot before
    
    setTimeout(function() { this.baz(); }, 1000);
    //                      ^^^^
    //                      'this' here is Window/global, will thrown undefined method