也谈 setTimeout

也谈 setTimeout

setTimeout ,延迟一段事件执行代码,当然这是最基本的用法,这里不说基本用法。

jQuery 中的轮询

轮询,可能是 setTimeout 最典型的用法,jQuery 的兼容IE的 document ready 机制就用到了这个:

// jquery 1.9.1
(function doScrollCheck() {
    if ( !jQuery.isReady ) {

        try {
            // Use the trick by Diego Perini
            // http://javascript.nwbox.com/IEContentLoaded/
            top.doScroll("left");
        } catch(e) {
            // 不停地查看是否准备好
            return setTimeout( doScrollCheck, 50 );
        }

        // detach all dom ready events
        detach();

        // and execute any waiting functions
        jQuery.ready();
    }
})();

另外,我还看到了下面这种用法,缺省了 delay 这个参数,不知道会是一个什么状态,待探究。

// jquery 1.9.1
ready: function( wait ) {
    ...

    // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
    if ( !document.body ) {
        return setTimeout( jQuery.ready );
    }

    ...
},

setTimeout( func, 0 )

当然,初见这种用法时,我是一愣啊,什么情况,setTimeout( func, 0 ) 和直接调用 func 难道不是同一个效果?

肯定不是一个效果,在 stackOverflow 也有很多人问。

比如这个Why is setTimeout(fn, 0) sometimes useful?, IE6 中出现的奇葩问题竟然可用 setTimeout(func, 0) 神奇地解决。

这些问题概括来讲是这样:动态往 dom 树中插入元素, 然后立刻、马上操作这个元素(比如选择文本框的文本,改变 select 的 index 等), 普通方式写代码通常不起作用,但是放入 setTimeout( func, 0 ) 中就可以达到效果。

要理解这个问题还是要了解 浏览器的 UI 线程。

单线程的浏览器, js 引擎和渲染引擎必定是顺序执行 (stack),比如点击一个按钮,浏览器会先改变按钮的状态(actived,重绘), 然后才执行 js (js引擎) 。

所以往 dom 插入元素再立刻操作这个 dom ,那么很有可能这个 dom 还没有重绘完成,因此操作无效。

那么,为什么放入 setTimeout( func, 0 ) 中就可以呢? 其实答案已经出来了, setTimeout 会等到重绘完成才执行代码,自然无往而不利。

setTimeout 进一步理解

可以更深入的思考: setTimeout( func, 0 ) 是延迟 0ms 执行,也就是立刻执行,但为什么还是在重绘之后呢? 重绘肯定会超过 0ms 啊!

到这里才是这篇笔记的终极目的, javascript单线程的异步模式。

jQuery 作者 John Resig 的这篇《How JavaScript Timers Work》通俗易懂地阐述了这个问题……

以下是我对这篇文章的理解:

理解 javasript 定时器的内部机制是必要的,虽然有时候这些定时器表现的很古怪。

为了理解定时器的内部机制,有一点必须着重强调:延迟时间的精确度无法保证,比如延迟 10ms ,回调函数不一定在 10ms 后执行。 这是因为,浏览器中的 javascript引擎是单线程,所有的异步函数必须等到适合的时间执行。

为了更好地阐述,John 采用了看图说话的方式,点击查看图片

图中蓝色的圆角矩形是 js 块(javascript block) ,右边的数字表示时间,“问题”是模拟浏览器的判断,左边则是 javascript代码的执行时间。 比如第一个 js 块执行了大概 18ms ,“点击事件”大概执行了 11ms 等等。

既然是单线程,这些 js 块都是互相阻塞的,第一个 js 块执行过程中, "click" 被触发,但是必须排队,等到第一个块执行完才执行(当然, 排队的方式在各浏览器中不同,我们这里不关注这个)。

接下来就好理解了——

开始,在第一个 js 块中,两个延迟 10ms 的 timer 被初始化,注意这个 10ms ,不保证 10ms 后一定执行,两个 timer 必然会是在第一个 js block 执行完之后才执行。

另外,在第一个 js 块中,鼠标点击了,但是事件处理函数不会立刻执行,和 timer 一样,也要等到一个 js block 执行完后才执行。

终于,第一个 js 块执行完。这个时候浏览器会问,接下来干嘛。事件处理函数和 timer 都在等待,于是事件处理函数执行, timer 继续等待。

在事件处理函数执行过程中,10ms 的 interval 触发了,毫无疑问不会立刻执行,进入队列等待。

事件处理函数执行完毕, timer 执行,这个时候, interval 又触发了,要知道上一个 interval 还没有执行,怎么办?

这一次的 interval 会被抛弃 (dropped) 。如果不抛弃,那么有可能大量的 interval 会在 timer 执行完后同时执行,这显然不符合逻辑。 对于这,浏览器的排队方式是先检查有没有 interval ,如果没有,排队,有就抛弃。

继续看,当 timer 执行完, 第一个 interval 执行,在这个过程中,第三个 interval 触发.在其自身执行过程中,自身也可以被触发。 可见, setInterval 不管当前在执行什么,他都会强行排队,即使本身还没执行完。

最后没什么好说的了,没什么可等,所有的 interval 会立刻执行。

再来看看 setTimeout 和 setInterval 之间的区别:

setTimeout(function(){
    /* Some long block of code... */
    setTimeout(arguments.callee, 10);
}, 10);

setInterval(function(){
    /* Some long block of code... */
}, 10);

乍一看,效果一样,其实区别很大。

setTimeout 总是会在其回调函数执行后延迟 10ms (或者更多,但不可能少),而 setInterval 总是 10ms 执行一次,而不管 它的回调函数执行多久。

总结:

  • javascript引擎是异步的,总是强制异步过程排队。
  • setTimeout 和 setInterval 的机制完全不同。
  • 定时器的代码总是会被延迟到下一个可能的时间点执行,这个时间点很可能比你给定的时间要长。
  • 如果 Intervals 的回调执行时间比你给定的 delay 还要长,那么他们会连在一起执行。

上面就是 John 对 timer 的解释,唯一的缺憾是没有把渲染引擎的执行考虑进去。

setTimeout 中的 this

setTimeout 中的 this 被无数人吐槽过,老道直接说这是语言设计错误。

var o = {
    a: "a",
    b: function() {
        setTimeout(function() {
            console.log( this.a ); 
        }, 1000);
    }
}

o.b();  // undefined

是的,上面的这种情况,setTimeout 的 this 会指向全局作用域,于是 a 是 undefined 。

解决办法很多,最简单的是:

var o = {
    a: "a",
    b: function() {
        var that = this;
        setTimeout(function() {
            console.log( that.a ); 
        }, 1000);
    }
}

o.b();  // "a"

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端小叙

Vue.js 2.0 学习重点记录

Vue.js兼容性 Vue.js.js 不支持 IE8 及其以下版本,因为 Vue.js.js 使用了 IE8 不能模拟的 ECMAScript 5 特性。 V...

37950
来自专栏前端新视界

Vue.js 系列教程 1:渲染,指令,事件

原文:intro-to-vue-1-rendering-directives-events 译者:nzbin 如果要我用一句话描述使用 Vue 的经历,我可...

24490
来自专栏Pythonista

Golang之并发篇

12530
来自专栏开源优测

Selenium3源码之common下action_chains.py模块分析

介绍 本文主要对action_chains.py模块的源码进行分析说明,其代码位置如图: ? 在action_chains.py模块中定义和实现了类:Actio...

32860
来自专栏hbbliyong

RadioButtonList数据项不改变依然执行改变事件

问题:使用RadioButtonList导航到其他页面,当点击一数据项出现新页时候,关闭新页,再点击此数据项,由于数据项没有改变,所以不能触发他的Selecte...

28830
来自专栏进击的君君的前端之路

Vue成神之路之全局API

vue.js——开发版本:包含完整的警告和调试模式 vue.min.js——生产版本:删除了警告,进行了压缩

14030
来自专栏Java成神之路

GEF入门实例_总结_03_显示菜单和工具栏

还记得上一节我们新建的类: ApplicationActionBarAdvisor 吗,这个类继承自 ActionBarAdvisor。

11520
来自专栏彭湖湾的编程世界

【Vue】Vue中的父子组件通讯以及使用sync同步父子组件数据

前言: 之前写过一篇文章《在不同场景下Vue组件间的数据交流》,但现在来看,其中关于“父子组件通信”的介绍仍有诸多缺漏或者不当之处, 正好这几天学习了关于用sy...

1.2K110
来自专栏DeveWork

自定义WordPress 标签云小工具相关参数

相信你知道WordPress 标签云widget(小工具)是什么,如果你的WordPress 主题支持小工具,就可以在后台启用标签云小工具,该小工具不仅能展示标...

27280
来自专栏Youngxj

给源代码和控制台加上线条字

20130

扫码关注云+社区

领取腾讯云代金券