如果你在控制台看到了以下信息,那么这篇文章对你而言应该会有帮助:
(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版本开始就开始选择性的干预这类写法,当同时满足以下条件的时候,会禁止脚本执行:
- 用户处于低速网络下,比如2G(今后可能会把该条件扩展成3G以及低速wifi)
document.write
位于最高层级的document上(如果位于iframe中,因为不会阻止主页面渲染,所以不会干预)document.write
中的脚本是解析阻塞型的(如果带了async
或者defer
属性,则不会干预)- 脚本与页面不在同一个站点下,Chrome不会干预eTLD+1级别的脚本,如: http://js.example.com 和 http://www.example.com
- 脚本不在HTTP Cache中,如果在缓存中,依然会执行,因为不会发出HTTP请求
- 页面的请求不是重载。如果用户手动重新重载,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
的方式插入脚本了,项目开发中我们只要稍微留个心就好,一些远古的第三方库可能还会存在这样的问题。
参考
- https://developers.google.com/web/updates/2016/08/removing-document-write
- https://web.dev/no-document-write/
- https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript