代码示例为-
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
并打印aaa
和window
对象,这就是该MDN doc说,这是我的期望。
给我的印象是nodejs和Chrome都使用Google的v8 JS引擎执行JavaScript。那么输出为何不同?还有什么呢?我尝试搜索,但找不到满意的答案。
节点版本-v9.10。
Chrome版本-版本70.0.3538.110
答案 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和浏览器环境中记录obj
和obj.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);