深入探寻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入门—访问DOM对象方法
Jan 07 Javascript
jquery选择器之基本过滤选择器详解
Jan 27 Javascript
轻松实现JavaScript图片切换
Jan 12 Javascript
js中遍历Map对象的简单实例
Aug 08 Javascript
bootstrap table配置参数例子
Jan 05 Javascript
JavaScript实现水平进度条拖拽效果
Jan 18 Javascript
JavaScript字符串_动力节点Java学院整理
Jun 27 Javascript
Vue 项目分环境打包的方法示例
Aug 03 Javascript
Vue infinite update loop的问题解决
Apr 23 Javascript
解决使用layui对select append元素无效或者未及时更新的问题
Sep 18 Javascript
微信小程序收藏功能的实现代码
Jun 19 Javascript
VsCode里的Vue模板的实现
Aug 12 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
ADODB的数据库封包程序库
2006/12/31 PHP
jQuery的Ajax时无响应数据的解决方法
2010/05/25 Javascript
用jquery中插件dialog实现弹框效果实例代码
2013/11/15 Javascript
JQuery.get提交页面不跳转的解决方法
2015/01/13 Javascript
ajax+jQuery实现级联显示地址的方法
2015/05/06 Javascript
jQuery遮罩层实现方法实例详解(附遮罩层插件)
2015/12/08 Javascript
Bootstrap入门书籍之(四)菜单、按钮及导航
2016/02/17 Javascript
限制复选框最多选择项的实现代码
2016/05/30 Javascript
js改变style样式和css样式的简单实例
2016/06/28 Javascript
jQuery插件Easyui设置datagrid的pageNumber导致两次请求问题的解决方法
2016/08/06 Javascript
Sortable.js拖拽排序使用方法解析
2016/11/04 Javascript
利用CSS、JavaScript及Ajax实现图片预加载的三大方法
2017/01/22 Javascript
小程序自定义日历效果
2018/12/29 Javascript
详解微信小程序自定义组件的实现及数据交互
2019/07/22 Javascript
一起深入理解js中的事件对象
2021/02/06 Javascript
详解Python中for循环的使用
2015/04/14 Python
使用Python对Csv文件操作实例代码
2017/05/12 Python
Python实现堡垒机模式下远程命令执行操作示例
2019/05/09 Python
Python PyInstaller库基本使用方法分析
2019/12/12 Python
tensorflow通过模型文件,使用tensorboard查看其模型图Graph方式
2020/01/23 Python
如何在python中实现线性回归
2020/08/10 Python
python 中 .py文件 转 .pyd文件的操作
2021/03/04 Python
举例详解HTML5中使用JSON格式提交表单
2015/06/16 HTML / CSS
阿里巴巴国际站:Alibaba.com
2016/07/21 全球购物
英国天然有机美容护肤品:Neal’s Yard Remedies
2018/05/05 全球购物
一站式跨境收款解决方案:Payoneer(派安盈)
2018/09/06 全球购物
经济实惠的名牌太阳镜和眼镜:Privé Revaux
2021/02/07 全球购物
vue 中 get / delete 传递数组参数方法
2021/03/23 Vue.js
园林施工员岗位职责
2013/12/11 职场文书
《穷人》教学反思
2014/04/08 职场文书
实习生评语
2014/04/26 职场文书
小人国观后感
2015/06/11 职场文书
医院消毒隔离制度
2015/08/05 职场文书
创业计划书之o2o水果店
2019/08/30 职场文书
使用vue-element-admin框架从后端动态获取菜单功能的实现
2021/04/29 Vue.js
浅谈pytorch中stack和cat的及to_tensor的坑
2021/05/20 Python