1. 案例引出

  • 程序案例 ```javascript

/ 下面代码输出什么结果 /

// 执行时间是参数t(毫秒) const useTime = t => { let start = Date.now() while(Date.now() - start < t) {} }

let timer1 = setTimeout(() => { console.log(3) }, 500)

let timer2 = setTimeout(() => { console.log(4) }, 1000)

// 预计输出结果 1 3 4 2 // 实际输出结果 1 2 3 4 为什么会这样??

console.log(1) useTime(2000) console.log(2) ```

  • 实际生活案例
    • 方式1
      • 电饭煲操作1分钟,定时15分钟做早餐,守在旁边等待
      • 刷牙5分钟
      • 洗脸5分钟
      • 吃饭5分钟
    • 方式2
      • 给电饭煲定时(1分钟)
      • 刷牙5分钟
      • 洗脸5分钟
      • 再等待5分钟后听到电饭煲到时提示后,吃饭5分钟

2. 几个重要概念

image.png

  • 单线程模型
    • 单线程模型指的是JavaScript只在一个线程上运行
    • 也就是说,JavaScript同时只能执行一个任务,其他任务都必须在后面排队等待
    • JavaScript只在一个线程上运行,不代表JavaScript引擎只有一个线程
    • JavaScript引擎有多个线程,单个脚本只能在一个线程上运行,其他线程都是在后台配合
  • 执行栈
    • 当打开网页或浏览器时,宿主环境会将代码传递给引擎去执行,引擎首先会创建一个全局执行环境
    • 全局环境中的代码自上而下有顺序的执行,当遇到一个函数时,函数的环境被创建函数中的代码开始执行
    • 在函数执行之后,控制权又返还给之前的环境,这种类似于栈的控制机制,称为执行栈
      • 宿主环境:浏览器或者Node环境
      • 引擎:从头到尾负责整个JavaScript代码的编译及执行过程
      • 栈:一种遵循”后进先出”原则的有序数据集合,可以简单理解为使用 push() 和 pop() 操作数组
  • 任务队列
    • JavaScript是单线程的语言,在执行时必须等前面的任务处理完以后才会处理后面的而且是一个一个连续同步执行的,不像多线程可以同时处理多个任务
    • JavaScript中的任务分为同步任务和异步任务,同步任务就是主线程上一个个排队执行的任务,异步任务则不进入主线任务而是被加入到“任务队列”中,任务队列的任务只有在主线任务执行完成之后才去处理


3. 解析案例

image.png
执行流程

  • 进入全局执行上下文
    • 创建由第三方计时模块管理的timer1,timer1会在500ms后把任务fn1放入任务队列
    • 创建由第三方计时模块管理的timer2,time2会在1000ms后把任务fn2放入任务队列
    • 输出1
  • 进入useTime执行上下文
    • 执行代码耗时1000ms,退出useTime执行上下文
    • 在500ms和1000ms时timer1和timer2各自完成投放,此操作不属于JS主线程
  • 返回全局执行上下文
    • 输出2
    • 同步任务执行完毕 开始扫描任务队列 取出队列的fn1
  • 进入fn1执行上下文
    • 输出3
    • 退出fn1执行上下文 取出队列的fn2
  • 进入fn2执行上下文
    • 输出4
    • 退出fn2执行上下文 循环扫描任务队列