浏览器js资源加载的分析报告

浏览器是如何加载资源的?

网上有很多笼统的说法,css、图片并发加载,js同步加载。然而当我们碰到实际问题,尤其是在做性能优化的相关分析时,仅仅了解这点是不够的,我们需要更细粒度的时间点。


1.传统js

首先,我们做一个小实验。

<body>
<script>
    alert('waiting');
</script>
<nav id="nav">
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
</nav>
<script src="nav.js"></script>
</body>

这段代码会在最开始弹出一个提示框,我们知道alert是阻塞进程的,按前面的说法,js是同步加载的,那么是不是‘nav.js’在阻塞过程中没有被浏览器做任何处理呢?我们来看实验结果。

页面上,弹出了alert框,同时下面的html代码没有被渲染,这是好理解的,然而我们从chrome的Network中可以看到,nav.js居然已经被下载了!!!而我在nav.js里会有一个console.log的输出,查看后(不截图了),并没有打印,也就说该文件还没有被执行。

此时,我们能得出结论,浏览器的下载引擎和ui渲染引擎(js解析引擎与ui共享线程,可以把它们都归为ui引擎)是两个线程来运作的,它们是并行的。而js的执行和dom渲染则是同步串行执行的。

ps:查了一些资料,下载引擎和ui引擎的分离好像是较新版本的浏览器才出现的,我也没在老浏览器上试验过,需要注意下。

这就不难理解,为什么常有性能优化的文章指出,js文件要放到最后,这就是原因之一,js在dom前面,则要先执行,此时很可能js文件还没下载好,那么只能等待它下载,浪费了时间,而放在最后,往往可以利用解析前面的dom的时间来下载(至少下载一部分,能省一点是一点咯)。


2.H5提供的asnyc和defer属性支持的新型js加载方式

提到异步加载js,以前的前端师傅们,可能会想到js动态创建<script>标签,或者使用ajax,或者在onload或DOMContentLoaded之后执行等等方法,而最新的方法呢,就是H5提供的这两个属性。老师傅们的技术很好理解,就不聊了。我们来聊聊H5。

同样先复述一下我在网上查到的关于asnyc和defer的解释。

asnyc:异步加载js,如果js文件没有下载好,无需等待,跳过去执行下面的代码,什么时候这个js文件下载好了,什么时候执行。缺点:运行不依赖引入的顺序,如果js间有依赖可能会出错。

defer:延迟加载,只有在DOM完全解析渲染完毕,才会执行。浏览器确保多个 defer 脚本按其在HTML页面中的出现顺序依次执行,且执行时机为DOM解析完成后,document的DOMContentLoaded 事件触发之前。

再看一张TimeLine图   图片来源


defer还好理解,可以近似看作是  在DOMContentLoaded事件之后加载js  这种方法的官方增强版,毕竟比手动响应DOMContentLoaded事件要快一点。

但是asnyc这个就说的太抽象了,如图所示,asnyc声明的js文件是下载完成立刻执行,我立刻就有了一个疑问,它的执行还依赖它的引入顺序吗?你可能会理直气壮的说:不依赖啊,你上面不是说了吗?

的确,我上面说了,不依赖引入顺序,但是也说了,那是网上的表述。其实在我看来,它只分析了一种情况,那就是解析到js声明的地方,js还未下载好,此时确实不再依赖它,跳过去执行下面的。但是我们是否想过,如果js文件此时早就下载好了,那么这个js文件是解析到它声明的地方再执行呢?还是在它下载好的时候就已经执行了呢?(至于为什么要关注这个问题,我就不解释了,等遇到需要关注这个点的问题的时候,自然就明白了)

好,针对这个问题,我用前面的例子,又做了几个小实验。先看第一个实验的代码

<body>
<script src="nav.js" async></script>
<script>
    alert('waiting');
</script>
<nav id="nav">
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
</nav>
</body>

nav.js

var nav = document.getElementById('nav');
nav.addEventListener('click',function () {
    alert('nav');
});
console.log('async nav.js');

我们在nav.js里去获取了id为nav的元素,如果不异步的话,那很明显,nav.js获取不到该元素,因为还没有渲染。但是在这里情况可能就不一样了。

浏览器执行1,弹出alert后,等待一段时间,等nav.js下载好,并执行,再点确定。结果如下:
结果如我等所料,元素获取不到,绑定事件出错。

浏览器执行2,弹出alert后,立刻点确定,这个时间差不好把握,可能需要多试几次。
我们会发现有时候是能获取到元素,并成功绑定的。why?当然是因为它的异步执行,如果下载好的时候,刚好它要操作的DOM元素已经渲染好了,那么就能获取到了呀。

实验2
实验1其实主要想让大家看一下异步执行在实际例子中到底是什么体现。而我们的问题是,带有async声明的js文件的执行与顺序有没有关系,那么我们稍微改变一下nav.js的引入位置。
<body>
<script>
    alert('waiting');
</script>
<nav id="nav">
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
</nav>
<script src="nav.js" async></script>
</body>


nav.js不变。

此时我们实验,不管alert弹框的确定在什么时候点,都能获取到DOM元素,那么结果就很明显了。


async实验完了,结果也有了,defer我觉得表述没有歧义,也就无所谓实验了,不过关于它是否依赖顺序的说法,网上也有说不依赖的,我做了简单的测验,顺序没乱,但是我用的js文件较少,就不贴实验过程了,不过我倾向于它是依赖顺序的说法。顺带提一下,w3cschool上说defer只有IE支持。


现将所有结果总结如下:

1.浏览器(较新版本,一些老版本没用过)下载js文件与html的解析渲染是接近同步的。

2.普通<script>标签引入的js,其执行与html解析同步,共享同一线程,只有在解析到该<script>标签时,才会执行(没有下载好还会阻塞线程,等待下载好)。

3.带有async属性的<script>标签,执行同样依赖其所在的位置,但是如果解析到该标签,js文件还没下载好,则跳过去,执行下面的代码,直到其下载好,再异步执行。这一点就是async所指的异步的真正含义。

4.defer标签会将所有带有该属性的<script>标签按照其声明顺序放入一个队列,在DOM元素渲染完成后,DOMContentLoaded事件触发前依次执行。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值