宝哥软件园

浏览器环境中JavaScript脚本加载和执行的延迟和异步特性

编辑:宝哥软件园 来源:互联网 时间:2021-09-18

延迟和异步被认为是许多JavaScript开发人员“熟悉和不熟悉”的两个特性。从字面上看,它们的功能都很好理解,分别是‘延迟脚本’和‘异步脚本’的功能。但是,以delayeded为例,有些细节可能是开发人员不太熟悉的,比如:具有delay特性的脚本什么时候会延迟执行;内部脚本和外部脚本是否都支持defer;延期后除了延迟脚本的执行,还有什么特别的地方等。结合已有的一些文章和MDN文档中对这两个特性的描述,对delay和async进行了较为全面的研究和总结,希望能帮助开发者更好地掌握这两个特性。

1导言

正如我们在《浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序》中提到的,JavaScript代码的执行会阻塞页面的解析和呈现以及其他资源的下载。当然,由于JavaScript是单线程语言,这意味着在正常情况下,页面中的JavaScript代码只能从上到下按顺序执行。当然,正如我们在《浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序》中所分析的,在某些情况下,例如,当通过document.write输入脚本或通过动态脚本技术引入脚本时,JavaScript代码的执行顺序不一定是严格从上到下的,defer和async也是我们所说的“异常情况”。

我们常说JavaScript执行是有阻碍的,但在实际开发中,我们通常最关心的是阻碍,而最影响用户体验的阻碍应该是以下几个方面:

[1]阻止页面解析和呈现。

[2]我们编写的页面初始化脚本(一般是通过监控DOMContentLoaded事件绑定的脚本,这部分脚本是我们希望先执行的脚本,因为我们会在这里为用户交互编写最相关的代码)。

[3]阻止页面上外部资源(如图片)的下载。

如果我们有一个耗时的脚本操作,这个脚本把上面三个地方都屏蔽了,那么这个网页的性能或者用户体验就会非常差。

延迟和异步的初衷是为了解决或减轻阻塞对页面体验的影响。下面我们来分析一下这两个特点,主要从以下几个方面来理解:

[1]延迟或异步脚本的执行时机是什么时候?页面的阻塞情况如何?

[2]内部脚本和外部脚本都可以延迟或异步吗?

[3]浏览器如何支持这两个功能?有没有相关的bug?

[4]在使用使用这两个功能的脚本时,我还应该注意什么?

2延迟特性。

2.1关于延期脚本的执行时机。

delay特性是HTML4规范中定义的扩展特性,最初只被IE4和firefox3.5支持,后来被chrome等浏览器支持,使用的方法是defer=' defer '。延迟意味着延迟,这意味着延迟脚本的执行。在正常情况下,我们介绍的脚本会被立即下载并执行,但是有了defer特性,脚本在下载后不会立即执行,而是会等到页面被解析。让我们看看HTML4标准中对defer的描述:

defer:设置后,此布尔属性向用户代理提供提示,说明脚本不会生成任何文档内容(例如,javascript中没有“document.write”),因此用户代理可以继续解析和呈现。

也就是说,如果设置了delay,告诉用户代理这个脚本不会产生任何文档内容,这样用户代理就可以继续解析和呈现。让我们看一下MDN中延迟的关键描述:

延迟:如果异步属性不存在,但延迟属性存在,则在页面完成解析后执行脚本。

通过标准中的定义,我们可以明确delay的脚本不会阻塞页面解析,而是会等到页面解析完成,但是耗时的delay可能还是会阻塞外部资源的下载,那么会阻塞DOMContentLoaded事件吗?事实上,defer的脚本仍然在DOMContentLoaded事件之前执行,所以它仍然会阻止DOMContentLoaded中的脚本。我们可以使用下图来帮助我们理解延迟脚本的执行时间:

根据标准中的定义,内部脚本不支持delay,而IE9及以下版本的浏览器为内部脚本提供delay支持。

2.2浏览器支持延期。

让我们看看浏览器对延迟功能的支持:

IE9及以下浏览器有一个bug,后面的DEMO中会详细解释。

2.3演示:延期功能的功能验证。

我们模仿Olivier Rochard在《the script defer attribute》中使用的方法来验证延迟特征的功能:

首先,我们准备了6个外部脚本:

1.js:

Test='我是头部外部脚本 n ';

2.js

Test='我是body的外部脚本 n ';

3.js

Test='我是底层外部脚本 n ';

defer1.js

Test='我是头外部延迟脚本 n ';

defer2.js

Test='我是一个身体外部延迟脚本 n ';

defer3.js

Test='我是底层外部延迟脚本 n ';

HTML中的代码是:

!DOCTYPE html htmlheartheta charset=' UTF-8 '/title defer属性test/title script src=' http :http://lib . Sina app.com/js/jquery/1 . 9 . 1/jquery-1 . 9 . 1 . min . js '/script script type=' text/JavaScript ' var test=' ';/script script src=' http : defer 1 . js ' type=' text/JavaScript ' delay=' delay '/script script src=' http :1 . js ' type=' text/JavaScript '/script script delay=' delay ' test='我是head delay的内部脚本n '。/scriptscripttest='我是头部内部脚本 n ';/Script/head dybutton id=' test ' click/button Script src=' http : defer 2 . js ' type=' text/JavaScript ' delay=' delay '/Script Script src=' http :2 . js ' type=' text/JavaScript '/Script/body Script src=' http : defer 3 . js ' type=' text/JavaScript ' delay=' delay '/Script src=' http :3 . js '})window . onload=function(){ test='我是window.onload n中的脚本';var button=document . getelementbyid(' test ');button . onclick=function(){ alert(test);}}/script/html代码,为了方便DOMContentLoaded事件的实现,我们引入了jQuery(下面的文章将介绍如何自己实现兼容的DOMContentLoaded)。然后,我们分别在脚本的头部、体内部和体外部引入延迟脚本和正常脚本,并通过全局字符串记录每段代码的执行状态。让我们看看每个浏览器中的执行结果:

IE9IE10Chrome Firefox我是head外部脚本我是head内部脚本我是body外部脚本我是head外部延迟脚本我是head延迟内部脚本我是body外部延迟脚本我是DOMContentLoaded内部脚本我是window.onload内部脚本。

我是头部外部脚本,我是头部内部脚本,我是底部外部脚本,我是头部外部延迟脚本,我是身体外部延迟脚本,我是底部外部延迟脚本,我在DOMContentLoaded脚本内部,我在window.onload脚本内部。

我是head外部脚本,我是head延迟内部脚本,我是body外部脚本,我是head外部延迟脚本,我是body外部延迟脚本,我是DOMContentLoaded内部脚本,我是window.onload内部脚本。

我是head外部脚本,我是head延迟内部脚本,我是body外部脚本,我是head外部延迟脚本,我是body外部延迟脚本,我是DOMContentLoaded内部脚本,我是window.onload内部脚本。

我是head外部脚本,我是head delay内部脚本,我是body外部脚本,我是head外部延迟脚本,我是body外部延迟脚本,我是DOMContentLoaded内部脚本,我是window.onload内部脚本,我们可以从输出结果中确认。只有IE9及以下的浏览器支持内部延迟脚本,延迟后的脚本会在DOMContentLoaded事件之前触发,所以DOMContentLoaded事件会被阻止。

2.4 DEMO: IE=9延期特性bug。

从2.3节的演示中可以看出,延期后的脚本仍然可以按照添加的顺序执行。但是在IE=9中,这个问题有一个bug:如果我们添加几个脚本来遵从文档,而前面的脚本有修改DOM的接口调用,比如appendChild、innerHTML、insertBefore、replaceChild等。则可以在此脚本之前执行以下脚本。你可以参考github的问题:https://github.com/h5bp/lazyweb-requests/issues/42.

我们用DEMO来验证一下。首先,将1.js的代码修改为(这段代码只是为了模拟,实际上这段代码有很大的性能问题):

document . body . inner html=' div id=' div '我后来加入了/div ';document . body . inner html=' div id=' div '我后来加入了/div ';document . body . inner html=' div id=' div '我后来加入了/div ';document . body . inner html=' div id=' div '我后来加入了/div ';document . body . inner html=' div id=' div '我后来加入了/div ';document . body . inner html=' div id=' div '我后来加入了/div ';document . body . inner html=' div id=' div '我后来加入了/div ';alert(‘我是第一个剧本’);

2.js

alert(‘我是第二个剧本’);

将HMTL的代码修改为:

!DOCTYPE html html townleta charset=' UTF-8 '/title defer bug in IE=9 test/title Script src=' http :1 . js ' type=' text/JavaScript ' defer=' defer '/Script src=' http :2 . js ' type=' text/JavaScript ' defer=' defer '/Script/head dy/body/html在正常情况下,浏览器中弹出框的顺序必须是:我是第一个脚本-“我是

2.5推迟总结

在总结之前,首先要说一个注意事项:正如标准中提到的,document.write的操作不应该出现在delay的脚本中,浏览器会直接忽略这些操作。

总的来说,defer的功能在一定程度上类似于将脚本放在页面底部。但是由于IE=9中的bug,如果页面中有多个delay,可能会干扰脚本的执行顺序,导致代码依赖错误。因此,delay特性在实际项目中很少使用,在页面底部放置脚本代码可以替代delay提供的功能。

3个异步功能。

3.1关于异步脚本的执行时机。

异步特性是HTML5中引入的一个特性,它的使用方式如下:async='async '。让我们首先看看标准中异步特性的相关描述:

async:如果存在async属性,那么只要脚本可用,它就会异步执行。

需要指出的是,这里的异步指的是异步加载而不是异步执行,也就是说当浏览器遇到异步脚本标签时,会异步卸载(个人认为这个过程主要是下载过程),一旦加载完成,代码就会被执行,执行过程肯定是同步的,也就是被阻塞。我们可以通过下图全面理解延迟和异步:

这样,异步脚本的执行时间是不确定的,因为脚本加载的时间也是不确定的。让我们通过下面的演示来感受一下:

async1.js

alert(‘我是异步脚本’);

HTML代码:

!DOCTYPE html html head lang=' en ' meta charset=' UTF-8 ' title async属性test/title script src=' http :/delayfile . PHP?URL=http://localhost/js/load/async 1 . jsdelay=2 ' async=' async ' type=' text/JavaScript '/script script alert('我是同步脚本');/script/head body/body/html在这里,我们借用了《浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序》中的delayfile脚本来提供延迟。在支持异步的浏览器中,这个脚本的顺序一般是:我是同步脚本——“我是异步脚本”。

3.2异步浏览器支持。

让我们来看看浏览器对异步功能的支持:

可以看到只有IE10支持异步,opera mini不支持异步,异步不支持内部脚本。

3.3异步总结

Async指的是异步脚本,即脚本是异步加载的,加载过程不会造成阻塞。然而,异步脚本的执行时间是不确定的,执行顺序也是不确定的。因此,使用async的脚本应该是不依赖于任何代码(如第三方统计代码或广告代码)的脚本,否则会导致执行错误。

4延迟和异步的优先级问题。

这很容易理解,标准规定:

[1]如果script元素同时定义了defer和async属性,它将被视为async(注意:对于不支持async的浏览器,async属性将被直接忽略)。

[2]如果脚本元素只定义了delay,它将被视为延迟脚本。

[3]如果脚本元素没有定义defer或async,它将被正常处理,也就是说,脚本将被立即加载和执行。

更多资讯
游戏推荐
更多+