神奇的setTimeout(fn,0)

问题场景

最近在用layui写一个小项目,用到了layui的table组件,并且对某一列设置了可编辑属性,如下图,鼠标点击单元格的时候,就可以进行编辑了


image.png

UI很漂亮,但是因为这是一个财务方面的应用,而且这一列又是金额,所以需要做严格的输入验证,要求只能够输入数字和点。因为我没找到layui提供的属性,方法,那么只能自己给input绑定键盘按下事件,做验证了。相关单元格dom结构如下

<td data-field="initialBalance" data-edit="true" align="right">
   <div class="layui-table-cell laytable-cell-1-initialBalance">0</div>
   <!--<input class="layui-input layui-table-edit">-->
</td>

那么,为什么可以实现可编辑呢?当单击该单元格的时候,会动态的在td中创建input。我猜单元格必有click事件,所以在github上找到layui项目的table模块,通过搜索click关键字,查看相应源码,可以验证layui的确是这么做的,源码如下。


image.png

因为input是动态生成的,所以我们无法直接给input绑定事件。

setTimeout登场解围

既然input只有在单击单元格之后才出现,那么我先触发了其单击事件,然后在事件处理程序中,再给它绑定事件不就行了嘛。就像下面这样

$(".layui-table-body .laytable-cell-1-initialBalance").click(function(){    
    that.next()[0].onkeyup=function(){
        this.value=this.value.replace(/[^1-9|.]/g,'');
    };
});

但很不幸,这不work。我猜是因为input创建需要时间,就强制给键盘按下事件加了个setTimeout(fn,1000),想着等一段时间再去给input绑定事件,那肯定可以了。

$(".layui-table-body .laytable-cell-1-initialBalance").click(function(){
   setTimeout(function(){
       that.next()[0].onkeyup=function(){
           this.value=this.value.replace(/[^1-9|.]/g,'');
       };
   },1000)
});

设置了定时器之后,的确起作用了。我想等待1000毫秒是不是有点长,就改成了100,10,发现都可以,最后我所幸改成了0,让我意外的是,改成0之后,仍然有效,但是去掉setTimeout就是不行!我想肯定我对setTimeout的理解有偏差,看来setTimeout不是简简单单的等待一段时间后执行fn那么简单。不查不要紧,一查才发现,setTimeout水深着呢。

setTimeout揭秘

看了阮一峰的博客之后,我大概明白了怎么回事。我的知识盲区在于js代码执行顺序上,js是一门单线程语言,任一时刻,同时只能做一件事,但是这并不代表js里不可以进行异步操作。起码事件处理程序是异步的吧,只有当我们触发相应的事件时,事件处理程序才会被执行,再或者Ajax我们都了解吧,Ajax请求时,后面的代码并不会等待网络请求成功之后才去执行,所以Ajax请求也是异步的。那么我们有必要了解一下代码执行顺序到底是怎样的,setTimeout又会对执行顺序产生什么影响。
js引擎(浏览器)将任务分为同步任务和异步任务。同步任务就是按顺序执行,上一个任务不完成,下一个任务就无法进行,同步任务在主线程上排队运行,是线程阻塞的,而异步任务则处于“任务队列中”,不会阻塞线程。

js运行机制的简单理解

程序开始执行之后,主程序则开始执行同步任务,一个接着一个,碰到异步任务就把它放到任务队列中,如事件处理程序,然后继续执行下面的代码,等到同步任务全部执行完毕之后,js引擎便去查看任务队列有没有可以执行的异步任务,比如看看有没有事件被触发,网络请求有没有结束,有的话,将异步任务转为同步任务,开始执行,执行完同步任务之后继续查看任务队列,这个过程是一直循环的,因此这个过程就是所谓的事件循环,其中任务队列也被称为事件队列。通过一个任务队列,单线程的js实现了异步任务的执行,给人感觉js好像是多线程的。

setTimeout工作机制(setInterval机制与其一致)

以前我对setTimeout的理解就是,先把其他代码全部执行完之后,等待一定延长时间,然后执行setTimeout的回调函数。所以当setTimeout延迟时间设置为0就意外着执行到setTimeout的时候,立刻执行其回调函数,然后再执行setTimeout后面的代码。
但setTimeout是异步的,不管你延迟时间是多少,执行到setTimeout的时候,它便会将其回调函数放入任务队列队尾,也就是说即使延迟时间为0,它也会继续执行setTimeout后面的代码,而非立即执行回调函数。我们可以做个实验。

image.png

控制台,永远打印的是a,c,b而非a,b,c.
setTimeout将其回调函数放入任务队列,所以只有同步任务执行完成,才有机会去执行该任务。它的延迟时间从什么时候开始算呢?从所有的同步任务执行完成之后,再处理完队列中处于该任务前面的任务后(队列先进先出)。延迟设置为0表示,同步任务执行完成之后,并且队列中它前面没有要处理的任务了,立刻执行该异步任务,设置为1000表示同步任务结束1秒后,并且队列中它前面没有要处理的任务了,再执行该异步任务。所以此代码到底多长时间之后执行,并不是完全确定的,还取决于同步任务的执行时间和任务队列中前面任务的执行时间。

用任务队列解释开头的问题

查阅了好多博客,得出了自己的理解,不知道自己的理解是否正确,所以试着用后面的理论去验证一下开头提出的问题。
先考虑不用setTimeout的时候
1.js引擎从前到后执行,执行到td的单击事件的时候,将其放入任务队列,执行到td标签里的div的单击事件时,也将其放入任务队列,然后继续执行后面的代码,直到同步任务结束。
2.js引擎检查任务队列,当我们单击了该div,触发了绑定在该div的上的单击事件处理程序,所以此时开始将处理程序转为同步任务,但此时无法给input绑定事件,因为input的动态生成在td的单击事件处理程序里,根据事件冒泡顺序,此时还没有触发td的单击事件,所以input还没有生成,所以无法绑定事件。
3.当同步任务执行完成,继续检查任务队列,这个时候单击事件从div冒泡冒到了td,触发了td单击事件,如上一步,相应的代码转入执行栈开始执行,生成input,程序执行完毕。
也就是说,不写setTimeout的结果就是先给input绑定事件,然后才生成了input,当然没效果了。那么我们加了setTimeout(fn,0)再分析一下这个过程。
1.js引擎执行同步任务,执行到td的单击事件的时候,将其放入任务队列,执行到td标签里的div的单击事件时,也将其放入任务队列,然后继续执行后面的代码,直到执行完成同步任务。
2.检查任务队列,当点击div的时候,触发了div的单击事件处理程序,然后主线程开始执行,执行到setTimeout(function(){ input.onkeyup=...;},0)时候,js引擎将此任务插入到任务队列尾部,并设置延迟时间为0。同步任务执行结束。
3.js引擎继续检查任务队列,此时触发了td的单击处理程序,因为td的单击事件在定时器之前放进了任务队列,所以先处理该任务,生成了input,同步任务结束。
4.js引擎在任务队列中开始处理定时器这个任务,因为延迟时间为0,所以立刻执行,因为此时input已经生成了,所以可以成功为其绑定事件。

其它

其实在查阅资料的过程中,我了解到jq中用on()方法就可以实现给动态生成的dom元素绑定事件,on函数,第二个参数设一个选择器,就可以实现此需求。

$("#nav").on('click','li',function(){
 ...
})

以前一直不知道on和bind有什么区别,都是混用,今天才发现on的强大,除了可以给动态生成的dom元素添加事件,on的选择器还可以起到过滤事件的作用,如给父元素绑定事件,之后触发选择器元素的事件,而不会触发父元素的事件。

写了很久,理解了很久,边写边改,如有错误之处,还请大家指出。
参考博客:JavaScript 运行机制详解,阮一峰老师的博客写更加清楚更加全面,需要了解的可以自行查看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容