在看vue的时候,看到了vue关于异步更新的原理,里面提到了setTimeout(0)。于是我想起来之前面试的时候被问过这个问题,并且在很久之后我随便查了查敷衍了事= =,到底是敷衍谁。于是昨天又去google了一下这个问题……果然发现了很多不得了的事(笑哭)。
之前知道,js是单线程的,所以在一段时间内只能执行一个任务。然后我们又知道settimeout和setinterver都是存在时延的,因为可能在计时器到达时间后,线程正在做其他的任务。所以要等到这个任务结束了才开始执行回调函数。我以为是因为这样,所以settimeout(0)由于时延,导致了在下一条语句之后才能执行。。回想了下,那时候用的是百度,以后一定要告别度娘……
首先要说的一个是,现代浏览器一般要求settimeout这样的定时器,时延时间在4毫秒以上,如果小于4毫秒,就要往上加。(另外对于setInterval,H5规定最少时间为10毫秒)所以之前的理解从一定程度上……没什么问题TAT。
但是如果从一个稍微复杂的情况下去分析,那就可以发现……这个问题下的js机制相关的问题了。
以下是例子:
这是比较多引用的一个例子,源码在这儿:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>setTimeout</title>
<script type="text/javascript" >
(function(){
function get(id){
return document.getElementById(id);
}
window.onload = function(){
get('makeinput').onmousedown = function(){
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('value', 'test1');
get('inpwrapper').appendChild(input);
input.focus();
input.select();
}
get('makeinput2').onmousedown = function(){
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('value', 'test1');
get('inpwrapper2').appendChild(input);
setTimeout(function(){
input.focus();
input.select();
}, 0);
}
get('input1').onkeypress = function(){
get('preview1').innerHTML = this.value;
}
get('input2').onkeypress = function(){
setTimeout(function(){
get('preview2').innerHTML = get('input2').value;
},0 );
}
}
})();
</script>
</head>
<body>
<h1><code>DEMO1</code></h1>
<h2>1、未使用 <code>setTimeout</code>(未选中文本框内容)</h2>
<button id="makeinput">生成 input</button>
<p id="inpwrapper"></p>
<h2>2、使用 <code>setTimeout</code>(立即选中文本框内容)</h2>
<button id="makeinput2">生成 input</button></h2>
<p id="inpwrapper2"></p>
--------------------------------------------------------------------------
<h1><code>DEMO2</code></h1>
<h2>1、未使用 <code>setTimeout</code>(只有输入第二个字符时,前一个字符才显示出来)</h2>
<input type="text" id="input1" value=""/><div id="preview1"></div>
<h2>2、使用 <code>setTimeout</code>(输入时,字符同时显示出来)</h2>
<input type="text" id="input2" value=""/><div id="preview2"></div>
</body>
</html>
看了这个例子觉得很神奇,但是看到原文得出的结论觉得匪夷所思。结论认为,在第一个例子,onmousedown的时候,由于单线程,在执行onmousedown,所以就抛弃了focus和select。这个说法显然是非常不正确的。
这时候提到js的执行机制:event loop。
浏览器最起码有三个线程,js线程,GUI线程,浏览器事件触发线程。js线程和GUI线程是互斥的,当页面需要渲染(回流,重绘等)的时候,js线程就挂起。js执行的时候GUI线程就先缓存。浏览器事件触发捕获相关事件,然后把事件加入到js事件队列尾部。
把js线程看做一个队列,里面执行的都是同步的任务。对于异步的任务,当异步的任务到了可以执行的时候(比如事件触发了,计时器的时间到了),就把这个任务取出来加到一个队列里面。当主线程中的任务执行完了,就到这个异步队列里面取任务执行。
如上所述的过程就是一个事件循环的过程,一系列event事件做完了,再取异步的队列做。做完了再来一轮。(我觉得是按时间顺序,然后先处理同步事件,再处理异步事件这样)
所以上面那个例子,没有执行select()和focus()只是因为执行之后,btn.focus又重新占据了焦点。
关于settimeout(0),我之前一直以为是像++x++这样有点无趣,有点变态地考察知识点的方式。然而其实人家还是有正经用途的。由于事件循环机制,可以利用settimeout(0)实现异步,并且可以改变一些事件实现的顺序。比如在用promise的时候,通过设置settimeout(0)把同步的调用变成异步的。详情可以参见promise指南中的例子
参考资料:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
http://www.cnblogs.com/xieex/archive/2008/07/11/1241137.html
http://www.cnblogs.com/silin6/p/4333999.html