JavaScript的同步异步

我们首先要知道JavaScript是一门单线程的语言,顾名思义"单线程”,就是指一次只能执行一个任务,如果有多个任务,那就必须排队执行,在上一个任务执行完毕之后,再去执行后面的任务,以此类推。如果一个任务耗时过长,那么后面的任务就必须等待这个耗时过长的任务完成,才能继续往下执行,那么这种情况会造成什么后果呢?拖延我们的程序执行,常见的浏览器无反应。于是,JavaScript将所有任务分为两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

一、JS运行机制

Js是单线程语言,它是基于事件循环,事件循环大致分为以下几个步骤:

1.所有同步任务都在主线程上执⾏, 形成⼀个执⾏栈(execution context stack)。

2.主线程之外, 还存在⼀个"任务队列"(task queue) 。只要异步任务有了运⾏结果, 就在"任务队列"之中放置⼀个事件。

3.一旦"执⾏栈"中的所有同步任务执⾏完毕, 系统就会读取"任务队列", 看看⾥⾯有哪些事件。那些对应的异步任务, 于是结束等待状态, 进⼊执⾏栈, 开始执⾏。

4.不断重复第三步,直至"异步任务队列"全部清除。

同步任务

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

异步任务

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。

异步模式的四种方式:

1.回调函数callback

2.事件驱动 Event-Driven

3.观察者模式Observer pattern(又称发布订阅模式publish-subscribe pattern)

4.promise模式

5.宏任务(计时器, ajax, 读取文件)

6.setImmediate

二、浏览器的事件循环

JavaScript是如何执行的

JavaScript 并不是一行一行的分析并执行代码的,而是分段一段一段的进行分析并执行。

这里要说明一下,怎么才算一段。这里的段指的是 JavaScript 中的执行上下文。

执行上下文

全局执行上下文:就是指全局代码

函数执行上下文:这里指的是一个一个函数,一个函数执行前会先创建一个执行上下文

eval执行上下文:eval不推荐使用

由此可见当JavaScript运行时会有很多执行上下文,为了方便管理,js引擎会创建一个执行上下文栈来对这些执行上下文进行管理,遵循先进后出原则。

全局上下文在栈的最底部,全局上下文只有一个在浏览器关闭时出栈。

函数在执行时会创建函数执行上下文,并放入执行栈中执行,执行完毕后出栈。

Event loop 循环机制

当 JavaScript 执行时,会将全局执行上下文放入执行栈中,接下来遇到函数执行上下文时会将这个上下文也放入执行栈中,执行完毕会出栈,当执行栈为空时,会从任务队列头部拿取一个任务,创建上下文并放入执行栈中执行。每当执行栈为空时总会循环的从任务队列获取任务,并创建执行上下文放入执行栈执行。这个循环我们称之为事件循环。

三、Node的事件循环

当 Node.js 启动时,它会初始化事件循环,处理提供的脚本,同步代码入栈直接执行,异步任务(网络请求、文件操作、定时器等)在调用 API 传递回调函数后会把操作转移到后台由系统内核处理。目前大多数内核都是多线程的,当其中一个操作完成时,内核通知 Node.js 将回调函数添加到轮询队列中等待时机执行。

Node.js事件循环的六个阶段

1.timers(定时器阶段)

首先事件循环进入定时器阶段,用于存储定时器的回调函数(setTimeout setInterval)。

2.pending callbacks

执行与操作形同相关的回调函数。比如启动服务器应用时监听端口操作时的回调函数。

3.idle,prepare

idle, prepare 阶段是给系统内部使用,idle 这个名字很迷惑,尽管叫空闲,但是在每次的事件循环中都会被调用,当它们处于活动状态时。

4.poll

存储I/O操作相关的回调函数,比如文件读写操作的回调函数。如果时间队列中有回调函数,执行他们直到清空操作。否则事件循环在此阶段会停留一段时间以等待新的回调含税进入。这个等待取决于两个条件:

(1).setImmediate队列(check阶段)存在要执行的回调函数。

(2).timers队列中存在要执行的回调函数,在这种情况下,事件循环将移至check阶段,然后移至Closing callbacks阶段,并最终从timers阶段进入下一次循环。

5.check

存储setImmediate的回调函数。

6.Closing callback

执行与关闭事件相关的回调函数。比如:关闭数据库连接的回调函数。

四、宏任务与微任务

任务队列的分类

任务队列分为两种,一种叫宏任务(macrotask),一种叫微任务(microtask )。

宏任务(macrotask)

script( 整体代码)、setTimeout、setInterval、I/O(http请求)、UI 渲染。

微任务(microtask)

Promise.then()、MutationObserver(监听dom的更改)。

两者的区别

执行原则

执行完一个宏任务回去检测微任务队列是否为空,如果不为空则执行完队列内的所有微任务,如果队列为空,则继续执行下一个宏任务,下一个宏任务执行完后会继续检测微任务队列是否为空,往复循环。


 

五、JS任务队列

说起任务队列的话,首先我们要回顾一下JS语言的特点。

我们知道,Javascript 这门脚本语言诞生的使命就是为处理页面中用户的交互,以及操作 DOM 而诞生的。

所以JS的设计就是单线程的,总不能多线程来操作DOM结构吧(那不就乱套了吗)。

那么什么是单线程,其实就是任务一个接着一个做,不能同时处理多个任务。

那这样就会导致一个问题,如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

那JS是如何解决这个问题呢?我用一张图来说一下:

接下来我们来看一段代码

console.log(1)
setTimeout(function () {
    console.log(2);
    process.nextTick(function () {
        console.log(3);
    })
})
Promise.resolve().then(function() {
    console.log(4)
}).then(function() {
    console.log(5)
})
​
 

我们来看一下这个代码的执行过程。

反正肯定不是输出12345。

从上到下:

第一遍

console.log(1)会放到主线程中,

setTimeout会放到宏任务队列

Promise会放到微任务队列

主线程先执行,然后微任务:

打印1,4,5

再执行宏任务:

console.log(2)放到主线程

process.nextTick放到微任务队列

主线程先执行,然后微任务

打印2,3

所以打印结果为:14523

OK,那么关于Js的任务队列也就解释清楚了

总结

同步(Sync)发出一个功能调用时,必须一件一件事做,等前一件做完了才能做下一件事。

异步(Async)当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。

总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值