首先我们来看浏览器下的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=>onmouseup=>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.onfocus=>input.focus=>onmouseup=>onclick
在工作学习中,我看到团队中的有些大神的代码中不时的出现setTimeout(fn,0);这样的写法,很好奇,这样写的作用是什么,在网上,经过一位网友无私耐心地阐述,我才对js单线程的机制有了更深的认识,此处我引上链接,与君共勉!