首发于: 技术风暴
我们今天来聊一聊关于JavaScript文件的引入位置的问题;大家在平时的Web开发中有没有想过这样一个问题,那就是我应该在文档的头部(也就是<head>
标签内部里面)引入所需要的JavaScript文件还是应该在尾部(也就是</body>
之前)引入所需要的JavaScript文件呢?今天我们就来深入的探究一下这个问题。
首先我们需要了解的一点就是,在浏览器渲染页面之前,它需要通过解析HTML
标记然后构建DOM
树。在这个过程中,如果解析器遇到了一个脚本(script
),它就会停下来,并且执行这个脚本,然后才会继续解析HTML
。如果遇到了一个引用外部资源的脚本(script
),它就必须停下来等待这个脚本资源的下载,而这个行为会导致一个或者多个的网络往返,并且会延迟页面的首次渲染时间。
还有一点是需要我们注意的,那就是外部引入的脚本(script
)会阻塞浏览器的并行下载
,HTTP/1.1规范表明,浏览器在每个主机下并行下载的组件不超过两个(也就是说,浏览器一次只能够同时从同一个服务器加载两个脚本);如果你网站的图片是通过多个服务器提供的,那么按道理来说,你的网站可以一次并行下载多张图片。但是,当我们网站在加载脚本的时候;浏览器不会再启动任何其它的下载,即使这些组件来自不同的服务器。
看到这里,也许很多开发者都会想:既然把脚本(script
)资源放在head
里面是个不好的主意,并且可能会阻塞浏览器渲染页面;那我们是不是要把所有的JavaScript
文件都放置到文档的底部呢?这个当然也是太过极端了,因为还是有一些情况需要我们在头部引用脚本的;到底是哪些情况需要我们这么做呢,下面我们来看看一些大公司的做法:
我们可以看到,这个搜索页面在头部加载了5个JavaScript文件(箭头标注的地方),其中两个JavaScript文件是内联的(inline
),另外三个大家可以看到script
标签上都添加了async
属性,那这个属性是干嘛的呢?我们会在下面解释。
我们可以看到,百度也在头部引入了一些JavaScript文件,这些文件引入的方式与Google的做法差不多,都在引入外部资源的script
标签上添加了async
属性,除了第一个JavaScript文件没有那样做。
最后一个是facebook的首页,令我比较出乎意料的是,facebook的首页的头部引入了大量的脚本(script
),大家可以看一下截图
不过基本上facebook的script
标签上面都添加了async
属性,下面我们先来来说一下script
标签上面这个async
属性的作用。
这个属性是HTML5给script
新添加的属性,而且只适用于外部的JavaScript文件,如果在script
标签上添加了这个属性,那么表明这个脚本资源就不再是同步加载的了,而是异步加载的,所以不会阻塞浏览器对页面的渲染。当然这个属性会存在一些兼容性问题,一些浏览器还未实现对这个属性的支持。
我们可以看到,虽然这些网站大部分的script
标签(针对引入的外部文件)都添加了async
属性,但是还是有一些script
标签没有添加async
属性,那就表示这些资源是同步加载执行的,在这里你可能会问,那这些资源为什么不使用异步加载呢?原因很大程度上是因为,这些脚本需要在浏览器渲染页面之前就执行的;比如Yahoo在Best Practices for Speeding Up Your Web Site中就指出,如果你的脚本中使用了document.write
在页面中插入内容的话,那就不能够将这条脚本放置到文档的底部了。类似的还有weibo,weibo的head
中也使用了一个要在页面渲染之前就执行的脚本,如下:
|
|
还有百度首页的head
中也有两条需要在页面渲染之前就执行的JavaScript文件:
|
|
还有一些比如Google和Baidu他们搜索页面同步加载的那些JavaScrip文件一些是为了在页面渲染之前做一些全局的处理(比如Google)添加了全局变量google
。
还有的就是单纯的满足自己业务上的一些需求了,比如百度同步加载的那个JavaScript文件:
所以说,除了上面这些情况外,其它的情况下我们的脚本资源都需要放在文档的底部;当然这里还有一些需要我们注意的问题,首先,脚本加载的顺序很重要,比如如果你的脚本需要使用jQuery
库,那么你就应该在加载你的脚本之前先加载jQuery
库。其次,有些脚本是需要等到某些元素加载完成之后才可以执行的,那么你可以将你的脚本紧挨在那个元素的后面;还有一些元素是通过脚本动态创建的,所以它们也需要放在合适的位置。比如微博的:
如果使用过一些框架的脚手架你就会发现,这些框架打包后的那个index.html
里面引入的外部JavaScript资源都是放在文档的底部的,并且它们也是按照顺序来的,vendor.js
文件(项目使用的框架,库打包形成的文件)先引入,然后才是app.js
文件(我们写的代码文件打包形成的),这就说明了引入脚本文件的顺序也是很重要的。
到现在为止,我们已经讨论了很多关于把JavaScript文件放在文档的头部还是尾部的原因,那么下面我们可以总结出一些加载JavaScript文件的最佳实践;
- 对于必须要在DOM加载之前运行的JavaScript脚本,我们需要把这些脚本放置在页面的
head
中,而不是通过外部引用的方式,因为外部的引用增加了网络的请求次数;并且我们要确保内敛的这些JavaScript
脚本是很小的,最好是压缩过的,并且执行的速度很快,不会造成浏览器渲染的阻塞。 - 对于支持使用
script
标签的async
和defer
属性的浏览器,我们可以使用这两个属性;其中需要注意的点就是,async
表示的意思是异步加载JavaScript文件,它的下载过程可以在HTML的解析过程中进行,加载完成之后立即执行这个文件的代码,执行文件代码的过程中会阻塞HTML的解析,它不保证文件加载的顺序。defer
表示的意思是在HTML文档解析之后在执行加载完成的JavaScript文件,JavaScript文件的下载过程可以在HTML的解析过程中进行,它是按照script
标签的先后顺序来加载文件的。更多详细的解释可以参考async vs defer attributes
到这里为止,整篇文章就算是结束了,如果你还想进一步的了解关于JavaScript文件加载的一些知识,可以看看这篇文章
参考的一些资料:
- Remove Render-Blocking JavaScript
- Put Scripts at the Bottom
- Best Practice: Where to include your script tags
- Script Element
- execCommand
- What is difference between using JavaScript in head and body?
- Where To Include JavaScript Files In A Document
- Should I always put my JavaScript file in the head tag of my HTML file so that the code is loaded at the start?
- Why put JavaScript in head
- Deep dive into the murky waters of script loading
- Asynchronous and deferred JavaScript execution explained
本作品保留所有权利。未获得许可前,不允许他人复制、发行、展览和表演作品。不允许他人基于该作品创作演绎作品。