基本概念
1、js的执行过程是单线程的模式,也就是同步进行,只有前面的代码执行完了才会往下面执行
2、但是执行js代码也只是浏览器的线程之一所负责的事情,这个线程被称为js引擎,浏览器还具有其他线程:界面渲染线程(UI)、浏览器事件触发线程(控制交互,响应用户)、http请求线程(处理请求,而ajax发送请求则会委托浏览器新开一个http线程)、EventLoop轮询线程(负责轮询消息队列)
3、浏览器中js代码的作用:执行JavaScript代码 、对用户的输入(包含鼠标点击、键盘输入等等)做出反应 、处理异步的网络请求
js单线程
1、单线程的含义是js只能在一个线程上运行,也就说,js同时只能执行一个js任务,其它的任务则会排队等待执行。
2、js是单线程的,并不代表js引擎线程只有一个。js引擎有多个线程,一个主线程,其它的后台配合主线程。
3、多线程之间会共享运行资源,浏览器端的js会操作dom,多个线程必然会带来同步的问题,所有js核心选择了单线程来避免处理这个麻烦。js可以操作dom,影响渲染,所以js引擎线程和UI线程是互斥的。这也就解释了js执行时会阻塞页面的渲染
js消息队列
1、JavaScript运行时,除了一个运行线程,引擎还提供一个消息队列,里面是各种需要当前程序处理的消息。新的消息进入队列的时候,会自动排在队列的尾端
2、单线程意味着js任务需要排队,如果前一个任务出现大量的耗时操作,后面的任务得不到执行,任务的积累会导致页面的“假死”。这也是js编程一直在强调需要回避的“坑”
js执行任务方式
1、首先js任务分两种:同步任务、异步任务
2、同步任务:在主线程排队支持的任务,前一个任务执行完毕后,执行后一个任务,形成一个执行栈,线程执行时在内存形成的空间为栈,进程形成堆结构,这是内存的结构。执行栈可以实现函数的层层调用。注意不要理解成同步代码进入栈中,按栈的出栈顺序来执行。
3、异步任务:会被主线程挂起,不会进入主线程,而是进入消息队列,而且必须指定回调函数,只有消息队列通知主线程,并且执行栈为空时,该消息对应的任务才会进入执行栈获得执行的机会。
4、主线程说明:
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个”任务队列”(消息队列)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”(消息队列),看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
5、消息队列说明:
(1)消息队列队列(或者叫任务队列)是一个事件的队列,IO响应时(鼠标点击等输入输出设备的操作),会往队列中添加一个消息,此时说明相关的异步代码到了执行的时机,可以进入主线程的执行栈了。
(2)主线程读取消息队列,可以读取到对应的事件。
(3)消息队列可以响应IO事件,还有用户产生的事件(比如点击鼠标,页面滚动),只要指定了回调函数,就会进入消息队列,等待EventLoop轮询线程处理,是否可以进入主线程的执行栈。
(4)消息和回调函数相互联系的含义:主线程读到消息,就会执行相应的回调函数;进入消息队列的消息,必须对应相应的回调函数,否则这个消息会被丢弃不会进入消息队列。
(5)消息队列是一个先进先出的队列结构,这就决定了它的执行顺序,先产生的消息会被主线程先读取,会不会执行则会先检查一下执行时间,因为存在setTimeout等定时函数,这类事件产生的消息进入到消息队列,被执行的时机取决与它在队列中的位置和执行时间有关。【使用setTimeout能够避免阻塞UI线程就是这个原因】。
5、需要注意的是:执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。
EventLoop
1、主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
2、简单说,浏览器的两个线程:一个负责程序本身的运行,称为”主线程”;另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为”Event Loop线程”(可以译为”消息线程”)。
3、由于js是运行在单线程上的,所有浏览器单独开启一个线程来处理事件消息的轮询,避免阻塞js的执行。
异步代码执行逻辑
1、每当遇到I/O的时候,主线程就让EventLoop线程去通知相应的I/O程序,然后接着往后运行,所以不存在等待时间。等到I/O程序完成操作,EventLoop线程把消息添加到消息队列,主线程就调用事先设定的回调函数,完成整个任务。
2、js的ajax是new XMLHttpRequest()对象实现的,浏览器会新开一个线程来处理http请求,这就是ajax能够实现局部刷新的同时,还能响应用户交互的原因。
定时器
1、前面也提到了定时器,定时器是会在进入消息队列,这也就和异步代码的执行逻辑一样了。它在”消息队列”的尾部添加一个消息,因此要等到同步任务和”消息队列”现有的任务都处理完,才会得到执行的机会,还要看定时器设置的时间是否到了才会执行。
<script> for(var i = 0 ; i < 10; i++){ setTimeout(function(){ console.log(i);//打印10次10 },0); } </script>
所以,只有等到主线程的任务执行完之后,setTimeout中的事件才会被执行,虽然时间间隔是0秒,但是必须等主线程任务完成,所以最后打印的都是10
2、可以用闭包来解决问题,每次执行for循环的时候,把函数作为返回值给setTimeout作为参数,函数里面存着从for循环里面拿到的每一个值,下面代码会返回多个函数,每个函数的j值都不一样
<script> for(var i = 0; i< 3; i++){ function foo(j){ // var j; // j = 实参 //j = i return function(){ console.log(j); }; } //0 var f = foo(i); setTimeout(f, 0); } </script>
参考:
http://blog.csdn.net/w2765006513/article/details/53743051
https://www.cnblogs.com/haodawang/articles/5850822.html