页面加载性能之禁用document.write

发布于:2023-03-12 ⋅ 阅读:(69) ⋅ 点赞:(0)

如果你在控制台看到了以下信息,那么这篇文章对你而言应该会有帮助:

(index):34 A Parser-blocking, cross-origin script,
https://paul.kinlan.me/ad-inject.js, is invoked via document.write().
This may be blocked by the browser if the device has poor network connectivity.

对于现在的web的开发者而言,document.write 应该是入门级的DOM API,但实际项目开发中,却很少使用。如果面试官问你为什么不去使用它,你会怎么回答?

很多人可能会说这个性能低,那么为什么低呢?先看个例子:

document.write('<script src="https://paul.kinlan.me/ad-inject.js"></script>');

在浏览器渲染页面之前,会去根据HTML标签解析DOM树。如果解析器遇到了 <script> 标签,则会停止DOM的解析,优先执行脚本。如果这个脚本动态插入了另一个脚本,则解析器会等待另一个脚本下载完成到执行完成,延后了页面渲染的时间。

对于网络环境差的,比如 2G 的用户,通过 document.write 写入的新的脚本,可能会导致页面渲染的时间延后数十秒,用户可能就因此放弃了继续等待。基于Chrome的数据报告显示,那些通过 document.write 插入第三方脚本的页面在2G网络下比一般页面慢2倍。

Chrome从55版本开始就开始选择性的干预这类写法,当同时满足以下条件的时候,会禁止脚本执行:

  1. 用户处于低速网络下,比如2G(今后可能会把该条件扩展成3G以及低速wifi)
  2. document.write 位于最高层级的document上(如果位于iframe中,因为不会阻止主页面渲染,所以不会干预)
  3. document.write 中的脚本是解析阻塞型的(如果带了 async 或者 defer 属性,则不会干预)
  4. 脚本与页面不在同一个站点下,Chrome不会干预eTLD+1级别的脚本,如: js.example.com 和 www.example.com
  5. 脚本不在HTTP Cache中,如果在缓存中,依然会执行,因为不会发出HTTP请求
  6. 页面的请求不是重载。如果用户手动重新重载,Chrome还是会执行脚本

一般第三方库会用这种写法来加载脚本,不过庆幸的是,大部分第三方库都支持async加载,这样就不会阻塞剩余内容的展现了。

如何修复这类问题?

禁用 document.write 插入新的脚本即可,没有比这更好的方案,如果必须使用,则加上 async 属性。

当你的站点受影响之后,如何检测?

有6条之多的规则需要检查,对你来说可能太过复杂,有没有更好的方法去检测?

检测用户是不是2G网络

想要知道多少用户受影响,只需要看看多少用户是2G网络,可以使用 Network Information API 来检测,然后将检测结果发到统计系统或者RUM收集系统:

if(navigator.connection &&
   navigator.connection.type === 'cellular' &&
   navigator.connection.downlinkMax <= 0.115) {
  // Notify your service to indicate that you might be affected by this restriction.
}

捕获开发者工具中的提示

如果在使用 document.write 的时候,只满足了2-5的条件,你会看到下面这种提示:

在这里插入图片描述
在开发者工具中看到这个你可以立刻发现问题,但怎么去检测这个影响范围有多广呢?你可以检测发往你服务器请求的HTTP Headers

你可能会尝试着模拟2G网络来强制干预,但其实没必要,可以直接开启这项功能,使用 chrome://flags/#disallow-doc-written-script-loads

检查你的资源请求的HTTP headers

如果通过 document.write 的方式插入的脚本被阻止了,浏览器会携带这样的请求头去请求资源:

Intervention: <https://shorturl/relevant/spec>;

如果只是warning,会携带以下请求头:

Intervention: <https://shorturl/relevant/spec>; level="warning"

这些请求头会携带在对资源的GET请求中(在实际干预的情况下异步请求)

总结

对于现代开发者而言是幸运的,因为大部分第三方库的编写者已经不再使用 document.write 的方式插入脚本了,项目开发中我们只要稍微留个心就好,一些远古的第三方库可能还会存在这样的问题。

参考

本文含有隐藏内容,请 开通VIP 后查看