主要是由于最开始javascript
是单纯的服务于浏览器的一种脚步语言(那时候没有nodejs
)。浏览器是为了渲染网页,经过dom
与用户交互,若是一个线程须要给dom
执行click
事件,而另外一个进程要删除这个dom
,这2个动做可能同时进行,也可能前后进行(像java,c#
等语言中会引入锁的概念,这样会变得异常复杂),那么就会形成不少不可预料的错误。javascript
因此,为了不复杂性,从一诞生,JavaScript
就是单线程,这已经成了这门语言的核心特征。为了利用多核CPU的计算能力,HTML5
提出 Web Worker标准,容许JavaScript
脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM
。因此,这个新标准并无改变JavaScript
单线程的本质。
浏览器打开一个tab,就会单独开一个进程,这个进程包含多个线程,参考:JS运行机制
主要包含的线程有:html
负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
当界面须要重绘(Repaint)或因为某种操做引起回流(reflow)时,该线程就会执行
注意, GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(至关于被冻结了),GUI更新会被保存在一个队列中 等到JS引擎空闲时当即被执行。
也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
JS引擎线程负责解析Javascript脚本,运行代码。
JS引擎一直等待着任务队列中任务的到来,而后加以处理,一个Tab页(renderer进程)中不管何时都只有一个JS线程在运行JS程序
一样注意, GUI渲染线程与JS引擎线程是互斥的,因此若是JS执行的时间过长,这样就会形成页面的渲染不连贯,致使页面渲染加载阻塞。
归属于浏览器而不是JS引擎,用来控制事件循环(能够理解,JS引擎本身都忙不过来,须要浏览器另开线程协助)
当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其余线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
注意,因为JS的单线程关系,因此这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
传说中的setInterval
与setTimeout
所在线程
浏览器定时计数器并非由JavaScript引擎计数的,(由于JavaScript引擎是单线程的, 若是处于阻塞线程状态就会影响记计时的准确)
所以经过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
在XMLHttpRequest
在链接后是经过浏览器新开一个线程请求
将检测到状态变动时,若是设置有回调函数,异步线程就 产生状态变动事件,将这个回调再放入事件队列中。再由JavaScript
引擎执行。
上面列出的线程之间,有一个重要的规则是:GUI渲染线程与JS引擎线程互斥,那么咱们能够得出如下结论JS阻塞页面加载,那么在js
运行的这段时间内,GUI
的渲染会中止,这段时间内的界面交互,DOM
的重绘与回流会中止,会被保存到待执行队列中,直到js
线程空闲,才会执行这些队列。
咱们用下面的一段代码和运行结果来讲明这个机制:前端
<html> <head> <style> .box { width: 200px; height: 200px; margin-top: 100px; background: #f09; animation: bounce 2s linear 0s infinite alternate; background-image: linear-gradient(45deg, #3023AE 0%, #f09 100%); } @keyframes bounce { 0% { border-radius: 40% 60% 72% 28% / 70% 77% 23% 30%; } 100% { border-radius: 75% 25% 24% 76% / 13% 15% 85% 87%; } } </style> </head> <body> <div class="box"></div> </body> <script> // 计算斐波那契数列,这个数列从第3项开始,每一项都等于前两项之和。 function recurFib(n) { if (n < 2) { return n; } else { return recurFib(n - 1) + recurFib(n - 2) } } window.onload = function () { setTimeout(function () { console.time("运算耗时:") // 计算n为40的结果 console.log('结果:', recurFib(40)) console.timeEnd("运算耗时:") }, 2000) document.getElementsByClassName("box")[0].addEventListener('click', function () { console.log('click') }) } </script> </html>
能够看到,一开始网页和动画正常运行,可是开始执行计算斐波那契数列后,动画就中止了,页面也中止响应鼠标的click
事件了,直到recurFib(40)
计算出结果后,动画才开始继续执行,而期间积攒的click
事件也在一块儿被执行。这就解释了GUI渲染线程与JS引擎线程互斥。因为这个弊端HTML5
提出Web Worker标准。java
Web Worker 有如下几个使用注意点。node
1.同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
2.DOM 限制
Worker 线程所在的全局对象,与主线程不同,没法读取主线程所在网页的 DOM 对象,也没法使用document
、window
、parent
这些对象。可是,Worker 线程能够navigator
对象和location
对象。
3.通讯联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通讯,必须经过消息完成。
4.脚本限制
Worker 线程不能执行alert()
方法和confirm()
方法,但可使用 XMLHttpRequest 对象发出 AJAX 请求。
5.文件限制
Worker 线程没法读取本地文件,即不能打开本机的文件系统(file:
),它所加载的脚本,必须来自网络。
以上规则引用阮一峰老师的: Web Worker 使用教程
建立Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,彻底受主线程控制,并且不能操做DOM)
JS引擎线程与worker线程间经过特定的方式通讯(postMessage API
,须要经过序列化对象来与线程交互特定的数据)。
下面咱们用worker
的相关api
来解决上面卡顿的问题。web
<!--index.html主线程--> <html> <head> <style> .box { width: 200px; height: 200px; margin-top: 100px; background: #f09; animation: bounce 2s linear 0s infinite alternate; background-image: linear-gradient(45deg, #3023AE 0%, #f09 100%); } @keyframes bounce { 0% { border-radius: 40% 60% 72% 28% / 70% 77% 23% 30%; } 100% { border-radius: 75% 25% 24% 76% / 13% 15% 85% 87%; } } </style> </head> <body> <div class="box"></div> </body> <script> window.onload = function () { // 建立一个子线程worker实例 var worker = new Worker('./test.js'); setTimeout(function () { // 通讯:向子线程发送消息 worker.postMessage('start') }, 2000) worker.addEventListener('message', function(res) { // 通讯:收到子线程消息 console.log('result:',JSON.stringify(res.data)); // 关闭worker线程 worker.terminate(); }) document.getElementsByClassName("box")[0].addEventListener('click', function () { console.log('click') }) } </script> </html>
// test.js子线程代码 // 经过监听message来接受主线程中的消息 addEventListener('message', function(res) { // 子线程向主线程中发生消息 // 计算斐波那契数列,这个数列从第3项开始,每一项都等于前两项之和。 if(res.data === 'start') { // 开始运算 console.log('收到主线程消息,开始运算') function recurFib(n) { if(n < 2){ // 主动关闭子线程 // this.close() return n ; }else { return recurFib(n-1)+recurFib(n-2) } } console.time("运算时间:") // 计算n为40的结果 var count = recurFib(40) console.timeEnd("运算时间:") // 向主线程发送消息 console.log('运算完毕,发送消息给主线程!') this.postMessage(count); } })
运行结果:c#
能够看到整个运行过程动画没有卡顿,也能响应click
事件,因此在咱们遇到大型计算的时候,请单独开启一个worker
子线程来解决js
线程阻塞GUI
线程的问题。上文中只涉及到一部分worker API
。关于worker
更详细更具体的用法能够参见: Web Worker 使用教程segmentfault
能够看到除了Opera Mini浏览器,连IE都能使用了,因此兼容性问题不大。api
javaScript
的最初设计特色,采用了单线程的运行机制。js
线程在运行时,会锁死GUI
渲染线程,为了利用多核CPU的计算能力,HTML5
提出Web Worker标准。Web Worker
的使用有一些限制,好比说:同源限制,DOM
限制,文件限制等,但能解决在js
须要大量计算工做时,页面卡顿的问题。Web Worker
其实是js
线程的一个子线程,理论上js
仍是单线程的。学习如逆水行舟,不进则退,前端技术飞速发展,若是天天不坚持学习,就会跟不上,我会陪着你们,天天坚持推送博文,跟你们一同进步,但愿你们能关注我,第一时间收到最新文章。
我的公众号:浏览器