Javascript是单线程的深入分析

本来想总结一下的,网上却发现有人已经解释的很清楚了,特转过来。

这也解释了为什么在用自动化测试工具来运行dumrendtree时设定的超时和测试case设定的超时的关联性。

面试的时候发现99%的童鞋不理解为什么JavaScript是单线程的却能让AJAX异步发送和回调请求,还有setTimeout也看起来像是多线程的?还有non-blocking IO, event loop等概念很不清楚。来深入分析一下:

首先看下面的代码:


1

2

3

4

5

6

7

8

9

function foo() {

    console.log(‘first‘ );

    setTimeout( ( function(){ console.log( ‘second‘ ); } ), 5);

}

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

    foo();

}

执行结果会首先全部输出first,然后全部输出second;尽管中间的执行会超过5ms。为什么?

Javascript是单线程的

因为JS运行在浏览器中,是单线程的,每个window一个JS线程,既然是单线程的,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。而浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中。javascript引擎是单线程处理它的任务队列,你可以理解成就是普通函数和回调函数构成的队列。当异步事件发生时,如mouse click, a timer firing, or an XMLHttpRequest completing(鼠标点击事件发生、定时器触发事件发生、XMLHttpRequest完成回调触发等),将他们放入执行队列,等待当前代码执行完成。

异步事件驱动

前面已经提到浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件、XMLHttpRequest完成回调等。当一个异步事件发生的时候,它就进入事件队列。浏览器有一个内部大消息循环,Event Loop(事件循环)会轮询大的事件队列并处理事件。例如,浏览器当前正在忙于处理onclick事件,这时另外一个事件发生了(如:window onSize),这个异步事件就被放入事件队列等待处理,只有前面的处理完毕了,空闲了才会执行这个事件。setTimeout也是一样,当调用的时候,js引擎会启动定时器timer,大约xxms以后执行xxx,当定时器时间到,就把该事件放到主事件队列等待处理(浏览器不忙的时候才会真正执行)。

每个浏览器具体实现主事件队列不尽相同,这不谈了。

浏览器不是单线程的

虽然JS运行在浏览器中,是单线程的,每个window一个JS线程,但浏览器不是单线程的,例如Webkit或是Gecko引擎,都可能有如下线程:

  • javascript引擎线程
  • 界面渲染线程
  • 浏览器事件触发线程
  • Http请求线程

很多童鞋搞不清,如果js是单线程的,那么谁去轮询大的Event loop事件队列?答案是浏览器会有单独的线程去处理这个队列。

Ajax异步请求是否真的异步?

很多童鞋搞不清楚,既然说JavaScript是单线程运行的,那么XMLHttpRequest在连接后是否真的异步? 
其实请求确实是异步的,这请求是由浏览器新开一个线程请求(见前面的浏览器多线程)。当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到 JavaScript引擎的事件处理队列中等待处理。当浏览器空闲的时候出队列任务被处理,JavaScript引擎始终是单线程运行回调函数。javascript引擎确实是单线程处理它的任务队列,能理解成就是普通函数和回调函数构成的队列。

总结一下,Ajax请求确实是异步的,这请求是由浏览器新开一个线程请求,事件回调的时候是放入Event loop单线程事件队列等候处理。

setTimeout(func, 0)为什么有时候有用?

写js多的童鞋可能发现,有时候加一个setTimeout(func, 0)非常有用,为什么?难道是模拟多线程吗?错!前面已经说过了,javascript是JS运行在浏览器中,是单线程的,每个window一个JS线程,既然是单线程的,setTimeout(func, 0)神奇在哪儿?那就是告诉js引擎,在0ms以后把func放到主事件队列中,等待当前的代码执行完毕再执行,注意:重点是改变了代码流程,把func的执行放到了等待当前的代码执行完毕再执行。这就是它的神奇之处了。它的用处有三个:

  • 让浏览器渲染当前的变化(很多浏览器UI render和js执行是放在一个线程中,线程阻塞会导致界面无法更新渲染)
  • 重新评估”script is running too long”警告
  • 改变执行顺序

例如:下面的例子,点击按钮就会显示"calculating....",如果删除setTimeout就不会。因为reDraw事件被进入事件队列到长时间操作的最后才能被执行,所以无法刷新。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

<button
id=
‘do‘>
Do long calc!</button>

<div
id=
‘status‘></div>

<div
id=
‘result‘></div>

$(‘#do‘).on(‘click‘,function(){

 

  $(‘#status‘).text(‘calculating....‘);//此处会触发redraw事件的fired,但会放到队列里执行,直到long()执行完。

 

  //
without set timeout, user will never see "calculating...."

  //long();//执行长时间任务,阻塞

  

  //
with set timeout, works as expected

  setTimeout(long,50);//用定时器,大约50ms以后执行长时间任务,放入执行队列,但在redraw之后了,根据先进先出原则

 

 })

 

 

 

function long(){

  var result
= 0

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

    for (var j
= 0; j<1000; j++){

      for (var k
= 0; k<1000; k++){

        result
= result + i+j+k

      }

    }

  }

  $(‘#status‘).text(‘calclation
done‘
)//
has to be in here for this example. or else it will ALWAYS run instantly. This is the same as passing it a callback

}

非阻塞js的实现(non-blocking javascript)

js在浏览器中需要被下载、解释并执行这三步。在html body标签中的script都是阻塞的。也就是说,顺序下载、解释、执行。尽管Chrome可以实现多线程并行下载外部资源,例如:script file、image、frame等(css比较复杂,在IE中不阻塞下载,但Firefox阻塞下载)。但是,由于js是单线程的,所以尽管浏览器可以并发加快js的下载,但必须依次执行。所以chrome中image图片资源是可以并发下载的,但外部js文件并发下载没有多大意义。

要实现非阻塞js(non-blocking javascript)有两个方法:1. html5 2. 动态加载js

首先一种办法是HTML5的deferasync关键字:

defer


1

<script
type=
"text/javascript" defer
src=
"foo.js"></script>

async


1

<script
type=
"text/javascript" async
src=
"foo.js"></script>

然后第二种方法是动态加载js:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

setTimeout(function(){

    var script
= document.createElement(
"script");

    script.type
=
"text/javascript";

    script.src
=
"foo.js";

    var head
=
true;//加在头还是尾

    if(head)

      document.getElementsByTagName("head")[0].appendChild(script);

    else

      document.body.appendChild(script);

},
0);

//另外一个独立的动态加载js的函数

function loadJs(jsurl,
head, callback){

    var script=document.createElement(‘script‘);

    script.setAttribute("type","text/javascript");

    

    if(callback){

        if (script.readyState){ //IE

            script.onreadystatechange
=
function(){

                if (script.readyState
==
"loaded" ||

                        script.readyState
==
"complete"){

                    script.onreadystatechange
=
null;

                    callback();

                }

            };

        }else //Others

            script.onload
=
function(){

                callback();

            };

        }

    }

    script.setAttribute("src",
jsurl);

    

    if(head)

     document.getElementsByTagName(‘head‘)[0].appendChild(script);

    else

      document.body.appendChild(script);

}


转自:http://www.cnblogs.com/Mainz/p/3552717.html

这篇文章也不错,可以迅速的了解一些架构:http://www.cnblogs.com/Mainz/archive/2012/09/08/2676618.html

时间: 2024-12-14 18:47:40

Javascript是单线程的深入分析的相关文章

[转]Javascript是单线程的深入分析

Javascript是单线程的深入分析 面试的时候发现99%的童鞋不理解为什么JavaScript是单线程的却能让AJAX异步发送和回调请求,还有setTimeout也看起来像是多线程的?还有non-blocking IO, event loop等概念很不清楚.来深入分析一下: 首先看下面的代码: function foo() { console.log( 'first' ); setTimeout( ( function(){ console.log( 'second' ); } ), 5);

Javascript是单线程的深入分析(转)

原文: http://blog.csdn.net/talking12391239/article/details/21168489 Javascript是单线程的 因为JS运行在浏览器中,是单线程的,每个window一个JS线程,既然是单线程的,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码.而浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中.javascript引擎是单线程处理它的任务队列,你可以理解

Javascript引擎单线程机制及setTimeout执行原理说明

setTimeout用法在实际项目中还是会时常遇到.比如浏览器会聪明的等到一个函数堆栈结束后才改变DOM,如果再这个函数堆栈中把页面背景先从白色设为红色,再设回白色,那么浏览器会认为DOM没有发生任何改变而忽略这两句话,因此我们可以通过setTimeout把“设回白色”函数加入下一个堆栈,那么就可以确保背景颜色发生过改变了(虽然速度很快可能无法被察觉). 总之,setTimeout增加了Javascript函数调用的灵活性,为函数执行顺序的调度提供极大便利. 然后,我们从基础的层面来看看:理解J

理解 Javascript 的单线程,着实不易

理解 Javascript 的单线程,着实不易. 比如,对于 C 系编程来说,写个 while(true) 不是什么令人紧张的事情.在 nodejs 中似乎也不会出什么意外,因为还有消息机制可以操控--这只是你对 nodejs 的幻觉,或者说,这只是我对 nodejs 的幻觉.若子进程写成这样的话: process.on('message', function() { while(true); }); process.on('SIGINT', function() { console.log('

关于javascript的单线程和异步的一些问题

关于js单线程和异步方面突然就糊涂了,看别人的文章越看越糊涂,感觉这方面是个坑,跳进去就不好跳出来.再去看,看着看着感觉自己明白了一些东西,也不知道对不对,反正是暂时把自己说服了,这样理解能理解的通,就总结了一下几个问题. 问题1:浏览器的线程与进程 看了很多资料,很多人对进程和线程是不加区分的,甚至上面说的说进程下面就改口为线程了.按我的理解进程是比线程大的一个概念,当一个程序运行就开辟单个或多个进程地址空间(IE.Firefox.Safari均是单进程模式,Chrome则上多进程大),然后各

JavaScript 是单线程的而且是异步的机制

浏览器中的js程序是单线程的,那异步调用是怎么实现的呢?计时器是靠谁实现的呢?单线程难道是一边执行程序一边计时吗? 好了 ----之前就有好多的疑问  ,现在按我的理解和大家说一说 一.JavaScript单线程 在浏览器中,执行JS程序只有一个线程,所以是单线程,所以执行顺序就是从上到下依次执行,同一段时间内只能有一段代码被执行.你可能会问,为什么不用多线程呢,这样不是更能充分利用CPU吗? ps:只能说早期的页面非常简单,所以我认为设计者没有考虑多线程的问题 (本人猜想的哈,勿喷!!).另外

Javascript的单线程和异步编程

运行时概念 下面的内容解释了一个理论上的模型.现代 JavaScript 引擎着重实现和优化了描述的几个语义. 可视化描述 栈 函数调用形成了一个栈帧. function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); 当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量.当bar调用foo时,第二个帧就被

我想这次我真的理解了 JavaScript 的单线程机制

今天面试的时候被问到一个问题,是关于 JS 异步的.当时我脑海中闪过了一个单线程的概念,但却没有把真正的原理阐述清楚.所以回来特意重新回顾了前面单线程和异步相关的一些知识点. 虽然之前学习的时候也接触了单线程模型相关的东西,但当时理解得并不是很清楚和明白.所以这道面试题也没有给出一语中的的答案.重新阅读阮一峰的 <JavaScript 运行机制详解>和我之前写的<setTimeout 异步与回调>之后.我决定重新写一篇博客来更加白话的总结 JS 的单线程机制和异步. 重演历史 -

JavaScript是单线程还是多线程(转)

多线程要考虑线程之间的资源抢占,死锁,冲突之类一系列问题.JavaScript作为一门客户端脚本,貌似没有多线程的一些列问题.那么JavaScript是单线程还是多线程?通过查资料总结了JavaScript运行的原理.如下: 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScri