丽江旅游网站建设,网站文章内容一键排版功能,北京列表网,网站整站开发项目亮点js加载和长任务
本文将讲解以下浏览器如何加载js#xff0c;并介绍一些可以提高网页加载速度的方法。
Evaluate Script
如果我们在devtools的performance中分析过网站的加载性能#xff0c;可能会看到一个很长的任务#xff0c;叫做Evaluate Script. 在这种情况下#x…js加载和长任务
本文将讲解以下浏览器如何加载js并介绍一些可以提高网页加载速度的方法。
Evaluate Script
如果我们在devtools的performance中分析过网站的加载性能可能会看到一个很长的任务叫做Evaluate Script. 在这种情况下该工作足以导致长时间任务从而阻止主线程承担其他工作包括驱动用户交互的任务.
Evaluate Script是在浏览器中执行 JavaScript 的必要部分因为 JavaScript在执行前即时编译。当评估脚本时首先会分析它是否有错误。如果解析器没有发现错误则脚本将被编译为字节码然后可以继续执行。
因为用户可能会在页面最初显示后不久就尝试与页面进行交互这样就会导致Evaluate Script出现问题页面已呈现并不意味着页面已完成加载。由于页面正忙于评估脚本因此加载期间发生的交互可能会延迟。 总阻塞时间 (TBT)是一个可以让我们深入了解页面加载期间是否发生过多脚本评估的指标因为它是一种负载响应指标。 script和评估它们的任务之间的关系
负责脚本评估的任务如何启动取决于网站正在加载的脚本是否是通过常规script元素加载的或者脚本是否是使用typemodule. 由于浏览器倾向于以不同的方式处理事物因此主要浏览器引擎如何处理脚本评估将涉及它们之间的脚本评估行为的不同之处。
使用script元素
分派评估脚本的任务数量通常与页面上的script元素数量有直接关系。每个script元素都会启动一个任务来评估所请求的脚本以便可以对其进行解析、编译和执行。
我们可以通过避免加载大块 JavaScript 来分解脚本评估工作并使用其他script元素加载更多单独的、更小的脚本。 由于设备的功能各不相同因此很难为单个脚本的大小定义一个设定的限制。为了在压缩效率、下载时间和脚本评估时间之间取得良好的平衡每个脚本的大小限制为 100 KB 是一个不错的指标。 因此我们在页面加载期间应该加载尽可能少的 JavaScript通过拆分脚本可确保我们拥有大量不会阻塞主线程的较小任务而不是一个可能阻塞主线程的大型任务。 由于页面 HTML 中存在多个script元素因此产生了多个任务来评估脚本。这比向用户单独发送一个体积非常大的js包更好因为这样更有可能阻塞主线程。
在script元素添加typemodule属性
通过在script元素上添加typemodule属性可以在浏览器中本地加载 ES 模块。这种脚本加载方法具有一些开发人员体验优势例如在生产环境中不需要转换代码就可以使用。但是以这种方式加载脚本会根据浏览器的不同而具有不同的任务。
基于 Chromium 的浏览器
在 Chrome 等浏览器或衍生自 Chrome 的浏览器中使用typemodule属性加载 ES 模块会产生与不使用时看到不同类型的任务。例如每个将执行的模块脚本携带typemodule的任务都呗标记为Compile module tasks。 每个模块脚本都会生成一个编译模块任务在评估之前编译其内容。
模块编译完成后随后在其中运行的任何代码都标记为Evaluate module tasks。 从上图可以看见使用typemodule需要承担一些不可避免的成本。虽然我们应该努力提供尽可能少的 JavaScript但使用 ES 模块无论浏览器如何都可以提供以下好处:
所有模块代码都会在严格模式下自动运行这允许 JavaScript 引擎进行潜在的优化而这些优化在非严格上下文中是无法实现的。使用typemodule属性在加载时会默认当作为defer。可以在ES加载的脚本上使用设置async来更改此行为。
基于Safari 和 Firefox 的浏览器
当在 Safari 和 Firefox 中加载typemodule模块时每个模块都会在单独的任务中进行评估。这意味着理论上我们可以将仅包含静态import语句的单个顶级模块加载到其他模块中并且加载的每个模块都会产生单独的网络请求和任务来进行评估。
使用动态import()
动态import()是加载脚本的另一种方法。import与需要位于 ES 模块顶部的静态语句不同动态import()调用可以出现在脚本中的任何位置以按需加载 JavaScript 块。这种技术称为代码分割。
使用动态import()有两个好处
推迟加载的模块设置defer通过减少当前加载的 JavaScript 量来减少启动期间的主线程争用。这释放了主线程因此它可以更好地响应用户交互。当进行动态import()调用时每次调用都会有效地将每个模块的编译和评估分离到自己的任务中。当然import()加载非常大的模块的动态将启动相当大的脚本评估任务并且如果交互与动态调用同时发生import()则可能会干扰主线程响应用户输入的能力。因此加载尽可能少的 JavaScript 还是非常重要的。
动态import()调用在所有主要浏览器引擎中的行为都类似结果的脚本评估任务将与动态导入的模块数量相同。
在web worker中加载js
Web Worker是一个特殊的 JavaScript 用例。Web Worker 在主线程上注册然后 Worker 中的代码会在自己的线程上运行。这可以减少主线程拥塞并有助于保持主线程对用户交互的响应更加灵敏。
除了减少主线程工作之外Web Worker本身还可以importScripts或者静态import来加载外部js这样通过 Web Worker 请求的任何脚本都会在主线程之外进行评估。
总结
虽然将脚本分解为单独的较小文件有助于限制长时间任务但是在决定如何分解脚本时也需要考虑以下几点
压缩效率
压缩是分解脚本的一个因素。当脚本较小时压缩效率会有所降低。较大的脚本将从压缩中受益更多。虽然提高压缩效率有助于尽可能缩短脚本的加载时间但确保将脚本分解为足够小的块以在启动期间促进更好的交互性也是需要权衡决定的。
打包工具是一个非常理想的工具可以用来管理我们的js打包结果
比如说webpack可以通过SplitChunksPlugin插件来管理打包后的js的大小。对于Rollup和esbuild而言可以通过在代码中使用动态import()来管理脚本文件大小。这些打包工具会自动将动态import()的资源分解到其他的文件中从而避免生成较大的文件。
缓存
缓存对于一些需要重复访问时页面加载的速度起着重要作用。当我们的js包非常大时每次更新之前的一些打包文件都会失效多个文件都打包在一个js文件里当修改里边的一些代码时整个打包文件都是更新过的必须要重新下载。
通过分解脚本我们不仅可以将脚本评估工作分解为较小的任务还可以让用户尽量从浏览器的缓存中获取之前的页面内容而不是需要重新下载文件。这意味着整体页面加载速度更快。 为了使缓存既高效又可以避免从缓存中提供过时的资源可以在打包出的文件设置哈希值作为文件名。 嵌套模块和加载性能
我们要是在生产环境中使用 ES 模块并使用typemodule属性加载它们则需要了解模块嵌套如何影响启动时间。模块嵌套是指一个 ES 模块静态导入另一个 ES 模块而另一个 ES 模块又静态导入另一个 ES 模块
// a.js
import {b} from ./b.js;// b.js
import {c} from ./c.js;如果这些模块没有被打包在一起那么就会导致生成一个请求链当一个script元素中包含了a.js的代码的时候就会发送一个请求去请求b.js文件然后b.js文件又会发送一个请求去请求c.js文件。这种情况下除了修改代码将这些打包在一起外还可以通过 modulepreload方式提前预加载 ES 模块以避免网络请求链仅支持基于 Chromium 的浏览器.