Appearance
异步与阻塞
元信息
- 目标:从"三个外域脚本输出什么"切入,理解互联网不可靠决定了脚本加载的确定性无法预设;掌握同步/async/defer 三种加载方式在下载、执行时机、顺序上的差异,分清同步异步×阻塞非阻塞两组概念
- 关键概念:同步加载、异步加载、defer、async、阻塞渲染、DOMContentLoaded、动态加载脚本、同步/异步、阻塞/非阻塞
- 关联阶段:cp3
- 常见误区:以为脚本按书写顺序输出是理所当然(网络不可靠,async 顺序不确定);有依赖的脚本用 async(应 defer 保序);把同步/异步与阻塞/非阻塞混为一谈(一组说任务间关系,一组说是否卡住渲染)
问题
假设每个脚本都包含一个console.log('...'),输出的是自己的文件名,比如a.js输出的是a
请问:上述代码最终输出的是什么?
答案
- abc
- ab
- a
- 没有任何输出
因为互联网是不可靠的,可能因各种原因无法访问(网络故障,主机宕机),所以是否加载成功是难以预测或确定的
同步加载
当浏览器解析HTML文档时,默认遇到<script>标签会立即停止HTML解析,开始下载并执行JavaScript代码,执行完毕后继续解析HTML。这种行为称为"同步加载"和"阻塞渲染"。
JavaScript可能会操作DOM(文档对象模型),比如修改页面元素、添加事件监听器等。如果脚本执行时DOM还未构建完成,可能会导致错误。因此,浏览器默认采用同步方式确保脚本执行时DOM已经准备就绪。
异步加载
2.1 defer属性:延迟执行
defer属性告诉浏览器异步下载脚本,但延迟到HTML解析完毕后,DOMContentLoaded事件触发前执行。
特点:
- 脚本异步下载,不阻塞HTML解析
- 延迟到HTML解析完毕后执行
- 多个带
defer属性的脚本按在HTML中的顺序执行 - 适用于依赖DOM的脚本
执行时序图:
2.2 async属性:异步加载
async属性告诉浏览器异步下载脚本,不阻塞HTML解析。脚本下载完成后会立即执行,执行时会暂停HTML解析。
特点:
- 脚本异步下载,不阻塞HTML解析
- 下载完成后立即执行,执行时暂停HTML解析
- 多个带
async属性的脚本执行顺序不确定,取决于下载完成的速度 - 适用于独立脚本,如统计分析代码
执行时序图:
3. 三种加载方式对比
理解的核心是浏览器对网页的三个关键步骤
- 下载:连接服务器,读取网络流到本地
- 解析:对html代码进行解析,如定位、布局、渲染等
- 执行:运行js代码
| 属性 | 下载方式 | 执行时机 | 执行顺序 | 适用场景 |
|---|---|---|---|---|
| 无属性 | 同步 | 立即执行 | 按HTML顺序 | 依赖DOM且需要立即执行的脚本 |
async | 异步 | 下载完成后立即执行 | 不确定 | 独立脚本,如统计分析 |
defer | 异步 | HTML解析完成后执行 | 按HTML顺序 | 依赖DOM但不需要立即执行的脚本 |
4. 推荐的脚本加载策略
4.2 动态加载脚本
除了使用HTML属性,我们还可以通过JavaScript动态加载脚本:
5. 性能影响与最佳实践
5.1 页面加载性能
脚本加载方式直接影响页面加载性能:
- 同步脚本:增加页面白屏时间,用户体验差
- 异步脚本:减少页面加载时间,但可能执行时机不确定
- 延迟脚本:平衡加载时间和执行时机
5.2 最佳实践建议
- 关键路径优化:将关键CSS和少量关键JavaScript放在
<head>中同步加载 - 非关键资源延迟:使用
defer加载依赖DOM的非关键脚本 - 独立脚本异步:使用
async加载独立脚本,如统计、广告等 - 脚本合并:减少HTTP请求数量,合并小脚本
- 脚本压缩:使用工具压缩JavaScript文件,减少下载时间
6. 常见问题与解决方案
6.1 脚本依赖问题
问题:多个脚本有依赖关系,如何确保执行顺序?
解决方案:使用defer属性,确保按HTML顺序执行:
6.2 DOM Ready
问题:脚本执行时DOM尚未准备好
解决方案:使用DOMContentLoaded事件或defer属性:
7. 总结
理解JavaScript脚本的加载方式对前端开发至关重要:
- 同步加载(默认):阻塞渲染,确保脚本执行时DOM已准备好
- 异步加载(
async):不阻塞下载,执行时机不确定,适用于独立脚本 - 延迟加载(
defer):不阻塞下载,确保执行顺序,适用于依赖DOM的脚本
合理使用这些属性可以显著提升网页性能,改善用户体验。在实际开发中,应根据脚本特性和需求选择最合适的加载方式。
7.1 同步(Synchronous)与异步(Asynchronous)
同步是指任务按顺序执行,一个任务必须等待前一个任务完成后才能开始。就像排队买票,只有前面的人买完,后面的人才能开始购买。
异步是指任务可以并行执行,不需要等待其他任务完成。就像在餐厅点餐,多人可以同时点餐,不需要等待前面的人吃完。
7.2 阻塞(Blocking)与非阻塞(Non-blocking)
阻塞是指一个任务执行时会阻止其他任务的进行。在浏览器中,阻塞通常指脚本执行会暂停HTML解析和页面渲染。
非阻塞是指一个任务执行时不会阻止其他任务的进行。在浏览器中,非阻塞通常指脚本下载或执行不会影响HTML解析和页面渲染。
7.3 同步/异步与阻塞/非阻塞的组合
在JavaScript脚本加载中,这四个概念可以组合成不同的情况:
| 组合 | 描述 | 示例 |
|---|---|---|
| 同步阻塞 | 脚本按顺序加载,执行时暂停HTML解析 | 默认的<script>标签 |
| 异步阻塞 | 脚本异步下载,但执行时暂停HTML解析 | 带有async属性的脚本 |
| 异步非阻塞 | 脚本异步下载,执行时不暂停HTML解析 | 带有defer属性的脚本 |
9. 参考
思考
为什么网络软件总是采用异步调用?