【Web APIs】JavaScript 执行机制 ( 单线程特点 | 同步任务与异步任务 | 同步先行、异步排队 | 事件循环机制 )

文章目录

一、JavaScript 执行机制


1、JavaScript 单线程特点

浏览器 的 JavaScript 引擎 是单线程的 , 同一时间 只能 执行一个代码任务 ;

将 JavaScript 设计成单线程 的 核心原因 是 避免 DOM 操作冲突 :

  • JavaScript 的主要用途是 和 用户交互、操作 DOM ( 比如修改页面元素、添加事件 ) ;
  • 如果是多线程 , 可能出现 " 同时修改同一个 DOM " 的情况 ( 比如线程 1 想删除按钮 , 线程 2 想点击按钮 ) , 会 导致页面混乱 ;
  • 单线程 通过 " 排队执行 " 的方式 , 天然避免了这种冲突 , 让 DOM 操作更安全 ;

2、JavaScript 同步任务与异步任务

JavaScript 同步任务 与 异步任务 :

  • 同步任务 : 所有的任务 必须 按顺序执行 , 前一个任务完成后 , 下一个才开始 ; 执行过程中会 " 阻塞 " 后续任务 ( 比如前面的人办复杂业务 , 后面的人只能等 ) ;
  • 异步任务 : 任务执行时 不会阻塞后续任务 , 先 登记任务信息 , 等 主线程空闲时再回来执行 ( 比如取号后不用排队 , 先让别人办事 , 叫到号再去窗口 ) ;

同步任务 与 异步任务 对比 :

特性 同步任务 异步任务
执行顺序 按代码书写顺序 , 依次执行 先登记 , 主线程空闲后才执行
是否阻塞 会阻塞后续任务 不会阻塞后续任务
执行优先级 高于异步任务 ( 先执行所有同步任务 ) 低于同步任务 ( 等同步任务全完成 )
常见类型 1. 变量声明 / 赋值 2. 函数普通调用 3. 循环 / 条件判断 4. 直接操作 DOM ( 如document.write ) 1. 定时器 ( setTimeout/setInterval ) 2. 网络请求 ( fetch/axios ) 3. DOM 事件 ( click/load ) 4. Promise 的then/catch 5. async/await ( 语法糖 , 本质还是异步 )

3、同步先行、异步排队

JavaScript 核心执行机制 : 同步先行 , 异步排队 , 同步和异步任务的执行流程可以拆解为 3 步 :

  • 主线程优先执行所有同步任务 : 代码执行时 , 同步任务 会直接压入 " 调用栈 " 执行 , 执行完一个弹出一个 , 直到调用栈为空 ;
  • 异步任务先 " 登记 " 到任务队列 : 遇到异步任务时 , JS 引擎 不会等待它完成 , 而是把它的 " 回调函数 " ( 任务完成后要执行的代码 ) 放入 " 任务队列 " 排队 ;
    • 这里特别注意 : 只有马上将要执行的异步任务才会放入 任务队列 中 , 如果还没有到执行时间 , 如 : 用户未点击按钮 / 定时器倒计时未完成 , 则不会将 异步任务 放入 任务队列中 ;
  • 事件循环触发异步执行 : 当 调用栈 为空 ( 所有同步任务执行完 ) , 事件循环 会不断 检查任务队列 , 把队列中第一个任务压入调用栈执行 , 直到任务队列清空 ;

4、事件循环机制

事件循环 ( Event Loop ) 机制 :

  • 执行栈 ( 调用栈 ) : 同一时间只能处理 1 个订单 ( 单线程特性 ) , 遵循 " 先进后出 " ( LIFO ) 原则 ------ 执行完一个任务 , 再取下一个任务 ;
  • 任务队列 : 相当于餐厅的 " 订单排队系统 " , 存放已经 " 准备好要处理 " 的异步任务回调 ( 比如用户点的菜做好了、外卖订单确认了 ) , 遵循 " 先进先出 " ( FIFO ) 原则 ; 注意 : 任务队列分两类 , 优先级不同 ( 关键! ) :
    • 微任务队列 ( 高优先级 ) : 存放 Promise.then/catch/finally、process.nextTick ( Node.js ) 、queueMicrotask 等 ;
    • 宏任务队列 ( 低优先级 ) : 存放 setTimeout/setInterval、DOM 事件回调 ( click/load ) 、网络请求回调、script 标签整体代码等 ;
  • 浏览器异步进程处理模块 : 相当于餐厅的 " 调度员 " , 永远在循环做一件事 ------ 检查 " 执行栈是否为空 " :
    • 若 执行栈空 -> 先清空所有 微任务队列 ( 按顺序执行 ) ;
    • 微任务全执行完 -> 再从宏任务队列取第一个任务压入执行栈 ;
    • 重复上述步骤 , 无限循环 ;

二、JavaScript 事件循环 案例解析


1、案例解析

给出下面一段代码 , 分析该代码的执行过程 :

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>JavaScript 事件循环</title>
    <script>
        // 1. 立即执行 : 向控制台输出数字 1
        console.log(1);

        // 2. 为整个文档 ( 页面 ) 绑定点击事件
        // 当用户点击页面任意位置时 , 触发后面的匿名函数
        document.onclick = function() {
            // 点击事件触发后 , 向控制台输出字符串 'click'
            console.log('click');
        }

        // 3. 设置一个延时定时器
        // 第一个参数 : 3秒后要执行的匿名函数
        // 第二个参数 : 延时时间 , 单位是毫秒 ( 3000毫秒 = 3秒 ) 
        setTimeout(function() {
            // 3秒后自动执行 , 向控制台输出数字 3
            console.log(3)
        }, 3000)

        // 4. 立即执行 : 向控制台输出数字 2
        console.log(2);
    </script>
</head>

<body>
</body>

</html>

2、执行结果

进入界面 3 秒内不操作 , 3 秒后点击页面 :

进入界面后 , 马上打印出 1 和 2 , 在 3 秒定时器 到期后 , 打印出 3 , 3 秒之后 鼠标点击文档页面 , 此时会打印出 click ;

进入界面 3 秒内点击界面 :

进入界面后 , 马上打印出 1 和 2 , 在 3 秒定时器 没有到期前 , 鼠标点击文档页面 , 此时会打印出 click , 3 秒定时器到期后 , 会打印出 3 ;

3、执行结果分析

这段代码的实际执行顺序是 :

  • 先输出 1 ( 立即执行 )
  • 再输出 2 ( 立即执行 )
  • 点击页面时输出 click ( 事件触发时执行 , 时机由用户操作决定 )
  • 等待 3 秒后输出 3 ( 延时执行 )

因为 setTimeout 是异步操作 , 会被 浏览器 放入 "任务队列" , 等待主线程的同步代码 console.log(1) 和 console.log(2) 执行完毕后 , 才会在指定延时后执行 ;

特别注意 :

  • 点击页面时输出 click , 只有 当 用户点击 页面后 , 浏览器 中的 异步进程处理模块 才会 将 该点击回调函数 放入到 任务队列 中 , 如果用户不点击页面 , 浏览器 不会将该 点击事件回调函数 放入 异步任务队列中 ;
  • 等待 3 秒定时器 , 只有在 打开界面 3 秒以后 , 定时器到时之后 , 浏览器 中的 异步进程处理模块 才会 将 该定时器任务函数 放入到 异步任务队列 中 , 定时器 时间不到 , 任务队列不会有该 定时器任务 ;
相关推荐
linhuai1 小时前
Flutter如何实现头部固定
前端
单调7771 小时前
npm你还了解多少
前端
码途进化论1 小时前
基于 Vue 2 + VXE Table 的超大规模表格渲染架构设计与性能优化方案
前端
漫天星梦1 小时前
iOS 手机无法播放视频问题排查与解决方案记录
前端·ios
好好好明天会更好1 小时前
uniapp项目中视频播放控制对象
前端·vue.js
洲星河ZXH1 小时前
Java,比较器
java·开发语言·算法
萌狼蓝天1 小时前
[Vue2]项目中 vue-draggable-resizable 列宽拖动问题修复(首次拖动列宽突然变得很小)
前端·javascript·vue.js·前端框架·ecmascript
带带弟弟学爬虫__1 小时前
ks安卓—did注册
前端·javascript·vue.js·python·网络爬虫