script脚本阻塞的探究、异步属性async和defer的区别

您所在的位置:网站首页 加载脚本失败如何解决 script脚本阻塞的探究、异步属性async和defer的区别

script脚本阻塞的探究、异步属性async和defer的区别

2024-07-12 19:33| 来源: 网络整理| 查看: 265

本文主要讨论html中script标签的一些特性,其中,script的阻塞是讨论的核心点,主要围绕script的阻塞探讨其表现及解决方式。

一、阻塞dom的解析渲染 1、描述 由于js的执行可能会有操作dom的情况导致dom结构发生变化,而浏览器做了最坏的打算,所以在js执行时会暂停dom的解析渲染,这就造成了对dom的阻塞。记住这句话,任何情况下js的执行都会阻塞dom的渲染。 2、常规解决方式

一般就是把js放在body标签的最底部,这样既保证dom解析完后才执行js,也保证js里能获取到dom元素。 另外不建议把script代码放在body闭合标签()的下面,某些特殊的webview容器可能无法识别就把script给丢弃了(例如阿里百川某个版本的webview)

二、阻塞后续script的执行 1、js报错异常造成的影响 大家都知道,js是单线程的,一旦js代码报错就会造成阻塞,导致后续js代码不再执行。这里抛出个问题:如果有两个script标签,前一个script里的js报错会不会阻塞后面的script代码?实践一下呗,写个demo: Neo console.log(a) // 打印一个未定义的变量a console.log(0) console.log(1) 运行结果如下,第一个script里打印a报错,导致打印0的语句未执行,但后面的script并未受到影响,正常打印出了1。: 在这里插入图片描述结论:script里的代码异常只会阻塞同一script里后续的代码,不会影响其他script的代码。那是否意味着我的script怎么写怎么浪都没关系了?并不是,往下看。 2、js下载异常造成的影响 script引入js有两种方式,内联方式和外联方式,内联方式就是script标签包裹js代码,外联方式就是script标签通过src属性指定js的地址。内联方式里你script怎么写怎么浪都行,但外联方式就未必。你可以试着运行下面的代码试试: Neo console.log(1) 运行结果,html加载状态那里一直在转圈圈,起初没有任何打印输出,大概等了20秒后输出了如下结果: 开始时:开始时 20秒后:20秒后如上,浏览器查找"file://www.rogue.com/rogue.js"这个js文件,但是一直搜寻不到,导致后续的script也处于阻塞当中,这个问题和file协议没有关系,我这里只是找了个能作为示例的地址,http和https协议的地址都有可能发生。联想到实际项目中,通过script外联方式引入跨域的第三方插件时可能会有阻塞后续script代码的风险,一般是引用的跨域地址服务器不稳定或连接缓慢时发生,所以使用任何第三方的域名地址都要警惕,不管是开源组织的还是付费商用的,要把命运握在自己手里才踏实。 三、异步script

在不加任何异步属性的情况下,script的下载和执行都会阻塞dom的渲染,而添加异步属性可以使下载阶段异步进行。 以下是几种异步script的解释和对比:

1、async属性 异步下载script代码。不支持内联方式,也就是script标签必须有src属性。执行时机:下载完后,立即执行。执行顺序:下载完js文件的顺序,即网络请求返回顺序,无法提前预知。使用示例: tips:这个async属性正好解决了上述讲的的“js下载异常造成的影响”。 2、defer属性 异步下载script代码。(同async)不支持内联方式,也就是script标签必须有src属性。(同async)执行时机:下载完后,在dom解析完之后、触发DOMContentLoaded之前执行。(不同于async)执行顺序:如果带defer的script有多个,那它们将按照在页面中出现的顺序来依次执行。(不同于async)使用示例: 3、动态创建 通过src属性赋值动态创建的script,未指定async属性时也是默认异步的。例如: // 默认异步 var script = document.createElement('script') script.src = "file.js" document.body.appendChild(script) 通过innerHtml或eval方式创建的script,未指定async属性时默认不是异步。例如: // 非异步 document.body.innerHTML = '' 关于以上动态创建的异步是属于哪种异步,由于没有合适的方式验证,在mdn上这块也没有说明,不过是在介绍async时说到的,所以猜想应该是async异步方式吧。为了防止不必要的麻烦,即使动态创建的script默认是异步,也建议在创建异步script时手动添加相应的异步属性,async属性值用true,defer属性值就用defer。 4、用哪个

对于async和defer用哪个,看完上述两者的说明对比一下,应该能总结出一二了。

借来mdn上的建议总结:

如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 async。如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。

注意:

文章开始时讲到任何情况下js的执行都会阻塞dom的渲染。同样的,async和defer也一样,它们只是使js的下载阶段异步,执行阶段仍然会阻塞dom。 四、思考 现在的spa框架开发,大多数情况下我们不需要关注script标签的引入,脚手架和webpack都已经帮我们自动处理好了。而我探究这个问题的初衷是解决script方式引入第三方插件时的问题。像一些第三方插件例如微信jsbridge、埋点、监控、各种sdk什么的,很多都是需要通过script标签的方式来引入到html模板中,而如何引入才能做到对原项目的影响最小是个值得深思的问题。对于这类script的引入,个人建议: 能放在body最底部的就放在body最底部。能加异步属性的就加异步属性,根据具体情况选择一种,没有特殊要求时优先async,其次defer。(因为webpack打包后的script也会放在body最底部,比你引入的第三方插件script还靠后,所以你仍然需要异步属性。)如果只能使用同步方式,要格外注意src地址的连接速度和稳定性,最好能把代码拉下来放在本地项目或自己的服务器域名下,非插件官方的地址不要轻易使用,特别是国外的域名地址。

参考链接: [1] https://developers.google.com/web/fundamentals/performance/critical-rendering-path/adding-interactivity-with-javascript?hl=en [2] https://zhuanlan.zhihu.com/p/292953374 [3] https://html.spec.whatwg.org/multipage/scripting.html#the-script-element



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3