HTML script 脚本的 async 与 defer 属性及不同属性的运行时机与 DOMContentLoaded 事件的关系

news/发布时间2024/5/21 23:23:44

浏览器对于带有 async, defer 属性与不携带属性的 <script> 脚本有不同的行为。
它们可以分别翻译为:异步脚本,延迟脚本与同步(阻塞)脚本。
对于模块脚本,默认是 defer 的行为,它也能设置 async,以更改浏览器的处理方式。

同步脚本

不带 async 与 defer 属性的脚本是同步脚本,如果它们出现在文档头部及中间任意位置,会阻塞文档的解析。具体的行为是:

  1. 文档停止解析(与渲染)
  2. 请求脚本资源
  3. 资源下载完毕后立即解析并执行脚本
  4. 脚本解析完毕后恢复文档的解析

为了更好的用户体验,对于 HTML 文档,浏览器往往是边下载边解析边渲染,而不是等上一项任务完成后再继续下一项。

如果请求脚本资源消耗太长时间,或脚本中有同步的耗时任务,在脚本运行结束前,文档脚本位置之后的内容将不被解析与渲染,出现所谓‘白屏’,这会给人不好的感受。

为了保证文档的正常解析与渲染,如果使用同步脚本,应该将它们放在 <body> 闭合标签之前,这样不管脚本的下载或运行时怎样的慢,至少文档基本的内容是可读的。

关于 DOMContentLoaded 事件,它应该在文档解析完成后触发。但是触发之前,会等待同步脚本与延迟脚本(defer script)运行完毕。

async script (异步脚本)

async script 的下载会另起线程,与文档的解析是并行的,但它下载完成后会立刻解析并运行脚本,不论文档是否解析完毕。此时若文档尚未解析完成,则会阻塞文档的解析,当脚本运行耗时任务,也将会造成‘白屏’。

但是将 async script 放在文档的末尾意义不大,毕竟其下载资源时并不阻塞文档的解析。使用它的目的应该是:

希望脚本尽快执行,并减少对文档解析的阻塞。

由于 async script 会在下载后立即执行,不能预知哪个脚本会先下载完成,所以它们的运行时机是未知、无序的。

async script 可能会在文档解析完成之前或之后下载完成并执行,而 DOMContentLoaded 事件的触发并不会等待 async script 执行完毕,所以并不能知道 async script 会在 DOMContentLoaded 事件触发之前还是之后运行。基于以上原因,不要在 async script 中依赖别的脚本,也不要在 async script 中为 DOMContentLoaded 事件注册回调,回调函数能否被调用是未知的。

基于异步脚本的特点,以下场景适合使用:

  • 无依赖,不操作DOM,且需要尽快执行的任务,使用 async script 来减少对文档解析的阻塞。

    比如:PV/UV埋点统计

  • 在必要的 DOM 加载完成后,尽快为 DOM 元素加载内容、提供交互能力。

    由于 HTML 文档往往是边下载边解析边渲染,可以在网页首屏的 DOM 加载完毕后引入 async script,操作DOM元素,使用户可以尽快看到动态加载的内容或与页面互动。并尽量降低对 HTML 文档解析的影响

    但是除非文档的数据量很可观,下载与解析要消耗一些时间(3秒以上),否则这么做往往得不偿失。
    一般的动态页面,文档体积小,解析起来很快(下载到解析完只要几百毫秒),这么做不但达不到优化目的,阻塞渲染甚至会降低用户体验。

使用 async script 的目的是希望脚本尽快运行,可能是希望提升用户的使用体验,也可能是为了避免用户在脚本运行前就离开页面,而错过什么必要的任务。

使用它很可能会阻塞文档的解析,一定要慎重考量后做决定。
使用 async 也增加了维护成本:我们要记住它运行时机是不确定的,有些操作在该脚本中运行可能会无效,还要忍受它要引入依赖就只能在其前面引入同步脚本。
除此之外,在文档源码中间插入 <script> 标签也并不美观,它不被渲染到视图,还破坏了文档的连贯性。

不管出于什么目的,若决定使用 async script,插入到文档头部或中间,我们一定要保证脚本内的任务快速地运行完毕,不要做耗时操作,将对文档解析的阻塞尽量减少。

defer (延迟脚本)

defer script 会开辟新线程下载脚本,并等待文档解析完毕、所有 defer script 下载完毕后再按脚本在文档中出现的位置依次执行。
DOMContentLoaded 事件,会等待 defer script 全部执行完毕后才触发。

defer script 顺序加载的特点非常适合一个脚本依赖于另一个脚本的情况。
使用 defer script,并将其放到 <head> 中在大多数情况下都是合适的,这么做和将同步脚本放到 </body> 之前的效果一模一样,还能让 <body> 元素保持整洁,毕竟在尾部放一堆不会被渲染的 <script> 看起来不是那么的舒服。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ulsteruni.cn/article/52748524.html

如若内容造成侵权/违法违规/事实不符,请联系编程大学网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

测试与发布

目录测试报告一、bug的发现与解决二、场景测试(scenario testing)发布说明一、功能说明二、对运行环境的要求三、安装方法四、已知的限制和缺陷五、发布方式和发布地址 测试报告 一、bug的发现与解决1.在测试过程中总共发现了多少Bug?每个类别的Bug分别为多少个? 答:共发现…

8086 汇编学习 Part 5

流程转移 背景 一般情况下指令是顺序地逐条执行的,而在实际中,常需要改变程序的执行流程。 转移指令可以控制 CPU 执行内存中某处代码的指令。 可以修改 IP ,或同时修改 CS 和 IP 的指令。分类 按转移行为分类段内转移 : 只修改 IP (例如 JMP AX) 段间转移 : 同时修改 C…

win10 hyper-v 配置教程

非家庭版跳过以下这一步。 pushd "%~dp0"dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hv.txtfor /f %%i in (findstr /i . hv.txt 2^>nul) do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i"del hv.txtDi…

嵌入式Linux,openssh连接报错:ssh_sandbox_violation: unexpected system call

背景: 使用buildroot编译完镜像,烧录到开发板,板子上电启动后,网络正常,ssh不能连接,sshd相同配置在其他机器上可以正常使用; 查看内核日志,看到连接时上报异常系统调用的错误:Jan 1 00:01:18 NanoPC-T2 auth.crit sshd[278]: fatal: ssh_sandbox_violation: unexpec…

30 秒出服装设计稿,森马用函数计算+AIGC 整“新活”!

2023 年初,森马希望迅速将 AI 技术深度融入到企业的核心业务流程中,实现服装行业中,从产品设计、生产制造、营销推广到售后服务的全链条智能化升级。在一个阳光明媚的下午我们走进森马,一起聊聊这个陪伴一代代中国年轻人成长的企业,如何基于阿里云函数计算,快速实现传统服…

BurpSuite连接浏览器代理无法打开部分网页问题

BurpSuite连接浏览器代理时,部分页面能打开,部分页面报错的可能有效的解决方法。本人写这篇记录时,尚未熟悉bp基本操作,仅记录该次探索bp功能解决问题的心路历程。 发现并解决问题 最近两天为能打开尘封已久的bp,抓包做题,卸载了jdk20,下了jdk1.8(高版本jdk破解用的jav…