JavaScript单线程和浏览器事件循环简述
JavaScript单线程
在上篇博客《Promise的前世今生和妙用技巧》的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型。应很多网友的回复,在这篇文章中将继续展开这一个话题。当然这里是博主的一些理解,如果还存在什么纰漏的话,请不吝指教。
JavaScript这门语言运行在浏览器中,是以单线程的方式运行的。说到单线程,就得从操作系统进程开始说起。进程和线程都是操作系统的概念。进程是应用程序的执行实例,每一个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成;进程在运行过程中能够申请创建和使用系统资源(如独立的内存区域等),这些资源也会随着进程的终止而被销毁。而线程则是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制。在系统创建进程之后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程是由系统进程所创建的,同时用户也可以自主创建其它线程,这一系列的线程都会并发地运行于同一个进程中。
在多线程操作的情况下可以实现应用的并行处理,而提高整个应用程序的性能和吞吐量,更大粒度的榨取本机的CPU利用率,特别是现代很多语言都支持了多核并行处理技术。然后JavaScript居然还是单线程执行,为什么呢?
这是因为JavaScript这门脚本语言诞生的使命所致:JavaScript为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果JavaScript是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突;在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,而线程1要求浏览器删除DOM节点,线程2却希望修改这个节点的某些样式风格。这个时候浏览器就无法裁决采用哪一种策略了。当然我们可以为浏览器引入“排它锁”或者是“乐观锁”来解决这些冲突,但为了避免引入了更大的复杂性,所以JavaScript从诞生开始就选择了单线程执行。
因为单线程执行,所以对于JavaScript的任务而言,在同一时间内只能执行一个特定的任务,并且它会阻塞其他的任务执行。那么JavaScript的执行不会很慢吗?特别是对于长时间任务执行的时候,那么其他的任务就得不到执行。然而在软件开发中,特别是应用软件开发中,对于I/O设备的访问都是一些及其耗时的操作。在这些耗时任务执行的时候,其实并没必要等待它的完成,在I/O任务完成之前JavaScript完全可以继续执行其他的任务,直到I/O任务完成后再继续执行该任务的处理就行。JavaScript在设计之初,就意识这一点。所以在JavaScript中将这些耗时的I/O等操作封装为了异步的方法,等到这些任务完成后就将后续的处理操作封装为JavaScript任务放入执行任务队列中,等待JavaScript线程空闲的时候被执行。因此这里形成了另一个话题“浏览器的事件循环”机制,将在后续中详细阐述。
因为在JavaScript语言中,和其他大多数语言不一样之处:JavaScript中耗时的I/O操作都被处理为异步操作,以及回调注册机制。异步和回调仿佛和JavaScript就是“与生俱来”的一样。如Nodejs创始人Ryan Dahl所言,JavaScript语言的非阻塞的异步I/O事件驱动模型,以及JavaScript在Chrome推进下的多次性能优化、具有函数式等高级语言特性,因此最终Nodejs选择JavaScript。由于Nodejs最终选择了JavaScript,从此也大大的推动了JavaScript在非浏览器领域的急速扩展。
下面的文字是来自Nodejs官网:
当然对于非I/O的操作耗时操作如上篇博文《Promise的前世今生和妙用技巧》所说,在HTML5中也提高了新的解决方案,它就是Web Worker。Web Worker就是在当前JavaScript的执行主线程中利用Worker类新开辟一个额外的线程来加载和运行特定的JavaScript文件,这个新的线程和JavaScript的主线程之间并不会互相影响和阻塞执行的;并且在Web Worker中提供这个新线程和JavaScript主线程之间数据交换的接口:postMessage和onMessage事件。但在HTML5 Web Worker中是不能操作DOM的,任何需要操作DOM的任务都需要委托给JavaScript主线程来执行,所以虽然引入HTML5 WebWorker但仍然没有改线JavaScript单线程的本质。对于HTML5的Web Worker和在C# WinForm设计中的BackgroundWorker很类似,对于这类GUI(图形化界面)操作的应用程序中,对于UI界面的操作都需要委托给UI主线程来执行,避免多线程情况下UI操作的安全性和避免不必要的多线程访问控制的复杂度。
浏览器事件循环
在上面已经提到JavaScript中为了不阻塞UI的渲染,很多JavaScript任务都是异步的,它们包括键盘、鼠标I/O输入输出事件、窗口大小的resize事件、定时器(setTimeout、setInterval)事件、Ajax请求网络I/O回调等。当这些异步任务发生的时候,它们将会被放入浏览器的事件任务队列中去。在浏览器内部中存在一个消息循环池,也叫Event Loop(事件循环),JavaScript引擎在运行时后单线程的处理这些事件任务。例如用户在网页中点击了button事件,则它们会被放入在这个事件循环池中,需要等到JavaScript运行时执行线程空闲时候才会按照队列先进先出的原则被一一执行。对于setTimeout这类定时任务也是一样的,只有当定时时刻达到的时候,它们才会被放入浏览器的事件队列中等待被执行;由于此时的JavaScript主线程也许并不空闲,所以它将并不会被JavaScript引擎所立即执行,因为在JavaScript语言设计中setTimeout这类定时任务的执行时间并不是精确的。在前端开发中经常会发现setTimeout(func, 0)很有用,因为这并不是理解执行,而是将当前执行回调函数放入浏览器的事件队列中,等待当前其他任务的完成,然后在执行它;所以setTimeout(func, 0)具有改变当前代码执行顺序的作用,让浏览器有机会完成UI界面渲染等任务后在执行这段回调函数。当然对于老式浏览器这里具有16ms的差距,HTML5规定为4ms,以及关于动画操作中的requestAnimationFrame,请读者参见MDN资料https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame。
浏览器事件循环如下图所示:
虽然JavaScript是单线程执行的,但是浏览器并不是单线程执行的,它们有JavaScript的执行线程、UI节点的渲染线程,图片等资源的加载线程,以及Ajax请求线程等。在Chrome设计中,为了防止因一个Tab window的奔溃而影响整个浏览器,它的每一个Tab被设计为一个进程;在Chrome设计中存在很多的进程,并利用进程间通讯来完成它们之间的同步,因此这也是Chrome快速的法宝之一。对于Ajax的请求也需要特殊线程来执行,当需要发送一个Ajax请求的时候,浏览器会开辟一个新的线程来执行HTTP的请求,它并不会阻塞JavaScript线程的执行,HTTP请求状态变更事件会被作为回调放入到浏览器的事件队列中等待被执行。
总结
写到这里,本文也进入了尾声。希望这篇文章能给阅读本文的读者一些启发,同时如果本文中存在不足的地方,也希望你能不吝指教。另外,同时也欢迎关注博主的微信公众号[破狼](微信二维码位于博客右侧),这里将会为大家第一时间推送博主的最新博文,谢谢大家的支持和鼓励。
JavaScript单线程和浏览器事件循环简述的更多相关文章
- Javascript并发模型和事件循环
Javascript并发模型和事件循环 JavaScript的"并发模型"是基于事件循环的,这个并发模型有别于Java的多线程, javascript的并发是单线程的. Javas ...
- javascript的event loop事件循环
javascript的event loop事件循环 这是今天一个朋友发给我的一个面试题, 感觉还挺有意思的, 写个博客以供分享 先看看这个面试题目: 观察下面的代码,写出输出结果 console.lo ...
- [浏览器事件循环] javaScript事件循环 EventLoop
前言 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理. 先熟悉基本概念 [堆Heap] 堆是一种数据结构 ...
- js高级-浏览器事件循环机制Event Loop
JavaScript 是队列的形式一个个执行的 同一时间只能执行一段代码,单线程的 (队列的数据结构) 浏览器是多线程的 JavaScript执行线程负责执行js代码 UI线程负责UI展示的 Jav ...
- How Javascript works (Javascript工作原理) (四) 事件循环及异步编程的出现和 5 种更好的 async/await 编程方式
个人总结: 1.讲解了JS引擎,webAPI与event loop合作的机制. 2.setTimeout是把事件推送给Web API去处理,当时间到了之后才把setTimeout中的事件推入调用栈. ...
- 浏览器事件循环 & nodejs事件循环
第1篇:如何理解EventLoop——宏任务和微任务篇 宏任务(MacroTask)引入 在 JS 中,大部分的任务都是在主线程上执行,常见的任务有: 渲染事件 用户交互事件 js脚本执行 网络请求. ...
- JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧
原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...
- 浏览器事件循环机制(event loop)
JS是单线程的 JS是单线程的,或者说只有一个主线程,也就是它一次只能执行一段代码.JS中其实是没有线程概念的,所谓的单线程也只是相对于多线程而言.JS的设计初衷就没有考虑这些,针对JS这种不具备并行 ...
- JS浏览器事件循环机制
文章来自我的 github 博客,包括技术输出和学习笔记,欢迎star. 先来明白些概念性内容. 进程.线程 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的. ...
随机推荐
- ABP文档 - 异常处理
文档目录 本节内容: 简介 启用错误处理 非AJAX请求 显示异常 UserFriendlyException Error 模型 AJAX 请求 异常事件 简介 这个文档针对Asp.net Mvc和W ...
- 预览github里面的网页或dome
1.问题所在: 之前把项目提交到github都可以在路径前面加上http://htmlpreview.github.io/?来预览demo,最近发现这种方式预览的时候加载不出来css,js(原因不详) ...
- Android业务组件化之现状分析与探讨
前言: 从个人经历来说的话,从事APP开发这么多年来,所接触的APP的体积变得越来越大,业务的也变得越来越复杂,总来来说只有一句话:这是一个APP臃肿的时代!所以为了告别APP臃肿的时代,让我们进入一 ...
- 【NLP】十分钟快览自然语言处理学习总结
十分钟学习自然语言处理概述 作者:白宁超 2016年9月23日00:24:12 摘要:近来自然语言处理行业发展朝气蓬勃,市场应用广泛.笔者学习以来写了不少文章,文章深度层次不一,今天因为某种需要,将文 ...
- 如何定位Oracle数据库被锁阻塞会话的根源
首先再次明确下,数据库因为要同时保证数据的并发性和一致性,所以操作有锁等待是正常的. 只有那些长时间没有提交或回滚的事物,阻塞了其他业务正常操作,才是需要去定位处理的. 1.单实例环境 2.RAC环境 ...
- 免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)
在生活中有一种东西几乎已经快要成为我们的另一个电子”身份证“,那就是二维码.无论是在软件开发的过程中,还是在普通用户的日常中,几乎都离不开二维码.二维码 (dimensional barcode) , ...
- javascript动画系列第二篇——磁性吸附
× 目录 [1]范围限定 [2]拖拽范围 [3]磁性吸附 前面的话 上一篇,我们介绍了元素拖拽的实现.但在实际应用中,常常需要为拖拽的元素限定范围.而通过限定范围,再增加一些辅助的措施,就可以实现磁性 ...
- 【JavaScript】innerHTML、innerText和outerHTML的用法区别
用法: <div id="test"> <span style="color:red">test1</span> tes ...
- Android—Volley:接收服务端发送的json数据乱码问题解决
new JsonObjectRequest中重写方法parseNetworkResponse,内容如下: /** * 重写此方法不会导致乱码 */ @Override protected Respon ...
- Android 关于ijkplayer
基于ijkplayer封装支持简单界面UI定制的视频播放器 可以解析ts格式的so库 怎样编译出可以解析ts等格式的so库?就是编译的时候需要在哪一步修改配置? 一些电视台的m3u8 CCTV1综合, ...