setTimeout那些事儿

setTimeout那些事儿之单线程

一直以来,大家都在说Javascript是单线程,浏览器无论在什么时候,都且只有一个线程在运行JavaScript程序。

但是,不知道大家有疑问没——就是我们在编程过程中的setTimeout(类似的还有setInterval、Ajax),不是异步执行的吗?!!

例如:

<!DOCTYPE html>
    <head>
        <title>setTimeout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            console.log("a");
            //利用setTimeout延迟执行匿名函数
            setTimeout(function(){
                console.log("b");
            },100);
            console.log("c");
        </script>
    </body>
</html>

运行代码,打开chrome调试器,得如下结果

这个结果很容易理解,因为我setTimeout里的内容是在100ms后执行的嘛,当然是先输出a,再输出c,100ms后再输出setTimeout里的b嘛。

咦,那Javascript这不就不是单线程了嘛,这不就可以实现多线程了?!!

其实,不是的。setTimeout没有打破JavaScript的单线程机制,它其实还是单线程。

为什么这么说呢,那就得理解setTimeout到底是个怎么回事。

请看下面的代码,猜结果:

<!DOCTYPE html>
    <head>
        <title>setTimeout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            var date = new Date();
            //打印才进入时的时间
            console.log(‘first time: ‘ + date.getTime());
            //一秒后打印setTimeout里匿名函数的时间
            setTimeout(function(){
                var date1 = new Date();
                console.log(‘second time: ‘ + date1.getTime() );
                console.log( date1.getTime() - date.getTime() );
            },1000);
            //重复操作
            for(var i=0; i < 10000 ; i++){
                console.log(1);
            }
        </script>
    </body>
</html>

看了上面的代码,猜猜输出的结果是多少呢?1000毫秒?

我们打开chrome调试器,见下图

纳尼,怎么不是1000毫秒呢?!!!

我们再看看下面的代码:

<!DOCTYPE html>
    <head>
        <title>setTimeout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            //一秒后执行setTimeout里的匿名函数,alert下
            setTimeout(function(){
                alert("monkey");
            },1000);
            while(true){};
        </script>
    </body>
</html>

运行代码后!

我靠,怎么一直刷新,浏览器卡死了呢,并且没有alert!!

按道理,即使我while无限循环,在1秒后也得alert一下啊。

种种问题皆一个原因,JavaScript是单线程

要记住JavaScript是单线程,setTimeout没有实现多线程,它背后的真相是这样滴:

JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。

浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程

    *JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。

    *GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。

    *事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。

so,通过以上讲解,以上种种问题迎刃而解。

setTimeout那些事儿之延迟时间为0

当setTimeout的延迟时间为0时,大家想想它会怎么执行呢?

例如下面的代码:

<!DOCTYPE html>
    <head>
        <title>setTimeout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            console.log(‘a‘);
            setTimeout(function(){
                console.log(‘b‘);
            },0);
            console.log(‘c‘);
            console.log(‘d‘);
        </script>
    </body>
</html>

运行代码结果如下:

假设你已经知道Javascript单线程的运行原理了。那么,可能会有这样的疑问:setTimeout的时间为0,都没加到处理队列的末尾,怎么会晚执行呢?不应该立即执行吗?

我的理解是,就算setTimeout的时间为0,但是它仍然是setTimeout啊,原理是不变的。所以会将其加入到队列末尾,0秒后执行。

况且,经过查找资料发现,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动。

这个最小的时间间隔是多少呢?

这和浏览器及操作系统有关。在John Resig的新书《Javascript忍者的秘密》一书中提到--Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒),另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。

说了这么多,setTimeout的延迟时间为0,看来没什么意义嘛,都是放在队列后执行嘛。

非也,天生我材必有用,就看你怎么用咯。抛砖迎玉下。

1、可以用setTimeout的延迟时间为0,模拟动画效果哦。

详情请见下代码:

 1 <!DOCTYPE html>
 2     <head>
 3         <title>setTimeout</title>
 4         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 5     </head>
 6     <body>
 7         <div id="container" style="width:100px;height:100px;border:1px solid black;"></div>
 8         <div id="btn" style="width:40px;height:40px;line-height:40px;margin-top:20px;background:pink;">click</div>
 9         <script>
10             window.onload = function(){
11                 var con = document.getElementById(‘container‘);
12                 var btn = document.getElementById(‘btn‘);
13                 //Params: i 为起始高度,num为预期高度
14                 function render(i, num) {
15                     i++;
16                     con.style.height = i + ‘px‘;
17                     //亮点在此
18                     if(i < num){
19                         setTimeout(function() {
20                             render(i, num);
21                         },0);
22                     }
23                     else {
24                         con = null;
25                         btn = null;
26                     }
27                 };
28                 btn.onclick = function(){
29                     render(100, 200);
30                 };
31             };
32         </script>
33     </body>
34 </html>

由于是动画,所以想看其效果,还请各位看官运行下代码哦。

代码第19行中,利用setTimeout,在每一次render执行完成(给高度递增1)后,由于Javascript是单线程,且setTimeout里的匿名函数会在render执行完成后,再执行render。所以可以实现动画效果。

2、可以用setTimeout的延迟时间为0,实现捕获事件哦。

当我们点击子元素时,我们可以利用setTimeout的特性,来模拟捕获事件。

请见如下代码:

<!DOCTYPE html>
    <head>
        <title>setTimeout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <style>
            #parent {
                width:100px;
                height:100px;
                border:1px solid black;
            }
            #child {
                width:50px;
                height:50px;
                background:pink;
            }
        </style>
    </head>
    <body>
        <div id="parent">
            <div id="child"></div>
        </div>
        <script>
            //点击子元素,实现子元素的事件在父元素触发后触发
            window.onload = function(){
                var parent = document.getElementById(‘parent‘);
                var child = document.getElementById(‘child‘);
                parent.onclick = function(){
                    console.log(‘parent‘);
                }
                child.onclick = function(){
                    //利用setTimeout,冒泡结束后,最后输出child
                    setTimeout(function(){
                        console.log(‘child‘);
                    },0);
                }
                parent = null;
                child = null;
            }
        </script>
    </body>
</html>

 执行代码,点击粉红色方块,输出结果:

setTimeout那些事儿之this

说到this,对于它的理解就是:this是指向函数执行时的当前对象,倘若没有明确的当前对象,它就是指向window的。

好了,那么我们来看看下面这段代码:

<!DOCTYPE html>
    <head>
        <title>setTimeout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
           var name = ‘!!‘;
           var obj = {
               name:‘monkey‘,
               print:function(){
                   console.log(this.name);
               },
               test:function(){
                   //this.print
                   setTimeout(this.print,1000);
               }
           }
           obj.test();
        </script>
    </body>
</html>

通过chrome调试器,查看输出结果:

咦,它怎么输出的是”!!”呢?不应该是obj里的”monkey”吗?!!

这是因为setTimeout中所执行函数中的this,永远指向window。

不对吧,那上面代码中的setTimeout(this.print,1000)里的this.print怎么指向的是obj呢?!!

注意哦,我这里说的是“延迟执行函数中的this”,而不是setTimeout调用环境下的this。

什么意思?

setTimeout(this.print,1000),这里的this.print中的this就是调用环境下的;

而this.print=function(){console.log(this.name);},这个匿名函数就是setTimeout延迟执行函数,其中的this.name也就是延迟执行函数中的this啦。

嘿嘿,这下明白了吧。

var age = 24;
function Fn(){
    this.age = 18;
    setTimeout(function(){
        //this代表window
        console.log(this);
        //输出24,而不是Fn的18
        console.log(this.age);
    },1000);
}
new Fn();                

咦,那有个疑问,比如我想在setTimeout延迟执行函数中的this指向调用的函数呢,而不是window?!!我们该怎么办呢。

常用的方法就是利用that。

that?

对,that。利用闭包的知识,让that保证你传进去的this,是你想要的。

详情见下:

var age = 24;
function Fn(){
    //that在此
    var that = this;
    this.age = 18;
    setTimeout(function(){
        console.log(that);
        console.log(that.age);
    },1000);
}
new Fn();       

还有一种方法就是,利用bind。

如下:

var age = 24;
function Fn(){
    this.age = 18;
    //bind传入this
    setTimeout(function(){
        console.log(this);
        console.log(this.age);
    }.bind(this),1000);
}
new Fn();

好了时间也不早,晚安~另外,清明快乐哦

时间: 2024-12-11 18:37:00

setTimeout那些事儿的相关文章

call,apply,bind

call,apply,bind 一.call&apply call, apply都属于Function.prototype的方法,因为属于Function.prototype,所以每个Function对象实例,也就是每个方法都有call, apply属性啦. 如果不明白,请见“Javascript之一切皆为对象3”. 而且它们的作用都是一样的,只是使用方式不同而已. 作用:借用别人的方法来调用,就像自己有这个方法一样. 咦,那它们怎样才能达到这目的呢? 对象. 对象? 是的,其实就是改变执行上下

浅谈webWorker

一.webWorker之初体验 在"setTimeout那些事儿"中,说到JavaScript是单线程.也就是同一时间只能做同一事情. 也好理解,作为浏览器脚本语言,如果JavaScript不是单线程,那么就有点棘手了.比如,与用户交互或者对DOM进行操作时,在一个线程上修改某个DOM,另外的线程删除DOM,这时浏览器该如何抉择呢? 所以,JavaScript是单线程也是有背景的. 如下: <!DOCTYPE html>     <head>         &

XSS与字符编码的那些事儿

目录 0x00:基本介绍 0x01:html实体编码 0x02:新增的实体编码 实体编码变异以及浏览器的某些工作原理! 0x03:javascript编码 0x04:base64编码 0x05:闲扯 0x00基本介绍 提起XSS 想到的就是插入字符字符编码与各种解析了! 这也就是各种xss编码插件跟工具出世的原因!之前不懂浏览器是如何对我们编码过的代码进行解析的时候就是一顿乱插! 各种编码 各种插 没把编码还原就算了 还原了就算运气好!后来到PKAV经过二哥和短短的调教后才算是弄清楚了一点编码与

菜单的那些事儿(一)

菜单的那些事儿(一) 首先呢,先做简单的二级导航菜单,在这里,我们用伪类的方法来实现鼠标点击出现二级菜单的效果,代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>demo1</title> <style type="text/css"> *{ margin: 0; padding: 0; list-st

util.promisify 的那些事儿

util.promisify是在node.js 8.x版本中新增的一个工具,用于将老式的Error first callback转换为Promise对象,让老项目改造变得更为轻松. 在官方推出这个工具之前,民间已经有很多类似的工具了,比如es6-promisify.thenify.bluebird.promisify. 以及很多其他优秀的工具,都是实现了这样的功能,帮助我们在处理老项目的时候,不必费神将各种代码使用Promise再重新实现一遍. 工具实现的大致思路 首先要解释一下这种工具大致的实

setTimeout的异步传输机制

setTimeout是异步的,在设置完setTimeout后,指定代码会在设定的时间后加入到任务队列,但并不是立即执行,js是单线程语言,所有的代码按顺序执行,即同步执行,同步执行的代码放在执行队列中,而异步执行的setTimeout放在任务队列中,执行顺序是先执行完执行队列中的代码再去查看任务队列中是否有要执行的代码: 这段代码看上去好像4应该比5先打印出来,但实际上是先打印出5再打印出4: 就如上面所说的,1,3,5都放在执行队列中,而4,2放在任务队列中,所以4,2要等执行队列中的1,3,

setTimeout ,setInterval

1.setInterval是间隔执行,间隔多久执行一次,执行多次 setInvertal(function(){},500) 2.setTimeout是延迟执行,执行一次 setTimeout(function(){},500) 清除定时器 param=setInterval(function(){},300) clearInterval(param)关闭定时器

有些事儿,工程师可能今生仅此一次

郑昀 创建于2016/9/15 最后更新于2016/9/18 关键词:深度思考,碎片化阅读,做论文,深入研究, 早先在<技术高手如何炼成>一文中提到,我会问面试者,你日常如何构建自己的知识体系.有人会觉得你怎么就问出这么宏大的问题?知识体系,这是什么鬼? 面试时的交谈 工作之后你做过这样的事情吗? 面试是一个谁主张谁举证的过程,有时候需要面试者举出实例,自我证明. 而我认为问一些我们工作中遇到的难题和业务场景是在“欺负”面试者,所以我喜欢问开放型问题: 在你工作之后,你有没有像做毕业论文一样对

JS setTimeout clearTimeout

<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>计时器</title> </head> <script type="text/javascript"> var num=0; var i; fu