深入探寻javascript定时器


Posted in Javascript onJanuary 02, 2015

javascript单线程

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

队列任务

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。


异步事件驱动

浏览器中很多行为是异步(Asynchronized)的,例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件、XMLHttpRequest完成回调等。当一个异步事件发生的时候,它就进入事件队列。浏览器有一个内部大消息循环,Event Loop(事件循环),会轮询大的事件队列并处理事件。例如,浏览器当前正在忙于处理onclick事件,这时另外一个事件发生了(如:window onSize),这个异步事件就被放入事件队列等待处理,只有前面的处理完毕了,空闲了才会执行这个事件。

Event Loop

JavaScript是单线程的,但浏览器不是单线程的

浏览器至少会有以下一些进程

1.浏览器 GUI 渲染线程

2.JavaScript 引擎线程

3.浏览器定时触发器线程

4.浏览器事件触发线程

5.浏览器 http 异步请求线程

因为 JavaScript 引擎是单线程的,所以代码都是先压到队列,然后由引擎采用先进先出的方式运行。事件处理函数、timer 执行函数也会排到这个队列中,然后利用一个无穷回圈,不断从队头取出函数执行,这个就是Event Loop。

总结一句话,js是单线程的,但是浏览器是多线程的,遇到异步的东西都是由浏览器把异步的回调放到Event Loop中,js线程不繁忙的时候,再去读取Event Loop

定时器原理

定时器的用法

setTimeout(fn, delay)

setInterval(fn, delay)

fn是函数也可以是字符串,delay是延迟的时间,单位是毫秒

有以下要注意的

1.fn虽然可以是字符串,但是一直都不推荐这么使用

2.fn里面的函数如果有this,执行的时候this会指向到window上面去

如果理解好了js单线程和Event loop,定时器的原理也很好理解了

如果设置了定时器,当到了延迟时间,浏览器会把延迟执行的事件放到Event loop里面,当时间到了,如果js线程空闲就会执行了(所以定时器的精度不准啊)

看有文章介绍说setTimeout和setInterval对函数一直轮询的区别,代码如下

        setTimeout(function(){

            setTimeout(arguments.callee,100)    

        },100)

        setInterval(function(){},1000)

文章大概的意思是setTimeout是在回调函数执行后才启动下一次定时器,所以肯定是有间隔的执行,而setInterval是一直都在执行,如果碰到了js线程一直执行,可能就会在Event loop里面加多个回调,当js线程不忙的时候,会一下子执行多个

经过测试发现无论在ie,ff,chrome,Opera,Safari下,setInterval都是按一定间隔来的

测试代码如下

        setInterval(function(){

            xx.innerHTML=xx.innerHTML+1;

        },100);

        

        for(var i=0;i<6000000;i++){

            xx.offsetWidth

        }
        setTimeout(function(){

            debugger;

        },10)

断点的时候还是只打印出了1个1

定时器的精度问题

因为js单线程原因,如果遇到繁忙,定时器肯定不准,而且肯定是越来越长的,这个好像无解啊,无解啊

还有一个精度问题就是最小间隔setTimeout(fun,0)

在js线程不忙的时候,也不可能0秒后马上执行,总有个最小间隔,每个浏览器还各不一样,这个未做测试

我看一篇文章中说的是w3c的标准,定时器的最小时间执行是4ms,找不到出处无从考证呀!!!

定时器相关的一些优化

在做定时器的时候还是可以有一些优化的

1.比如如果绑定window.onresize,在浏览器缩放的时候,该触发的非常频繁,所以可以延迟执行,当下次执行到的时候clear掉,减少频繁执行

 伪代码如下

    var timer;

    function r(){

        clearTimeout(timer);

        timer = setTimeout(function(){

            //do something

        },150);        

    }

2.在滚动条往下拉的时候也是一下,比如图片的lazyload,也应该有一个定时器,避免过多的计算

3.当有多个地方需要定时器的时候,可以合并成一个定时器,时间间隔以最小的那个为准,然后需要执行的回调函数往数组里面塞,当到了时间间隔,遍历数组执行即可

 一个小demo

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8" />

    <style>

    .wrap{width:80%; margin: 30px auto; border: 1px solid #ccc; padding: 20px;}

    .c{border: 1px solid #ccc; height: 30px;margin-bottom: 20px;}

    </style>

</head>

<body>

<div id="xx"></div>

    <div class="wrap" >

        <div id="a1" class="c">0</div>

        <div id="a2" class="c">0</div>

        <div id="a3" class="c">0</div>

        <div id="a4" class="c">0</div>

    </div>

<script src="http://static.paipaiimg.com/paipai_h5/js/ttj/zepto.min.js"></script>

    <script type="text/javascript">

        var runTime = {

            options  : {

                step : 1000

            },

            callbacks:[],

            addCallbacks : [],

            start : false,

            timer : null,

            extend : function(){

                var target = arguments[0] || {}, i = 1, length = arguments.length, options;

                if ( typeof target != "object" && typeof target != "function" )

                    target = {};

                for ( ; i < length; i++ )

                    if ( (options = arguments[ i ]) != null )

                        for ( var name in options ) {

                            var copy = options[ name ];

                            if ( target === copy )

                                continue;

                            if ( copy !== undefined )

                                target[ name ] = copy;

                        }

                return target;

            },

            init  : function(options){

                $.extend(this,this.options,options||{});

            },

            add : function(fun,options){

                options = options ||{};

                this.addCallbacks.push({

                    fun       : fun,

                    startTime : new Date().getTime(),

                    step      : options.step || this.step,

                    i         : 1

                });

                var self = this;

                if(!this.start){

                    this.callbacks = [fun];

                    this.start = true;

                    this.startTime = new Date().getTime();

                    this.timer = setInterval(function(){

                        self.done();    

                       

                    },this.step);

                }

            },

            done : function(){

                var callbacks = this.callbacks,

                    self   = this,

                    newArr = [];

                $.each(callbacks,function(i,obj){

                    if(obj.step == self.step){

                        obj.fun();

                    }else{                        

                        if(obj.i == obj.step/self.step){

                            if((new Date().getTime())-obj.startTime>obj.step*2/3){

                                obj.fun();

                            }

                            obj.i = 1;

                        }else{

                            obj.i = obj.i + 1;

                        }

                    }

                });

                $.each(this.addCallbacks,function(i,obj){

                    if(obj.step == self.step){

                        if((new Date().getTime())-obj.startTime>obj.step*2/3){

                            obj.fun();

                            callbacks.push(obj);

                        }else{

                            newArr.push(obj);

                        }

                    }else{

                        obj.i = obj.i + 1;

                        callbacks.push(obj);

                    }

                });

                this.addCallbacks = newArr;

            },

            clear : function(){

                clearInterval(this.timer);

            }

        }

        runTime.init();
        runTime.add(function(){

            a1.innerHTML = ~~a1.innerHTML+1;

        });
        runTime.add(function(){

            a2.innerHTML = ~~a2.innerHTML+1;

        },{step:2000});
        runTime.add(function(){

            a3.innerHTML = ~~a3.innerHTML+1;

        },{step:4000});

                runTime.add(function(){

            a4.innerHTML = ~~a4.innerHTML+1;

        },{step:8000});

    </script>

</body>

</html>

小伙伴们是否对javascript定时器有所了解了呢,如有疑问给我留言吧。

Javascript 相关文章推荐
重构Javascript代码示例(重构前后对比)
Jan 23 Javascript
使用JQUERY进行后台页面布局控制DIV实现左右式
Jan 07 Javascript
angularjs的一些优化小技巧
Dec 06 Javascript
javascript基于DOM实现权限选择实例分析
May 14 Javascript
Backbone.js的Hello World程序实例
Jun 19 Javascript
Bootstrap中点击按钮后变灰并显示加载中实例代码
Sep 23 Javascript
省市区三级联动jquery实现代码
Apr 15 Javascript
集合Bootstrap自定义confirm提示效果
Sep 19 Javascript
node.js用fs.rename强制重命名或移动文件夹的方法
Dec 27 Javascript
layui table 表格上添加日期控件的两种方法
Sep 28 Javascript
完美解决vue 中多个echarts图表自适应的问题
Jul 19 Javascript
基于脚手架创建Vue项目实现步骤详解
Aug 03 Javascript
JavaScript中的Truthy和Falsy介绍
Jan 01 #Javascript
JavaScript中的null和undefined区别介绍
Jan 01 #Javascript
JavaScript中的全局对象介绍
Jan 01 #Javascript
原生javascript获取元素样式
Dec 31 #Javascript
JavaScript分析、压缩工具JavaScript Analyser
Dec 31 #Javascript
jQuery中:last-child选择器用法实例
Dec 31 #Javascript
jQuery中:nth-child选择器用法实例
Dec 31 #Javascript
You might like
WAR3重制版DOTA 5V5初体验
2020/04/09 DOTA
PHP 开源AJAX框架14种
2009/08/24 PHP
解析php如何将日志写进syslog
2013/06/28 PHP
php站内搜索关键词变亮的实现方法
2014/12/30 PHP
如何批量清理系统临时文件(语言:C#、 C/C++、 php 、python 、java )
2016/02/01 PHP
php简单的上传类分享
2016/05/15 PHP
PHP学习记录之数组函数
2018/06/01 PHP
Javascript hasOwnProperty 方法 &amp; in 关键字
2008/11/26 Javascript
javascript下4个跨浏览器必备的函数
2010/03/07 Javascript
jQuery性能优化28条建议你值得借鉴
2013/02/16 Javascript
解析window.open的使用方法总结
2013/06/19 Javascript
基于jquery实现的图片在各种分辨率下未知的容器内上下左右居中
2014/05/11 Javascript
原生JS绑定滑轮滚动事件兼容常见浏览器
2014/06/30 Javascript
JavaScript实现Base64编码转换
2016/04/23 Javascript
结合代码图文讲解JavaScript中的作用域与作用域链
2016/07/05 Javascript
轻松掌握JavaScript单例模式
2016/08/25 Javascript
基于JS实现弹出一个隐藏的div窗口body页面变成灰色并且不可被编辑
2016/12/14 Javascript
Vue.js路由vue-router使用方法详解
2017/03/20 Javascript
jQuery实现简单日期格式化功能示例
2017/09/19 jQuery
Angular2的管道Pipe的使用方法
2017/11/07 Javascript
webpack-dev-server自动更新页面方法
2018/02/22 Javascript
vue 微信授权登录解决方案
2018/04/10 Javascript
在 vue-cli v3.0 中使用 SCSS/SASS的方法
2018/06/14 Javascript
如何在vue里面优雅的解决跨域(路由冲突问题)
2019/01/20 Javascript
微信小程序 确认框的实现(附代码)
2019/07/23 Javascript
[27:53]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS iG
2014/05/26 DOTA
[56:45]DOTA2上海特级锦标赛D组小组赛#1 EG VS COL第一局
2016/02/28 DOTA
[53:43]VP vs NewBee Supermajor 胜者组 BO3 第三场 6.5
2018/06/06 DOTA
Python微信库:itchat的用法详解
2017/08/14 Python
Pandas0.25来了千万别错过这10大好用的新功能
2019/08/07 Python
numpy ndarray 按条件筛选数组,关联筛选的例子
2019/11/26 Python
如何将tensorflow训练好的模型移植到Android (MNIST手写数字识别)
2020/04/22 Python
基于python爬取梨视频实现过程解析
2020/11/09 Python
Django中的DateTimeField和DateField实现
2021/02/24 Python
健康状况证明书
2014/11/26 职场文书
质量保证书格式模板
2015/02/27 职场文书