一个网页的有很多地方可以进行性能优化,比较常见的一种方式就是异步加载js脚本文件。在谈异步加载之前,先来看看浏览器加载js文件的原理。

浏览器加载 JavaScript 脚本,主要通过<script>元素完成。正常的网页加载流程是这样的。

  1. 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
  2. 解析过程中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。
  3. 如果<script>元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。
  4. JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复解析 HTML 网页。

加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题。

上面所说的,就是我们平时最常见到的,将`<script>`标签放到`<head>`中的做法,这样的加载方式叫做同步加载,或者叫阻塞加载,因为在加载js脚本文件时,会阻塞浏览器解析HTML文档,等到下载并执行完毕之后,才会接着解析HTML文档。如果加载时间过长(比如下载时间太长),就会造成浏览器“假死”,页面一片空白。而且,放在`<head>`中同步加载的js文件中不能对DOM进行操作,否则会产生错误,因为这个时候HTML还没有进行解析,DOM还没有生成。由此看来,同步加载带来的体验往往并不好。

下面我们来看几种异步加载的方式。

1. 将<script>标签放到<body>底部

严格来说,这并不算是异步加载,但是这也是常见的通过改变js加载方式来提升页面性能的一种方式,所以也就放到这里来说。
<script>放到<body>底部,解决上上面说到的几个问题,一是不会造成页面解析的阻塞,就算加载时间过长用户也可以看到页面而不是一片空白,而且这时候可以在脚本中操作DOM。

2. defer属性

通过给<script>标签设置defer属性,将脚本文件设置为延迟加载,当浏览器遇到带有defer属性的<script>标签时,会再开启一个线程去下载js文件,同时继续解析HTML文档,等等HTML全部解析完毕DOM加载完成之后,再去执行加载好的js文件。
这种方式只适用于引用外部js文件的<script>标签,可以保证多个js文件的执行顺序就是它们在页面中出现的顺序,但是要注意,添加defer属性的js文件不应该使用document.write方法。

3. async属性

async属性和defer属性类似,也是会开启一个线程去下载js文件,但和defer不同的时,它会在下载完成后立刻执行,而不是会等到DOM加载完成之后再执行,所以还是有可能会造成阻塞。
同样的,async也是只适用于外部js文件,也不能在js中使用document.write方法,但是对多个带有async的js文件,它不能像defer那样保证按顺序执行,它是哪个js文件先下载完就先执行哪个。

4. 动态创建<script>标签

可以通过动态地创建<script>标签来实现异步加载js文件,例如下面代码:

(function(){
var scriptEle = document.createElement("script");
scriptEle.type = "text/javasctipt";
scriptEle.async = true;
scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";
var x = document.getElementsByTagName("head")[0];
x.insertBefore(scriptEle, x.firstChild);
})();

或者

(function(){
if(window.attachEvent){
window.attachEvent("load", asyncLoad);
}else{
window.addEventListener("load", asyncLoad);
}
var asyncLoad = function(){
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
}
})();

上面两种方法中,第一种方式执行完之前会阻止onload事件的触发,而现在很多页面的代码都在onload时还执行额外的渲染工作,所以还是会阻塞部分页面的初始化处理。第二种则不会阻止onload事件的触发。
这里要简要说明一下window.DOMContentLoadedwindow.onload这两个事件的区别,前者是在DOM解析完毕之后触发,这时候DOM解析完毕,JavaScript可以获取到DOM引用,但是页面中的一些资源比如图片、视频等还没有加载完,作用同jQuery中的ready事件。后者则是页面完全加载完毕,包括各种资源。

说完了这几种常见的异步加载js脚本的方式,再来看最后一个问题,什么时候用defer,什么时候用async呢?一般来说,两者之间的选择则是看脚本之间是否有依赖关系,有依赖的话应当要保证执行顺序,应当使用defer没有依赖的话使用async,同时使用的话defer失效。要注意的是两者都不应该使用document.write,这个导致整个页面被清除。

下面一幅图表明了同步加载以及deferasync加载时的区别,其中绿色线代表 HTML 解析,蓝色线代表网络读取js脚本,红色线代表js脚本执行时间:

网页性能优化之异步加载js文件的更多相关文章

  1. JS性能优化之怎么加载JS文件

    IE8+等实行并行下载,各JS下载不受影响,但仍阻塞其他资源下载 如: 图片 所以首要规则就是:将JS放在body底部(推荐) 加载100kb的单个文件比4个25kb的文件快(减少外链文件数量)(脚本 ...

  2. angularLoad(用以异步加载js文件)

    angularLoad(用以异步加载js文件) 使用方法: 1.执行命令 下载 lib npm install angular-load --save 2.index.html引用js <scr ...

  3. 异步加载js文件的方法

    # 异步加载js文件 - js的加载默认是同步的,因为js是单线程执行,只能完成一件再执行下一件. - 一些外部引入的js文件可以因为文件太大,在加载资源的过程中会影响dom元素的加载,影响了用户体验 ...

  4. 异步加载js文件的方法总结

    方法一,jQuery.getScript HTML 代码: 代码如下 复制代码 <button id="go">Run</button><div cl ...

  5. 页面异步加载javascript文件

    昨天听一同事说的异步加载js文件,可以提高页面加载速度.具体方法如下:(function() {  var ga = document.createElement('script'); ga.type ...

  6. 网页加载速度优化2--先加载css,然后再加载js文件。

    网页加载时,是按从上到下,从左到右的顺序加载的.所以一定要先加载css文件(不要让用户看到一个杂乱无章的页面),最后再加载js文件,js一般都是处理功能的,所以不需要提前加载.先给用户观感,再给用户上 ...

  7. HTML5中script的async属性异步加载JS

    HTML5中script的async属性异步加载JS     HTML4.01为script标签定义了5个属性: charset 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer 可 ...

  8. 异步加载js的三种方法

    js加载时间线 : 它是根据js出生的那一刻开始记录的一系列浏览器按照顺序做的事,形容的就是加载顺序,可以用来优化什么东西,理论基础,背下来. 1.创建Document对象,开始解析web页面.解析H ...

  9. 异步加载js

    //异步加载js function loadScript(url,callback){ var script = document.createElement("script"); ...

随机推荐

  1. MATLAB——神经网络sim仿真函数

  2. Intel支持八九代酷睿的B365芯片组将登场亮相

    不久前,Intel悄然推出了新款芯片组B365,看起来像是B360的升级版,但事实上二者并无太大关系. 根据最新曝料,基于B365芯片组的主板将在1月16日登场亮相,支持八九代酷睿. 同时获悉,B36 ...

  3. linux源码分析 - 进程

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 最近在回想一些知识点的时候,觉得对进程这一块有些模糊,特别写一篇随笔对进程信息进行巩固和复习. 程序和进程 以我 ...

  4. Tensorflow[架构流程]

    1. tensorflow工作流程 如官网所示: 根据整体架构或者代码功能可以分为: 图1.1 tensorflow架构 如图所示,一层C的api接口将底层的核运行时部分与顶层的多语言接口分离开. 而 ...

  5. Maven-SSM项目pom.xml配置以及springmvc配置以及mybatis配置及web.xml配置

    一.Maven本地仓库的pom.xml配置 (全部是mysql数据库) <project xmlns="http://maven.apache.org/POM/4.0.0" ...

  6. Luogu2612 ZJOI2012 波浪 DP

    传送门 花掉了自己用来搞学科的时间做了这道题-- 一道类似的题:Here 考虑拆开绝对值计算贡献.那么我们对于\(1\)到\(N\)的排列,从小到大地将插入它们插入排列中. 假设我们现在计算到了数\( ...

  7. 获取2个集合List<T>的共同元素

    获取2个集合List<T>的共同元素,循环2个集合,然后比对. class Bj { public void GetIntersect() { , , , , , , }; , , , , ...

  8. .NET Core 3.0 跟踪

    Preview1: https://blogs.msdn.microsoft.com/dotnet/2018/12/04/announcing-net-core-3-preview-1-and-ope ...

  9. HDU 3400

    一道很适合练习三分的题目三分套三分强不强 题意:给你平面上两条平行线段\(AB\)和\(CD\),一个人要从\(A\)走到\(D\),他在线段\(AB\)上的速度为\(P\),在\(CD\)上的速度为 ...

  10. 事务,acid,cap,paxos随笔

    事务ACID四个特性: A:原子性(Atomicity)C:一致性(Consistency)I:隔离性(Isolation)D:持久性(Durability) 原子性:语句要么全执行,要么全不执行,是 ...