深入探寻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 相关文章推荐
js 操作css实现代码
Jun 11 Javascript
Javascript document.referrer判断访客来源网址
May 15 Javascript
javascript qq右下角滑出窗口 sheyMsg
Mar 21 Javascript
JavaScript词法作用域与调用对象深入理解
Nov 29 Javascript
Js base64 加密解密介绍
Oct 11 Javascript
js中的DOM模拟购物车功能
Mar 22 Javascript
JavaScript设计模式之单例模式详解
Jun 09 Javascript
javascript checkbox/radio onchange不能兼容ie8处理办法
Jun 13 Javascript
Vue项目webpack打包部署到服务器的实例详解
Jul 17 Javascript
vue.js组件vue-waterfall-easy实现瀑布流效果
Aug 22 Javascript
关于jQuery里prev()的简单操作代码
Oct 27 jQuery
vue项目初始化到登录login页面的示例
Oct 31 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分页显示制作详细讲解
2006/12/05 PHP
PHP中的多行字符串传递给JavaScript的两种方法
2014/06/19 PHP
php实现处理输入转义字符的代码
2015/11/08 PHP
基于PHP实现通过照片获取ip地址
2016/04/26 PHP
PHP流Streams、包装器wrapper概念与用法实例详解
2017/11/17 PHP
动手学习无线电
2021/03/10 无线电
stream.js 一个很小、完全独立的Javascript类库
2011/10/28 Javascript
jQuery操作CheckBox的方法介绍(选中,取消,取值)
2014/02/04 Javascript
JS替换字符串中字符即替换全部而不是第一个
2014/06/04 Javascript
Bootstrap所支持的表单控件实例详解
2016/05/16 Javascript
jQuery EasyUI Panel面板组件使用详解
2017/02/28 Javascript
微信小程序新增的拖动组件movable-view使用教程
2017/05/20 Javascript
redux.js详解及基本使用
2019/05/24 Javascript
七行JSON代码把你的网站变成移动应用过程详解
2019/07/09 Javascript
微信小程序 自定义弹窗实现过程(附代码)
2019/12/05 Javascript
简述Python中的面向对象编程的概念
2015/04/27 Python
Python的SQLAlchemy框架使用入门
2015/04/29 Python
Python版微信红包分配算法
2015/05/04 Python
python中单例常用的几种实现方法总结
2018/10/13 Python
Python3 使用cookiejar管理cookie的方法
2018/12/28 Python
python使用梯度下降算法实现一个多线性回归
2020/03/24 Python
ipython jupyter notebook中显示图像和数学公式实例
2020/04/15 Python
通过Python实现Payload分离免杀过程详解
2020/07/13 Python
python中的时区问题
2021/01/14 Python
IE下实现类似CSS3 text-shadow文字阴影的几种方法
2011/05/11 HTML / CSS
澳大利亚婴儿喂养品牌:Cherub Baby
2018/11/01 全球购物
德国最大的网上足球商店:11teamsports
2019/09/11 全球购物
跳蚤市场口号
2014/06/13 职场文书
2014年乡镇领导个人整改措施
2014/09/19 职场文书
毕业证委托书范文
2014/09/26 职场文书
依法行政工作汇报
2014/10/28 职场文书
党的群众路线教育实践活动制度建设计划
2014/11/03 职场文书
升职自荐信范文
2015/03/27 职场文书
JS继承最简单的理解方式
2021/03/31 Javascript
详解Redis实现限流的三种方式
2021/04/27 Redis
分析Python感知线程状态的解决方案之Event与信号量
2021/06/16 Python