知道html5 Web Worker标准吗?能实现JavaScript的多线程?
js为什么是单线程?
主要是由于最开始javascript是单纯的服务于浏览器的一种脚步语言(那时候没有nodejs)。浏览器是为了渲染网页,通过dom与客户交互,假如一个线程需要给dom执行click事件,而另一个进程要删除这个dom,这2个动作可能同时进行,也可能先后进行(像java,c#等语言中会引入锁的概念,这样会变得异常复杂),那么就会造成很多不可意料的错误。
所以,为了避免复杂性,从一诞生,
JavaScript就是单线程,这已经成了这门语言的核心特征。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创立多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
浏览器是多线程的
浏览器打开一个tab,就会单独开一个进程,这个进程包含多个线程,参考:JS运行机制
主要包含的线程有:
- GUI渲染线程
负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
当界面需要重绘(Repaint)或者因为某种操作引发回流(reflow)时,该线程就会执行
注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI升级会被保存在一个队列中等到JS引擎空闲时立即被执行。
- 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。
- 异步http请求线程
在
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>
5.gif
可以看到,一开始网页和动画正常运行,但是开始执行计算斐波那契数列后,动画就中止了,页面也中止响应鼠标的click事件了,直到recurFib(40)计算出结果后,动画才开始继续执行,而期间积攒的click事件也在一起被执行。这就解释了GUI渲染线程与JS引擎线程互斥。因为这个弊端HTML5提出Web Worker标准。
利用Web Worker开启一个子线程
Web Worker 有以下几个使用注意点。
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来处理上面卡慢的问题。
<!--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); }})运行结果:
7.png
可以看到整个运行过程动画没有卡慢,也能响应click事件,所以在我们遇到大型计算的时候,请单独开启一个worker子线程来处理js线程阻塞GUI线程的问题。上文中只涉及到一部分worker API。关于worker更详细更具体的用法可以参见: Web Worker 使用教程
兼容性
1.png
可以看到除了Opera Mini浏览器,连IE都能使用了,所以兼容性问题不大。
总结
- 因为
javaScript的最初设计特点,采用了单线程的运行机制。 - 浏览器是多个线程相互协作来工作的,但是GUI渲染线程与JS引擎线程互斥。
js线程在运行时,会锁死GUI渲染线程,为了利用多核CPU的计算能力,HTML5提出Web Worker标准。Web Worker的使用有少量限制,比方说:同源限制,DOM限制,文件限制等,但能处理在js需要大量计算工作时,页面卡慢的问题。Web Worker实际上是js线程的一个子线程,理论上js还是单线程的。
学习如逆水行舟,不进则退,前台技术飞速发展,假如每天不坚持学习,就会跟不上,我会陪着大家,每天坚持推送博文,跟大家一同进步,希望大家能关注我,第一时间收到最新文章。
个人公众号:
长按保存关注
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 知道html5 Web Worker标准吗?能实现JavaScript的多线程?