从头认识JavaScript的事件循环模型
众所周知JavaScript是一门单线程的语言,所以在JavaScript的世界中默认的情况下同一个时间节点只能做一件事情,这就造成了JavaScript这门语言的一些局限性,比如在我们的页面加载一些远程数据时,如果按照单线程同步的方式运行,一旦有HTTP请求向服务器发送,就会出现等待数据返回之前页面假死的现象。因为JavaScript在同一时间只能做一件事,这就导致了页面渲染和事件的执行无法同进进行。
关于同步和异步
关于上述的情况,我们知道在JavaScript的世界中应该存在一种解决方案,来处理单线程造成的诟病。这就是同步【阻塞】和异步【非阻塞】执行模式的出现。
同步(阻塞)
同步的意思是JavaScript会严格按照单线程(从上到下,从左到右的方式)执行代码逻辑,进行代码的解释和运行,所以在运行代码时,不会出现先运行4、5行的代码,再回头支; 行1、3行的代码的这种情况。比如:
var a = 1 var b = 2 var c = a + b console.log(c) // c的返回一定是先执行了上面几行代码之后才会执行
升级场景
var a = 1 var b = 2 var d1 = new Date().getTime() var d2 = new Date().getTime() while(d2-d1 < 2000){ d2 = new Date().getTime() } // 这段代码在输出结果之前网页出进入一个类似假死的状态 console.log(a+b)
异步(非阻塞)
如上例子,我们明白了单线程同步模型中的问题,接下来引入异步模型介绍。
var a = 1 var b = 2 setTimeout(function(){ console.log('打印一段内容') }, 2000) // 这段代码会先输出3,过2秒左右再输出function的内部打印 console.log(a+b)
这段代码的setTimeout定时任务规定了2秒之后执行一些内容,在运行当前程序执行到setTimeout时,并不会直接执行内部的回调函数,而是会先将内部的函数在另外一个位置保存起来,然扣继续执行下面的console.log进行输出,输出之后代码执行完成,然后等待2秒后执行之前保存起来了函数。
总结
JavaScript的运行顺序就完全单线程的异步模型:同步在前,异步在后。所有的异步任务都要等待当前的同步任务执行完毕之后才能执行。请看下面案例:
var a = 1 var b = 2 var d1 = new Date().getTime() var d2 = new Date().getTime() setTimeout(function(){ console.log('我是异步任务') }, 1000) while(d2-d1 < 2000){ d2 = new Date().getTime() } // 这段代码在输出3之前会进入假死状态,'我是异步任务'一定会在3之后输出 console.log(a+b)
JS的线程组成
上面几个例子我们大概了解了Js的运行顺序,那么为是这个顺序呢?这个顺序的原理是什么样的?我们应该如保更好更深的探究真相?带着这些问题,我们一起来了解一下浏览器的一个页面的实际线程组成。
在了解线程组成前要了解一点,虽然浏览器是单线程执行JavaScript代dcbg的,但是浏览器实际是以多个线程协助操作来实现单线程异步模型的,具体线程组成如下:
-
GUI渲染线程
-
JavaScript引擎线程
-
事件触发线程
-
定时器触发线程
-
Http请求线程
-
其他线程
按照真实浏览器线程组成分析,我们会发现实际上运行JavaScript的线程其实并不是一个,但是为什么说JavaScript是一门单线程的语言呢?因为这些线程实际参与代码执行的线程并不是所有的线程,如比GUI渲染线程为什么单独存在,这个是防止我们在html网页渲染一半的时间突然执行了一段阻塞式的JS代码而导致网页卡在一半停住的这种效果。 在JavaScritp代码运行的过程中实际执行程序时同时只存在一个活动线程,这里实现同步异步就是靠多个线程切换的形式来进行实现的 。
所以,我们通常分析时,将上面的线分线程归纳为下面两条线程:
-
【主线程】:这个线程用来执行页面的渲染,JS代码运行,事件触发等
-
【工作线程】:这个线程是幕后工作的,用来处理异步任务的执行
2. JavaScript的运行模型
从代码片段开发分析
function task1(){ console.log('第一个任务') } function task1(){ console.log('第二个任务') } function task1(){ console.log('第三个任务') } function task1(){ console.log('第四个任务') } task1() setTimeout(task2, 1000) setTimeout(task3, 500) task4()
我们创建了四个函数代表4个任务,函数本身都是同步代码。在执行的时候会按照1,2,3,4进行解析,解析过程中我们发现任务2和3被setTimout进行了定时托管,这样就只能先运行任务1和4了。当任务1和4运行完毕后500毫秒运行3,1000毫秒运行2
当同步任务都执行完成时,执行异步任务
总结
通过图解我们就更清晰的能搞懂异步任务的执行方式了。
关于执行栈
执行栈是一个栈的数据结构,当我们运行单层函数时,执行栈执行的函数进栈后,会出栈销毁然后下一个进栈出栈。如下例子
function task1(){ console.log('tak1执行') task2() console.log('task2执行完毕') } function task2(){ console.log('task2执行') task3() console.log('task3执行完毕') } function task2(){ console.log('task3执行') } console.log('task1执行完毕') // 打印 /* task1执行 task2执行 task3执行 task3执行完毕 task2执行完毕 task1执行完毕 */
- 如何处理后端一次性返回的十万条数据
- Chrome performance面板与API介绍
- 关于JS精度问题
- 小程序开发-数据预拉取和数据上报
- H5&小程序开发-React组件流行代码规范
- js代码质量-健壮性
- js执行顺序2022-04-26
- 前端模块化知识梳理
- 前端程序员进阶必备:抓包代理一 charles抓包
- ReactNative初尝入坑
- 你不知道的弹性盒子
- node-schedule 简介
- No Title
- 从头认识JavaScript的事件循环模型
- react-router原理解析 (一)
- 你不知道的TypeScript高级技巧
- 带你了解Webpack
- nginx限流配置
- nodejs初探2022-02-09
- CSS中的百分比