也谈 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 条评论
登录 后参与评论

相关文章

来自专栏码洞

巧用Google Fire简化Python命令行程序

上面是官方的示例代码,有了fire,编写Python的命令行程序就变得非常简单,我们无需再去处理繁琐的命令行参数解析了。接下来我们仿照HelloWorld,编写...

702
来自专栏技术小黑屋

Java中的堆和栈的区别

当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更...

493
来自专栏开发与安全

从零开始学C++之从C到C++(二):引用、数组引用与指针引用、内联函数inline、四种类型转换运算符

一、引用 (1)、引用是给一个变量起别名 定义引用的一般格式:类型  &引用名 = 变量名; 例如:int a=1;  int  &b=a;// b是a的别...

1780
来自专栏葡萄城控件技术团队

1000多个项目中的十大JavaScript错误以及如何避免

通过统计数据库中的1000多个项目,我们发现在 JavaScript 中最常出现的错误有10个。下面会向大家介绍这些错误发生的原因以及如何防止。 对于这些错误发...

3234
来自专栏前端架构

Eslint静态代码检查——参数配置详细说明

在团队协作中,为避免低级 Bug、产出风格统一的代码,会预先制定编码规范。使用 Lint 工具和代码风格检测工具,则可以辅助编码规范执行,有效控制代码质量。

391
来自专栏IMWeb前端团队

Express使用手记:核心入门

本文作者:IMWeb 陈映平 原文出处:IMWeb社区 未经同意,禁止转载 入门简介 ? Express是基于nodejs的web开发框架。优点是易上...

1776
来自专栏何俊林

Android开发基础规范(一)

【小提醒】阅读本文约耗时3分钟左右。 前言:Android中一些开发规范,避免给自己和别人少留坑。 一、工程相关 1.1 工程结构 当进行提交代码的工作时,工...

1757
来自专栏更流畅、简洁的软件开发方式

【自然框架】 页面里的父类—— 改进和想法、解释

1、 从Control到GridView继承了多少层? ? (这个图可不是现做的,这是以前为了写QuickPager分页控件而弄的。http://www.cn...

1965
来自专栏ionic3+

【技巧】ionic3的页面导航后退事件拦截

写一篇简单的,有这样一种业务场景:当使用push后,页面导航栏会自动添加后退按钮,当点击后退按钮后,拦截事件(如付费进来了,没有完成后续操作就后退退出,良好的用...

665
来自专栏计算机视觉life

OpenCV学习入门(四):RNG 伪随机问题

在我的上一篇博客《OpenCV学习入门(三):kmeans原理及代码 》中调试kmeans时发现一个问题:每次运行时,以下两行代码 int clusterCou...

2097

扫码关注云+社区