JavaScript单线程与异步——事件循环event loop

本文介绍了JavaScript的单线程特性及其为何采用单线程设计,详细阐述了浏览器中的多线程,包括GUI渲染线程、JS引擎线程等。还探讨了宏任务和微任务的概念,解释了事件循环(Event Loop)的工作原理,以及如何通过回调函数、Promise等方式处理异步。最后,通过实例分析了setTimeout和setImmediate的区别以及事件循环在不同环境(浏览器和Node.js)的应用。
摘要由CSDN通过智能技术生成

最近跟同学谈论时聊到多线程,突然想到Javascript是一门单线程的语言,那么它是怎么实现异步的一些操作(如ajax请求,setTimeout等),先看一下下面的这些概念

一、js单线程与非阻塞

  1. 单线程:是指一个浏览器进程中只有一个JS的执行线程,这个就是js的主线程,也就是js代码的执行栈,同一时刻内只会有一段代码在执行,这就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务,如果前一个任务耗时很长,后一个任务就不得不一直等着,可以使用IE的标签式浏览试试看效果,这时打开的多个页面使用的都是同一个JS执行线程,如果其中一个页面在执行一个运算量较大的function时,其他窗口的JS就会停止工作

  2. 非阻塞:当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调

二、为什么js是单线程的

javascript从诞生之日起就是一门单线程的非阻塞的脚本语言

js在设计之初只是为了解决一些简单的需求,主要用来与浏览器进行交互,比如进行各种各样的dom操作,试想,如果有两个线程同时去操作同一个DOM元素,一个线程点击了一个元素,而另一个删除了这个元素,那么浏览器到应该以哪一个为准呢?所以js的单线程是必要的,这也是javascript的基石
单线程的缺点

  1. 因为只有一个线程,代码需要排队按顺序执行,前一个任务执行完毕,后一个任务才会被执行
  2. 排队等待就需要大量的计算。耗费CPU。限制了js的执行效率。
  3. 如果有一段代码报错,就可能会导致代码阻塞,页面假死

三、浏览器多线程

  1. GUI 渲染线程
  • 绘制页面,解析 HTML、CSS,构建 DOM 树,布局和绘制等
  • 页面重绘和回流
  • 与 JS 引擎线程互斥,也就是所谓的 JS 执行阻塞页面更新
  1. JS 引擎线程
  • 负责 JS 脚本代码的执行
  • 负责准执行准备好待执行的事件,即定时器计数结束,或异步请求成功并正确返回的事件
  • 与 GUI 渲染线程互斥,执行时间过长将阻塞页面的渲染
  1. 事件触发线程
  • 负责将准备好的事件交给 JS 引擎线程执行
  • 多个事件加入任务队列的时候需要排队等待(JS 的单线程)
  1. 定时器触发线程
  • 负责执行异步的定时器类的事件,如 setTimeout、setInterval
  • 定时器到时间之后把注册的回调加到任务队列的队尾
  1. HTTP 请求线程
  • 负责执行异步请求
  • 主线程执行代码遇到异步请求的时候会把函数交给该线程处理,当监听到状态变更事件,如果有回调函数,该线程会把回调函数加入到任务队列的队尾等待执行

例如:异步请求是由两个常驻线程,JS执行线程和事件触发线程共同完成的,JS的执行线程发起异步请求,这时浏览器会开一条新的HTTP请求线程来执行请求,这时JS的任务已完成,继续执行线程队列中剩下的其他任务,然后在未来的某一时刻事件触发线程监视到之前的发起的HTTP请求已完成,它就会把完成事件插入到JS执行队列的尾部等待JS处理。
又例如定时触发(settimeout和setinterval)是由浏览器的定时器线程执行的定时计数,然后在定时时间把定时处理函数的执行请求插入到JS执行队列的尾端,所以用这两个函数的时候,实际的执行时间是大于或等于指定时间的,不保证能准确定时的

四、宏任务(macro task)和微任务(micro task)

在任务对列中注册的异步回调又分为两种类型,宏任务和微任务,为了方便理解可以认为在任务队列中有宏任务队列和微任务队列,宏任务队列有多个,微任务只有一个
以下事件属于宏任务

  1. setTimeout()
  2. setInterval()
  3. setImmediate() (Node环境下)
  4. requestAnimationFrame
  5. UI渲染

    微任务
  6. Promise
  7. pocess.nextTick(Node 环境下)

在一个事件循环中,异步事件返回结果后会被放到一个任务队列中,然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去,并且在当前执行栈为空的时候,主线程会先扫描一遍微任务队列是否还有事件存在,看还有没有微任务没有被执行,如果没有,则去宏任务队列中取出一个事件并把对应的回调到加入当前执行栈,如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环,也就是说当所有微任务被执行完毕后,才会开始执行宏任务
因此我们只需记住,当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件,同一次事件循环中,微任务永远在宏任务之前执行
以下代码的输出顺序是什么?

setTimeout(() => {
   
    console.log(4);
})
new Promise(resolve => 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值