深入探寻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 相关文章推荐
Jquery 插件学习实例1 插件制作说明与tableUI优化
Apr 02 Javascript
基于jquery的鼠标拖动效果代码
May 30 Javascript
删除select中所有option选项jquery代码
Aug 12 Javascript
javascript 循环调用示例介绍
Nov 20 Javascript
juery框架写的弹窗效果适合新手
Nov 27 Javascript
Extjs 4.x 得到form CheckBox 复选框的值
May 04 Javascript
node.js中的fs.rmdir方法使用说明
Dec 16 Javascript
javascript文本模板用法实例
Jul 31 Javascript
深入解析jQuery中Deferred的deferred.promise()方法
May 03 Javascript
vuex实现简易计数器
Oct 27 Javascript
使用JQ完成表格隔行换色的简单实例
Aug 25 Javascript
js实现移动端tab切换时下划线滑动效果
Sep 08 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
在PHP中使用curl_init函数的说明
2010/11/02 PHP
codeigniter教程之上传视频并使用ffmpeg转flv示例
2014/02/13 PHP
一个简洁实用的PHP缓存类完整实例
2014/07/26 PHP
jquery+thinkphp实现跨域抓取数据的方法
2016/10/15 PHP
基于Asp.net与Javascript控制的日期控件
2010/05/22 Javascript
js点击button按钮跳转到另一个新页面
2014/10/10 Javascript
jQuery选择器源码解读(七):elementMatcher函数
2015/03/31 Javascript
Javascript 计算字符串在localStorage中所占字节数
2015/10/21 Javascript
JavaScript中Array的实用操作技巧分享
2016/09/11 Javascript
理解JavaScript原型链
2016/10/25 Javascript
angular或者js怎么确定选中ul中的哪几个li
2017/08/16 Javascript
基于 Vue.js 之 iView UI 框架非工程化实践记录(推荐)
2017/11/21 Javascript
详解nodeJs文件系统(fs)与流(stream)
2018/01/24 NodeJs
Angular(5.2-&gt;6.1)升级小结
2018/12/27 Javascript
vue 接口请求地址前缀本地开发和线上开发设置方式
2020/08/13 Javascript
在vue中使用cookie记住用户上次选择的实例(本次例子中为下拉框)
2020/09/11 Javascript
[03:44]2015国际邀请赛选手档案—Cloud9.NoTail
2015/07/28 DOTA
Python中使用partial改变方法默认参数实例
2015/04/28 Python
在VS Code上搭建Python开发环境的方法
2018/04/06 Python
解决python中遇到字典里key值为None的情况,取不出来的问题
2018/10/17 Python
pygame游戏之旅 调用按钮实现游戏开始功能
2018/11/21 Python
pytorch实现线性拟合方式
2020/01/15 Python
keras 简单 lstm实例(基于one-hot编码)
2020/07/02 Python
python 基于卡方值分箱算法的实现示例
2020/07/17 Python
Python 按比例获取样本数据或执行任务的实现代码
2020/12/03 Python
HTML5本地存储localStorage、sessionStorage基本用法、遍历操作、异常处理等
2014/05/08 HTML / CSS
人力资源管理专业求职信
2014/07/23 职场文书
商场促销活动策划方案
2014/08/18 职场文书
民警群众路线教育实践活动对照检查材料
2014/10/04 职场文书
银行求职信模板
2015/03/20 职场文书
2015年国庆节新闻稿
2015/07/18 职场文书
图书借阅制度范本
2015/08/06 职场文书
2015年教师个人业务工作总结
2015/10/23 职场文书
小学六年级班主任工作经验交流材料
2015/11/02 职场文书
公司晚会主持词
2019/04/17 职场文书
《月歌。》宣布制作10周年纪念剧场版《RABBITS KINGDOM THE MOVIE》
2022/04/02 日漫