JavaScript 异步编程-----概念

众所周知,目前主流的JavaScript环境都是以单线程模式执行的js代码 ;JavaScript采用单线程模式工作的原因与设计初衷有关,  最早JavaScript这门语言就是运行在浏览器端的脚本语言,它的目的实现页面的动态交互,实现页面交互的核心就是dom操作,就决定了必须使用单线程模型,否则出现复杂的线程同步问题。

可以试想一下,如果js中有多个线程一起工作,其中一个线程修改了某元素,另外一个线程又删除了这个元素,此时浏览器就不知道该以哪个线程的工作结构为准。所以为了避免这种线程同步的问题。JavaScript最初就采用单线程模式工作。 这也就成为了这门语言最为核心的特性之一。

这里指的单线程就是说在JS的执行环境中负责执行代码的线程只有一个,任务队列依次执行;

它最大的优点就是更安全更简单;

缺点耗就是可能出现特别耗时的任务,后面的任务都必须排队等候。整个程序的执行会被拖延,任务阻塞出现假死的情况,

为了解决js将任务的执行模式分为两种同步模式(Synchronous)异步模式(Asynchronous)

 同步执行模式

代码中的任务依次执行,后一个任务必须等待前一个任务执行完毕才能开始执行。程序执行顺序跟我们代码编写顺序完全一致的。 在单线程模式下,大多数任务都会同步执行(排队执行)

异步执行模式 

异步模式的Api 不会等待任务结束才会执行下个任务,耗时任务开始后就立即执行下一个任务,耗时任务恶的后续逻辑一般会以回调函数的方式去定义,在内部耗时任务完成后会自动执行回调函数。

对于开发者而言单线程模式下的异步它最大的难点就是代码执行顺序混乱,比较跳跃。 

示例如下:

console.log('全局开始')

setTimeout(function timer_1() {
	console.log('timer_1 exec')
}, 1800)

setTimeout(function timer_2() {
	console.log('timer_2 exec')
  setTimeout(function inner() {
    console.log('inner exec')
  }, 1000)
}, 1000)

console.log('全局结束')

 需要注意的是,异步模式比同步模式多出了三个概念:事件循环、消息队列、平台API环境(Web 就是 Web Apis)。

webAPi:当遇到计时器、DOM事件监听或是网络请求时,JS引擎会将这些异步任务交给Web Api,也就是浏览器提供的相应线程。而JS引擎则继续后边的其他任务,以此方式实现异步非阻塞。

事件循环(Event loop):监听调用栈和消息队列,一旦调用者所有任务都结束后,事件循环就会从消息队列中取出第一个回调函数入栈。

消息队列:可以理解为待办的工作表。 (JS引擎先执行完调用栈中的所有任务,事件循环就会从消息队列中取 出一个任务出来继续执行,以此类推,整个过程异步线程都可以往消息队列中放入新任务(异步任务的回调)排队等待事件循环)。

所以说异步调用的核心是通过内部的消息队列和事件循环去实现的。

执行简述:

  1. JavaScript 执行引擎首先会在调用栈里压入一个匿名函数,同时把所有代码放入匿名函数中并执行,然后逐行执行每一行的代码。
  2. 第 1 行压入调用栈执行,执行完毕后弹出。
  3. 第 3 行 setTimeout 压入调用栈,这里因为函数是异步调用,Web Apis 会在内部为 timer 函数开启一个 1.8s 的倒计时器然后放到一边儿玩时间沙漏去了,而这个沙漏是单独工作不受 JavaScript 单线程影响的,会立即开始倒数。同时需要注意的是,对于 setTimeout 来说,它已经调用完毕了,于是 setTimeout 会弹出调用栈。
  4. 第 7 行 setTimeout 压入调用栈,timer_2 开启 1s 倒计时器也玩沙漏去了,然后 setTimeout 弹出调用栈。
  5. 第 14 行压入调用栈执行,执行完毕后出栈。
  6. 此时所有代码执行完毕,清空调用栈。
  7. Event Loop 会始终监听调用栈和消息队列,一旦调用栈里所有的任务都结束了,那么**事件循环就会从消息队列当中取出第一个回调函数。**但此时消息队列是空的不存在未执行的函数,因为两个 timer 还都在玩沙漏。
  8. 在时间过去 1s 后,timer_2 率先结束玩耍时间,进入了消息队列的第一个位置。
  9. 在时间又过去 0.8s 后,timer_1 紧随其后也进入了消息队列,此时它会被放置在第二个位置。
  10. **一旦消息队列当中发生了变化,事件循环就会监听到。**然后它会把消息队列当中的第一个也就是 timer_2 函数压入调用栈。
  11. 第 8 行压入调用栈执行,执行完毕后出栈。(此时调用栈里 timer_2 还在)
  12. 第 9 行 setTimeout 压入调用栈,inner 玩沙漏去后,setTimeout 出栈。
  13. timer_2 出栈,清空调用栈。
  14. 事件循环监听到调用栈无任务执行,把消息队列中第一位 timer_1 取出压入调用栈。
  15. 第 4 行压入调用栈执行,执行完毕后出栈。
  16. 时间过去 1s 后,inner 进入消息队列。
  17. 事件循环从消息队列取出 inner 压入调用栈。
  18. 第 10 行压入调用栈执行,执行完毕后出栈。
  19. inner 出栈,清空调用栈。

总结:

JacaScript是单线程的,而浏览器不是单线程的。

JacaScript调用的某些内部的API并不是单线程的例如setTimeout内部会有一个单独线程负责倒数,时间到了后会将回调任务放入消息队列。这个计时操作是由另外一个线程去做的。

我们所说的单线程是执行代码的那个线程是一个线程。就是说这些内部的API它们会用另外的一个线程去执行等待的操作。

还需要注意的是,同步模式和异步模式指的并不是我们代码的书写方式,而是我们代码里使用的 Api 是以同步模式还是异步模式的方式工作的。同步模式的API例如console.log() , 异步模式的API就是下达这个任务开启后的指令后继续往下执行,代码不会在这行等待。例如setTimeout();

 

try 只能捕获同步的代码,因为它执行在主线程中,它里面的任务执行完毕后, 就会被调用栈清空掉。等异步代码报错后 ,try已经不存在了,所以就捕获不到, 直接被抛出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值