JavaScript 高级特性之运行机制

86 阅读3分钟

JavaScript是单线程的

  • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题
  • H5提出Web Worker标准, 允许JavaScript脚本创建多个线程, 但是子线程完全受主线程控制, 不得操作DOM

任务队列

基于JavaScript是单线程的核心特征, 为了优化任务处理流程, 设计者将所有任务分为两种: 同步任务(synchronous)和异步任务(asynchronous)

同步任务

  • 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

异步任务

-   不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行  
-   宏任务(macrotask)  
    -   script  
    -   setTimeout  
    -   setInterval  
    -   setImmediate  
    -   I/O  
    -   UI rendering  

-   微任务(microtask)  
    -   process.nextTick  
    -   promise  
    -   MutationObserver  
  • 异步执行的运行机制:
  • 主流程和任务队列的示意图:
    Event Queue(任务队列)是一个事件的队列

事件和回调函数

事件

IO设备完成一项任务, 就在Event Queue中添加一个事件, 表示相关的异步任务可以进行“执行栈”.主线程读取Event Queue就是读取里面有哪些事件

回调函数(Callback)

被主线程挂起的代码. 异步任务必须制定回调函数, 当主线程开始执行异步任务, 就是执行对应的回调函数

任务队列(Event Queue)

一个先进先出的数据结构

  • 主线程的读取基本都是自动的, 执行栈已清空, 任务队列上的第一位事件就自动进入主线程, 但主线程会检查执行时间(定时器)

Event Loop

主线程从任务队列中读取事件, 这个过程是循环不断的, 所以整个运行过程又称为Event Loop(事件循环)

  • Event Loop示意图
    主线程运行时, 产生堆(Heap)和栈(Stack), 栈中的代码调用外部API, 在任务列表中加入各种事件. 只要栈中的代码代码执行完成, 主线程就会去读取任务队列, 依次执行那些事件所对应的回调函数

定时器

setTimeout()

-   一次性执行  
-   指定某个任务在主线程最早可得的空闲时间执行. 即在现有任务队列的尾部添加事件  
-   H5标准规定setTimeout()第二个参数的最小值(最短间隔)不得低于4毫秒, 如果低于会自动增加  
-   setTimeout()的执行时间并不一定在指定的时间执行, 执行时间取决于执行栈执行完的时间  

setInterval()

  • 重复执行

Node.Js的Event Loop

  • Node.Js的运行机制不同于浏览器环境
  • 运行示意图:
  • 运行机制:

特性:

process.nextTick

  • 在触发下一次事件轮询(读取Event Queue)前触发回调函数
  • 同一轮Loop下, nextTick总在setTimeout之前触发回调
  • 无论在一个函数内嵌套多少个nextTick, 都会在当前执行栈中完成

setImmediate

  • 在当前任务队列的尾部添加事件, 类似于setTimeout
  • 与setTimeout同时使用时, 无法确定哪个先被触发回调

JavaScript执行机制

  • 主线程任务 -> 微任务 -> 宏任务
  • 同级下, 微任务优先于宏任务执行