首先我们来看浏览器下的JavaScript:
浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。
-
javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。
-
GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
-
事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。(当线程中没有执行任何同步代码的前提下才会执行异步代码)
js是单线程的,浏览器有一个js任务队列,当在触发事件里面嵌套触发事件时,嵌套的这些事件会放在队列里面,等当前任务完成后再激活队列中的任务。解决方案就是使用setTimeout再次调整浏览器的代码任务运行队列。
看下面这2个对比的例子:
<div>
<h3>setTimeout</h3>
<h4>1、未使用 <code>setTimeout</code></h4>
<button id="generateInput">生成input</button>
<p id="inpwrapper"></p>
</div>
<div>
<h3>setTimeout</h3>
<h4>2、使用 <code>setTimeout</code></h4>
<button id="generateInput2">生成input</button>
<p id="inpwrapper2"></p>
</div>
<div>
<h3>3、另一个例子, 没有使用setTimeout()</h3>
<p>
<input type="text" id="input" value="aa" /><span id="preview"></span>
</p>
</div>
<div>
<h3>4、另一个例子, 使用setTimeout()</h3>
<p>
<input type="text" id="input2" /><span id="preview2"></span>
</p>
</div>
<script> function get(id){ return document.getElementById(id); } window.onload = function(){ get("generateInput").onmousedown = function(){ var input = document.createElement('input'); input.setAttribute('type',"text"); input.setAttribute('value',"test1"); get("inpwrapper").appendChild(input); input.focus(); input.select(); } get("generateInput2").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('input').onkeypress = function(){ get("preview").innerHTML = this.value; }; var domInput = get('input2'); domInput.onkeypress = function(event){ console.log(event); setTimeout(function(){ console.log(); get("preview2").innerHTML = domInput.value; },0); } } </script>
基本效果展示:
未使用setTimeout(),测试新生成的input的focus事件是被丢弃了还是被触发了,加强版测试例子1:
function get(id){
return document.getElementById(id);
}
window.onload = function(){
var gener1 = get("generateInput");
gener1.onmousedown = function(e){
console.log(e.type);
var input = document.createElement('input');
input.setAttribute('type',"text");
input.setAttribute('value',"test1");
get("inpwrapper").appendChild(input);
input.onfocus = function(){
console.info('input focus');
};
input.focus();
input.select();
};
gener1.onclick =function(e){
console.log(e.type);
};
gener1.onmouseup =function(e){
console.log(e.type);
};
gener1.onfocus =function(e){
console.log("gener1 focus");
};
结果如下:
例子1中,我们点击的按钮,在mousedown之后,才获得焦点,也就是说:我们的input本来已经得到了focus(),但在onmousedown之后,我们点击的按钮才迟迟触发了自己的onfocus(),导致我们的input的focus被覆盖。所以input才没有获得焦点。
未使用setTimeout(fn,0)之前,任务流程是这样的:
onmousedown->onmousedown中执行了input.focus=>button.onfoucs=>οnmοuseup=>onclick
使用setTimeout()后测试新生成的input的focus事件是被丢弃了还是被触发了,加强版测试例子1:
var gener2 = get("generateInput2");
gener2.onmousedown = function(e){
console.log(e.type);
var input = document.createElement('input');
input.setAttribute('type',"text");
input.setAttribute('value',"test1");
get("inpwrapper2").appendChild(input);
input.onfocus = function(){
console.info('input focus');
};
setTimeout(function(){
input.focus();
input.select();
},0);
};
gener2.onclick =function(e){
console.log(e.type);
};
gener2.onmouseup =function(e){
console.log(e.type);
};
gener2.onfocus =function(e){
console.log("gener1 focus");
};
运行结果如下:
示例2中的代码,我们延迟了焦点,当按钮获得焦点之后,我们的input再把焦点抢过来,所以,使用setTimeout(fn,0)之后,我们的input可以得到焦点并选中文本。
使用了setTimeout之后任务流程是这样的:
onmouseup->button.οnfοcus=>input.focus=>οnmοuseup=>onclick
在工作学习中,我看到团队中的有些大神的代码中不时的出现setTimeout(fn,0);这样的写法,很好奇,这样写的作用是什么,在网上,经过一位网友无私耐心地阐述,我才对js单线程的机制有了更深的认识,此处我引上链接,与君共勉!