setTimeout在nodejs和Chrome中的不同行为

时间:2018-11-28 22:46:24

标签: javascript node.js

代码示例为-

global.a = 'aaa';

const obj = {
    a: 'a',
    desc() {
        console.log(this);
        console.log(this.a);
    }
}

setTimeout(obj.desc, 2000)

在nodejs中运行此代码时,得到以下输出:

Timeout {
  _called: true,
  _idleTimeout: 2000,
  _idlePrev: null,
  _idleNext: null,
  _idleStart: 79,
  _onTimeout: [Function: desc],
  _timerArgs: undefined,
  _repeat: null,
  _destroyed: false,
  [Symbol(asyncId)]: 6,
  [Symbol(triggerAsyncId)]: 1 }
undefined

但是相同的代码在Chrome / Firefox中将global更改为window并打印aaawindow对象,这就是该MDN doc说,这是我的期望。

给我的印象是nodejs和Chrome都使用Google的v8 JS引擎执行JavaScript。那么输出为何不同?还有什么呢?我尝试搜索,但找不到满意的答案。

节点版本-v9.10。
Chrome版本-版本70.0.3538.110

2 个答案:

答案 0 :(得分:2)

node.js具有自己的计时器功能,这与浏览器实现不同(尽管它们通常可以相同的方式使用)。确实没有记录,但是当您使用setTimeout时,它将创建一个Timer类的实例,并将回调作为参数传递。此回调分配给Timer实例的属性,该属性稍后使用:

这意味着,node.js中的计时器的this上下文被偶然设置为Timer实例本身。浏览器显然做些不同的事情。

这在原始代码中主要是语义问题:当您从对象传递函数属性时,它们将失去上下文。您可以使用.bind来保留上下文,或使用另一个函数直接在desc上调用obj来保留上下文。

setTimeout(obj.desc.bind(obj), 2000);
setTimeout(() => obj.desc(), 2000);

这两个都将在node.js和浏览器环境中记录objobj.a

答案 1 :(得分:1)

要回答您的问题,我们必须查阅计时器的源代码,因为NodeJS的setTimeout和香草JS的setTimeout并不相同。

如果我们查看here,将找到setTimeout的定义。我们必须注意传入的回调会发生什么:

会发生什么,它将被传递到Timeout构造函数中:

const timeout = new Timeout(callback, after, args, false);

好吧,Timeout类是什么?我们可以找到here。注意这一行:

this._onTimeout = callback;

好吧,我们将回调保留在实例成员中,所以我怀疑这就是调用它的方式。

如果我们返回另一个源文件并搜索_onTimeout,则会发现以下行:

timer._onTimeout();

因此,在这种情况下,由于回调的调用方式,timer实际上是this所指的内容(正如您从回调中记录this所注意到的那样) ,并且由于计时器(或Timeout实例)没有任何此类属性(a),因此它记录undefined

在普通JS中,它的行为有所不同,this在您的情况下是指window

您可能知道,您可以通过简单地将回调绑定到obj中的setTimeout来解决此矛盾,如下所示:

setTimeout(obj.desc.bind(obj), 2000);