yixiao0 2018-09-07
对线程与进程、同步与异步了解不深,有了这样的困惑。翻了一些帖子,看了大牛的文章,按自己的理解总结一下。
1. JavaScript单线程
JavaScript是单线程的,指浏览器进程中只有一个js的执行线程,也就是同一时间内只有一段js代码(或者说一个任务)在执行。但如我们所想,多线程的执行效率会更高些,那为什么js不能有多线程呢?
JavaScript作为浏览器的脚本语言,用于与用户的交互和dom的操作,设想如果有两个线程同时操作一个dom,难免会有冲突发生。所以单线程是JavaScript语言本身的特性。
2. JavaScript异步机制
JavaScript是单线程执行的,但浏览器是多线程的,所以JavaScript异步机制是由浏览器的两个及以上常驻线程:js执行线程 和 事件触发线程等共同实现的。js执行线程作为主线程执行js代码,遇到异步执行语句时会向浏览器发送一个异步请求并告知回调函数。浏览器开一个新的线程来处理请求,js执行线程继续处理其他任务。事件触发线程监听这个请求,当请求完成时就将事件压入任务队列中,等待进入到主线程中执行。
说到这,得详细说说JavaScript的运行机制了。
js执行的任务分为两种:同步任务(synchronous) 和 异步任务(asynchronous)。同步任务指排列在主线程中等待执行的任务,这些任务形成了一个执行栈(execution content stack),只有前一个任务执行完,后一个任务才能执行。异步任务指不进入主线程而进入任务队列(task queue)的任务,当执行栈任务执行完后,任务队列中的任务才会进入到主线程中执行。
任务队列是一个事件的队列,包括I/O事件、定时器、用户事件(点击事件、滚屏事件等)。异步请求一定会指定回调函数,回调函数在异步请求执行期间被主线程挂起,当主线程执行异步任务时就是在执行回调函数。
主线程从任务队列中读取任务的过程是不断循环的,称作 Event Loop(事件循环),当执行栈(同步任务)一空就去读取任务队列(异步任务)。
举例
1. Ajax发送异步请求时的代码:
var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function (){}; req.onerror = function (){}; req.send();
与下面等价:
var req = new XMLHttpRequest(); req.open('GET', url); req.send(); req.onload = function (){}; req.onerror = function (){};
因为req.send()方法属于异步任务,而指定回调函数是执行栈的一部分,属于同步任务。不管send()放在前还是后,都会先处理执行栈中的任务,再读取任务队列中的任务,所以指定回调函数的语句总是先执行,与send()位置无关。
2. 定时器
定时器功能主要由setTimeout()和setInterval()实现。由浏览器的定时线程执行计数,事件触发线程监听到计数完毕后,将事件压入到任务队列的尾部等待执行。如setTimeout(fn,0) 会在当前执行栈任务和任务队列前排的任务都执行完毕后立即执行fn函数。