提问人:Dan Lew 提问时间:4/23/2009 最后编辑:refactorDan Lew 更新时间:12/3/2021 访问量:388171
为什么 setTimeout(fn, 0) 有时很有用?
Why is setTimeout(fn, 0) sometimes useful?
问:
我最近遇到了一个相当讨厌的错误,其中代码通过 JavaScript 动态加载。此动态加载具有预选值。在 IE6 中,我们已经有代码来修复 selected ,因为有时 的值会与 selected 的 属性不同步,如下所示:<select>
<select>
<option>
<select>
selectedIndex
<option>
index
field.selectedIndex = element.index;
但是,此代码不起作用。即使字段设置正确,最终也会选择错误的索引。但是,如果我在正确的时间插入语句,则会选择正确的选项。考虑到这可能是某种时间问题,我尝试了一些我以前在代码中看到的随机方法:selectedIndex
alert()
var wrapFn = (function() {
var myField = field;
var myElement = element;
return function() {
myField.selectedIndex = myElement.index;
}
})();
setTimeout(wrapFn, 0);
这奏效了!
我已经为我的问题找到了解决方案,但我感到不安的是,我不知道为什么这可以解决我的问题。有人有官方解释吗?我通过使用“稍后”调用我的函数来避免什么浏览器问题?setTimeout()
答:
setTimeout()
在加载 DOM 元素之前为您争取一些时间,即使设置为 0。
看看这个: setTimeout
通过调用 setTimeout,您可以让页面有时间对用户正在执行的任何操作做出反应。这对于在页面加载期间运行的函数特别有用。
在问题中,存在以下竞争条件:
- 浏览器尝试初始化下拉列表,准备更新其选定的索引,以及
- 用于设置所选索引的代码
您的代码一直在赢得这场比赛,并尝试在浏览器准备就绪之前设置下拉选择,这意味着会出现错误。
之所以存在这种竞争,是因为 JavaScript 具有与页面呈现共享的单个执行线程。实际上,运行 JavaScript 会阻止 DOM 的更新。
您的解决方法是:
setTimeout(callback, 0)
使用回调调用,并将 0 作为第二个参数,将调度回调在尽可能短的延迟之后异步运行 - 当选项卡具有焦点且执行的 JavaScript 线程不忙时,这将是大约 10 毫秒。setTimeout
因此,OP 的解决方案是将所选索引的设置延迟约 10 毫秒。这给了浏览器一个初始化 DOM 的机会,修复了这个错误。
每个版本的 Internet Explorer 都表现出古怪的行为,这种解决方法有时是必要的。或者,它可能是 OP 代码库中的真正错误。
请看菲利普·罗伯茨(Philip Roberts)的演讲“事件循环到底是什么?”,以获得更详尽的解释。
评论
看看 John Resig 关于 JavaScript 计时器如何工作的文章。设置超时时,它实际上会将异步代码排队,直到引擎执行当前调用堆栈。
这样做的一个原因是将代码的执行推迟到一个单独的后续事件循环中。在响应某种类型的浏览器事件(例如鼠标单击)时,有时只有在处理当前事件后才需要执行操作。该设施是最简单的方法。setTimeout()
现在是 2015 年,我应该注意到还有 ,这并不完全相同,但它非常接近,值得一提。requestAnimationFrame()
setTimeout(fn, 0)
评论
由于它被传递了 的持续时间,我想这是为了从执行流程中删除传递给 的代码。因此,如果它是一个可能需要一段时间的函数,它不会阻止后续代码的执行。0
setTimeout
评论
前言:
其他一些答案是正确的,但实际上并不能说明要解决的问题是什么,所以我创建了这个答案来展示这个详细的说明。
因此,我发布了一个详细的演练,介绍浏览器的功能以及使用 setTimeout()
如何提供帮助。它看起来很长,但实际上非常简单明了——我只是把它做得非常详细。
更新:我制作了一个 JSFiddle 来现场演示下面的解释:http://jsfiddle.net/C2YBE/31/ .非常感谢 @ThangChung 帮助启动它。
UPDATE2:为了以防万一 JSFiddle 网站死机或删除代码,我在最后添加了代码。
细节:
想象一下,一个带有“做某事”按钮和结果 div 的 Web 应用程序。
“do something”按钮的处理程序调用一个函数“LongCalc()”,该函数执行 2 项操作:onClick
进行很长的计算(例如需要 3 分钟)
将计算结果打印到结果 div 中。
现在,您的用户开始测试这一点,单击“做某事”按钮,页面在那里似乎无所事事 3 分钟,他们变得焦躁不安,再次单击按钮,等待 1 分钟,没有任何反应,再次单击按钮......
问题很明显 - 你想要一个“状态”DIV,它显示正在发生的事情。让我们看看它是如何工作的。
因此,您添加一个“状态”DIV(最初为空),并修改处理程序(函数)以执行 4 项操作:onclick
LongCalc()
填充状态“正在计算...可能需要 ~3 分钟“进入状态 DIV
进行很长的计算(例如需要 3 分钟)
将计算结果打印到结果 div 中。
将状态“计算完成”填充到状态 DIV 中
而且,您很乐意将应用程序提供给用户重新测试。
他们回到你身边,看起来很生气。并解释说,当他们单击该按钮时,状态 DIV 从未更新为“正在计算...”地位!!!
你挠了挠头,在 StackOverflow 上四处询问(或阅读文档或谷歌),并意识到问题所在:
浏览器将事件生成的所有“TODO”任务(包括 UI 任务和 JavaScript 命令)放入单个队列中。不幸的是,使用新的“正在计算......”重新绘制“状态”DIV。value 是一个单独的 TODO,它位于队列的末尾!
下面是用户测试期间的事件细分,以及每个事件后队列的内容:
- 队列:
[Empty]
- 事件:单击按钮。事件后排队:
[Execute OnClick handler(lines 1-4)]
- 事件:在 OnClick 处理程序中执行第一行(例如更改状态 DIV 值)。事件后的队列:。请注意,虽然 DOM 更改是即时发生的,但要重新绘制相应的 DOM 元素,您需要一个由 DOM 更改触发的新事件,该事件位于队列的末尾。
[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]
- 问题!!! 问题!!!详情如下。
- 事件:在处理程序中执行第二行(计算)。在以下位置排队: .
[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
- 事件:在处理程序中执行第 3 行(填充结果 DIV)。在以下位置排队: .
[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
- 事件:在处理程序中执行第 4 行(使用“DONE”填充状态 DIV)。队列:。
[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
- 事件:从处理程序 sub 执行隐含的执行。我们将“执行 OnClick 处理程序”从队列中移除,并开始执行队列中的下一项。
return
onclick
- 注意:由于我们已经完成了计算,因此用户已经过去了 3 分钟。重新绘制事件尚未发生!!
- 事件:使用“正在计算”值重新绘制状态 DIV。我们进行重新绘制并将其从队列中移除。
- 事件:使用结果值重新绘制结果 DIV。我们进行重新绘制并将其从队列中移除。
- 事件:使用“完成”值重新绘制状态 DIV。我们进行重新绘制并将其从队列中移除。 眼尖的观众甚至可能会注意到“状态 DIV”的“正在计算”值闪烁了几分之一微秒 - 计算完成后
因此,潜在的问题是“状态”DIV 的重新绘制事件被放置在队列的最后,在需要 3 分钟的“执行行 2”事件之后,因此实际的重新绘制直到计算完成之后才会发生。
救援来了.它有什么帮助?因为通过调用长时间执行的代码,您实际上创建了 2 个事件:执行本身,以及(由于 0 超时)正在执行的代码的单独队列条目。setTimeout()
setTimeout
setTimeout
因此,为了解决您的问题,您将处理程序修改为 TWO 语句(在新函数中或只是 中的一个块):onClick
onClick
填充状态“正在计算...可能需要 ~3 分钟“进入状态 DIV
执行
setTimeout(
) 超时 0 并调用LongCalc()
函数。LongCalc()
函数与上次几乎相同,但显然没有“计算...”状态 DIV 更新作为第一步;而是立即开始计算。
那么,事件序列和队列现在是什么样子的呢?
- 队列:
[Empty]
- 事件:单击按钮。事件后排队:
[Execute OnClick handler(status update, setTimeout() call)]
- 事件:在 OnClick 处理程序中执行第一行(例如更改状态 DIV 值)。事件后的队列:。
[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
- 事件:在处理程序中执行第二行(setTimeout 调用)。在以下位置排队: .队列在 0 秒内没有任何新内容。
[re-draw Status DIV with "Calculating" value]
- 事件:超时警报在 0 秒后响起。在以下位置排队: .
[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
- 事件:使用“正在计算”值重新绘制状态 DIV。在以下位置排队: .请注意,此重绘事件实际上可能在警报响起之前发生,这同样有效。
[execute LongCalc (lines 1-3)]
- ...
万岁!状态 DIV 刚刚更新为“正在计算...”在计算开始之前!!
下面是 JSFiddle 中的示例代码,演示了这些示例: http://jsfiddle.net/C2YBE/31/ :
HTML 代码:
<table border=1>
<tr><td><button id='do'>Do long calc - bad status!</button></td>
<td><div id='status'>Not Calculating yet.</div></td>
</tr>
<tr><td><button id='do_ok'>Do long calc - good status!</button></td>
<td><div id='status_ok'>Not Calculating yet.</div></td>
</tr>
</table>
JavaScript 代码:(在 onDomReady
上执行,可能需要 jQuery 1.9)
function long_running(status_div) {
var result = 0;
// Use 1000/700/300 limits in Chrome,
// 300/100/100 in IE8,
// 1000/500/200 in FireFox
// I have no idea why identical runtimes fail on diff browsers.
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 700; j++) {
for (var k = 0; k < 300; k++) {
result = result + i + j + k;
}
}
}
$(status_div).text('calculation done');
}
// Assign events to buttons
$('#do').on('click', function () {
$('#status').text('calculating....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('calculating....');
// This works on IE8. Works in Chrome
// Does NOT work in FireFox 25 with timeout =0 or =1
// DOES work in FF if you change timeout from 0 to 500
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});
评论
这样做的另一件事是将函数调用推送到堆栈的底部,从而防止在递归调用函数时堆栈溢出。这具有循环的效果,但允许 JavaScript 引擎触发其他异步计时器。while
评论
push the function invocation to the bottom of the stack
stack
setTimeout
setTimeout 有用的其他一些情况:
您希望将长时间运行的循环或计算分解为更小的组件,以便浏览器不会出现“冻结”或说“页面上的脚本繁忙”。
您希望在单击时禁用表单提交按钮,但如果在 onClick 处理程序中禁用该按钮,则不会提交表单。时间为零的 setTimeout 可以解决问题,允许事件结束,表单开始提交,然后可以禁用您的按钮。
评论
浏览器有一个称为“主线程”的进程,它负责执行一些 JavaScript 任务、UI 更新,例如:绘画、重绘、重排等。 JavaScript 任务排队到消息队列,然后调度到浏览器的主线程进行执行。 当在主线程繁忙时生成 UI 更新时,任务将添加到消息队列中。
评论
add this fn to the end of the queue
setTimeout
关于执行循环和在其他一些代码完成之前呈现 DOM 的答案是正确的。JavaScript 中的零秒超时有助于使代码成为伪多线程,即使它不是。
我想补充一点,JavaScript 中跨浏览器/跨平台零秒超时的最佳值实际上是大约 20 毫秒而不是 0(零),因为由于 AMD 芯片的时钟限制,许多移动浏览器无法注册小于 20 毫秒的超时。
此外,不涉及 DOM 操作的长时间运行的进程现在应该发送给 Web Worker,因为它们提供了真正的 JavaScript 多线程执行。
评论
这是一个老问题,老答案。我想对这个问题进行新的审视,并回答为什么会发生这种情况,而不是为什么这有用。
所以你有两个函数:
var f1 = function () {
setTimeout(function(){
console.log("f1", "First function call...");
}, 0);
};
var f2 = function () {
console.log("f2", "Second call...");
};
然后按以下顺序调用它们,只是为了查看第二个首先执行。f1(); f2();
原因如下:不可能有 0 毫秒的时间延迟。最小值由浏览器确定,不是 0 毫秒。从历史上看,浏览器将此最小值设置为 10 毫秒,但 HTML5 规范和现代浏览器将其设置为 4 毫秒。setTimeout
如果嵌套级别大于 5,且超时小于 4,则 将超时增加到 4。
同样来自mozilla:
若要在现代浏览器中实现 0 毫秒超时,可以使用 window.postMessage(),如下所述。
P.S. 信息是在阅读以下文章后获取的。
评论
setTimeout(fn,0)
setTimeout
setInterval
setInterval
setTimeout
这里有相互矛盾的赞成答案,没有证据,就没有办法知道该相信谁。这证明@DVK是对的,@SalvadorDali是不正确的。后者声称:
“原因如下:不可能有带有时间的 setTimeout 延迟 0 毫秒。最小值由 浏览器,它不是 0 毫秒。从历史上看,浏览器会设置这个 最小到 10 毫秒,但 HTML5 规范和现代浏览器 将其设置为 4 毫秒。
4ms 最小超时与正在发生的事情无关。实际情况是 setTimeout 将回调函数推送到执行队列的末尾。如果在 setTimeout(callback, 0) 之后,您的阻塞代码需要几秒钟才能运行,则在阻塞代码完成之前,回调将在几秒钟内不会执行。请尝试以下代码:
function testSettimeout0 () {
var startTime = new Date().getTime()
console.log('setting timeout 0 callback at ' +sinceStart())
setTimeout(function(){
console.log('in timeout callback at ' +sinceStart())
}, 0)
console.log('starting blocking loop at ' +sinceStart())
while (sinceStart() < 3000) {
continue
}
console.log('blocking loop ended at ' +sinceStart())
return // functions below
function sinceStart () {
return new Date().getTime() - startTime
} // sinceStart
} // testSettimeout0
输出为:
setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033
评论
setTimout on 0 在设置延迟承诺的模式中也非常有用,您希望立即返回:
myObject.prototype.myMethodDeferred = function() {
var deferredObject = $.Deferred();
var that = this; // Because setTimeout won't work right with this
setTimeout(function() {
return myMethodActualWork.call(that, deferredObject);
}, 0);
return deferredObject.promise();
}
Javascript 是单线程应用程序,因此不允许同时运行函数,因此为了实现此事件循环,使用了循环。因此,setTimeout(fn, 0) 究竟做了什么,它变成了任务任务,当您的调用堆栈为空时,该任务将执行。我知道这个解释很无聊,所以我建议你看完这个视频,这将有助于你在浏览器中的工作原理。 请看这个视频:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ
这两个最受好评的答案都是错误的。查看关于并发模型和事件循环的 MDN 描述,应该会清楚发生了什么(MDN 资源是一个真正的宝石)。除了“解决”这个小问题之外,简单地使用就可以在代码中添加意想不到的问题。setTimeout
这里实际发生的事情并不是“浏览器可能还没有完全准备好,因为并发性”,或者基于“每一行都是一个添加到队列后面的事件”的东西。
DVK 提供的 jsfiddle 确实说明了一个问题,但他对此的解释是不正确的。
在他的代码中发生的情况是,他首先将事件处理程序附加到按钮上的事件。click
#do
然后,当您实际单击该按钮时,将创建一个引用事件处理程序函数,该函数被添加到 .当 到达此消息时,它会在堆栈上创建一个,并在 jsfiddle 中调用 click 事件处理程序的函数。message
message queue
event loop
frame
这就是它变得有趣的地方。我们习惯于认为 Javascript 是异步的,以至于我们很容易忽略这个小事实:任何帧都必须完整执行,然后才能执行下一帧。没有并发,人。
这是什么意思?这意味着,每当从消息队列调用函数时,它都会阻塞队列,直到它生成的堆栈被清空。或者,更一般地说,它会阻塞,直到函数返回。它阻止了一切,包括 DOM 渲染操作、滚动等等。如果要确认,只需尝试增加小提琴中长时间运行操作的持续时间(例如,再运行外循环 10 次),您会注意到,当它运行时,您无法滚动页面。如果它运行的时间足够长,您的浏览器会询问您是否要终止该进程,因为它会使页面无响应。帧正在执行,事件循环和消息队列将停滞,直到它完成。
那么,为什么文本的这种副作用没有更新呢?因为当你在 DOM 中更改了元素的值时——你可以在更改它后立即查看它的值,并看到它已被更改(这显示了为什么 DVK 的解释不正确)——浏览器正在等待堆栈耗尽(处理函数返回)并因此完成消息,以便它最终可以执行运行时添加的消息作为对我们的突变操作做出反应,并为了在UI中反映该突变。console.log()
on
这是因为我们实际上是在等待代码完成运行。我们没有说过“有人获取这个,然后调用这个函数并得到结果,谢谢,现在我已经完成了,所以imma return,现在做任何事情”,就像我们通常对基于事件的异步 Javascript 所做的那样。我们进入一个点击事件处理函数,我们更新一个 DOM 元素,我们调用另一个函数,另一个函数工作很长时间然后返回,然后我们更新同一个 DOM 元素,然后我们从初始函数返回,有效地清空堆栈。然后浏览器可以访问队列中的下一条消息,这很可能是我们通过触发一些内部“on-DOM-mutation”类型事件生成的消息。
浏览器 UI 无法(或选择不)更新 UI,直到当前执行的帧完成(函数已返回)。就我个人而言,我认为这与其说是限制,不如说是设计使然。
那为什么这件事会起作用呢?它之所以这样做,是因为它有效地从自己的帧中删除了对长时间运行的函数的调用,将其安排为稍后在上下文中执行,以便它本身可以立即返回并允许消息队列处理其他消息。这个想法是,我们在 Javascript 中更改 DOM 中的文本时触发的 UI“on update”消息现在位于为长时间运行的函数排队的消息之前,因此 UI 更新发生在我们阻止很长时间之前。setTimeout
window
请注意,a) 长时间运行的函数在运行时仍会阻止所有内容,并且 b) 您不能保证 UI 更新实际上在消息队列中领先于它。在我 2018 年 6 月的 Chrome 浏览器上,值 10 并不能“修复”小提琴演示的问题。我实际上对此有点窒息,因为在我看来,UI 更新消息应该在它之前排队似乎是合乎逻辑的,因为它的触发器是在计划长时间运行的函数“稍后”运行之前执行的。但也许 V8 发动机中有一些优化可能会干扰,或者我的理解只是缺乏。0
好的,那么使用 有什么问题,对于这种特殊情况,有什么更好的解决方案?setTimeout
首先,在这样的事件处理程序上使用,试图缓解另一个问题,很容易与其他代码混淆。这是我工作中的一个真实例子:setTimeout
一位同事对事件循环的理解是错误的,他试图通过使用一些模板渲染代码来“线程”Javascript。他不再在这里问了,但我可以推测,也许他插入了计时器来衡量渲染速度(这将是函数的返回即时性),并发现使用这种方法可以使该函数的响应速度极快。setTimeout 0
第一个问题很明显;你不能线程 JavaScript,所以当你添加混淆时,你在这里什么也赢不了。其次,您现在已经有效地将模板的呈现与可能的事件侦听器堆栈分离,这些事件侦听器可能期望该模板已被呈现,而很可能没有呈现。该函数的实际行为现在是非确定性的,就像任何运行它或依赖它的函数一样——在不知不觉中如此。您可以做出有根据的猜测,但无法正确编码其行为。
在编写依赖于其逻辑的新事件处理程序时,“解决”也使用 .但是,这不是一个修复程序,很难理解,调试由此类代码引起的错误并不好玩。有时永远没有问题,有时它偶尔会失败,然后又会偶尔工作和中断,这取决于平台的当前性能以及当时发生的任何其他事情。这就是为什么我个人建议不要使用这个黑客(这是一个黑客,我们都应该知道它是),除非你真的知道你在做什么以及后果是什么。setTimeout 0
但是我们能做些什么呢?好吧,正如引用的 MDN 文章所建议的那样,要么将工作拆分为多条消息(如果可以的话),以便排队的其他消息可以与您的工作交错并在运行时执行,或者使用 Web Worker,它可以与您的页面一起运行,并在完成计算后返回结果。
哦,如果你在想,“好吧,我不能在长期运行的函数中放一个回调来使其异步吗?”那么不行。回调不会使其异步,在显式调用回调之前,它仍然必须运行长时间运行的代码。
评论
问题是您尝试对不存在的元素执行 Javascript 操作。该元素尚未加载,并按以下方式为元素提供了更多时间加载:setTimeout()
setTimeout()
使事件是异步的,因此在所有同步代码之后执行,从而为元素提供更多加载时间。异步回调(如回调)放置在事件队列中,并在同步代码堆栈为空后由事件循环放在堆栈上。setTimeout()
- 作为函数中第二个参数的 ms 值 0 通常略高(4-10ms,具体取决于浏览器)。执行回调所需的时间略高是由事件循环的“ticks”(如果堆栈为空,则tick在堆栈上推送回调)的数量引起的。由于性能和电池寿命的原因,事件循环中的滴答量被限制在一定数量内,低于每秒 1000 次。
setTimeout()
setTimeout()
如果你不想看一个完整的视频,这里有一个简单的解释,需要理解的事情,以便能够理解这个问题的答案:
- JavaScript 是单线程的,这意味着它在运行时一次只做一件事。
- 但是 JavaScript 运行的环境可以是多线程的。例如,浏览器通常是多线程生物,即能够一次做很多事情。因此,他们可以运行 JavaScript,同时跟踪处理其他东西。
从现在开始,我们谈论的是“浏览器中的”JavaScript。像这样的东西确实是浏览器的东西,而不是 JavaScript 本身的一部分。setTimeout
- 允许 JavaScript 异步运行的是多线程浏览器!除了 Javascript 使用的主要空间(称为调用堆栈)来放置每行代码并逐一运行它们之外,浏览器还为 JavaScript 提供了另一个空间来放置内容。
现在,我们把另一个空间称为第二个空间。
- 我们假设是一个函数。这里要理解的重要一点是,
fn(
); call 不等于setTimeout(fn, 0);
call,下面将进一步解释。fn
让我们先假设另一个延迟,而不是延迟,例如,5000 毫秒:。需要注意的是,这仍然是一个“函数调用”,所以它必须放在主空间上,并在完成后从主空间中删除,但是等等!,我们不喜欢整个冗长而无聊的 5 秒延迟。这将阻塞主空间,并且不允许 JavaScript 在此期间运行任何其他内容。0
setTimeout(fn, 5000);
值得庆幸的是,这不是浏览器设计者设计它们的方式。相反,此调用(setTimeout(fn, 5000);)
是立即完成的。这一点非常重要:即使有 5000 毫秒的延迟,这个函数调用也会在瞬间完成!接下来会发生什么?它从主空间中删除。它会放在哪里?(因为我们不想失去它)。您可能猜对了:浏览器听到此调用并将其放在第二个空格上。
浏览器会跟踪 5 秒的延迟,一旦通过,它就会查看主空间,并且“当它为空时”,将 fn();
回调。这就是工作方式。setTimeout
所以,回到 ,即使延迟为零,这仍然是对浏览器的调用,浏览器立即听到并拾取它,并将其放在第二个空间上,只有在主空间再次为空时才将其放回主空间,而不是真正的 0 毫秒后。setTimeout(fn, 0)
我真的建议你也看这个视频,因为他解释得非常好,而且对技术问题有更多了解。
评论
//When need "new a", setTimeout(fn, 0) is useful, when need to wait some action. Example:
var a = function (){console.log('a');};
var b = function(){setTimeout(b, 100);}; //wait some action before override this function
//without setTimeout:
console.log('no setTimeout: b.toString():', b.toString());
b(); //"b" is an old function
console.log('no setTieout: a.toString(): ', a.toString());
a(); //and "a" is not overrided
setTimeout(//but with setTimeout(fn, 0):
function(){
console.log('After timeout 0, b.toString(): ', b.toString());
b(); //"b" is a new function
console.log('After timeout 0, a.toString(): ', a.toString());
a(); //and "a" is overrided
},
0
);
//override var "b", which was been undefined
b = function (){
a = function(){console.log('new a');};
}
评论
setTimeout(fn)
与 ,顺便说一句。setTimeout(fn, 0)