探真无阻塞加载JavaScript脚本,有何隐藏的秘密令人惊奇?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1803个文字,预计阅读时间需要8分钟。
下面的图片是我在使用Firefox和Chrome浏览器访问百度首页时记录的HTTP请求:
Firefox:- 下面的图片是Firefox:+ 在浏览器百度首页前,我都将浏览器的缓存全部清理掉,让这个场景尽可能接近第一次访问百度首页。
Chrome:- 下面的图片是Chrome:+ 在浏览器百度首页前,我都将浏览器的缓存全部清理掉,让这个场景尽可能接近第一次访问百度首页。
下面的图片是我使用firefox和chrome浏览百度首页时候记录的anydomain.com/A.js"; document.getElementByTagName("head")[0].appendChild(scriptElem);
Script Defer
原生方案。利用defer属性来防止脚本阻塞。
代码如下:
<script defer src="A.js"></script>
不过许多浏览器不支持该属性。
document.write Script Tag
动态写脚本的另一种方案,不过只在IE中是并行下载的。
代码如下:
document.write("<script type='text/javascript' src='A.js'></script>");
script defer和document.write Srcipt Tag不是跨浏览器的方案这里不推荐。
页面嵌套iframe方案我没有详述,原因是我现在很讨厌iframe,iframe是dom元素里开销最大的元素,有它就意味着慢,而且我最近碰到一个生产问题就是iframe引起,原因就是对iframe跨域造成,iframe跨域以后,父窗体和子窗体代码就不能互访了,而且iframe写法的不正确(写的很类似跨站脚本挟持)还会导致浏览器启动默认的安全机制,最终出现用户无法正常使用页面的情况,所以我也不推荐使用iframe。
xhr eval也是我不会去使用的方式,因为它用eval命令,而eval的使用常常会为黑客留下破坏你网站的漏洞。
因此最好的方案就是xhr 注入和script dom element了,这两个方案不存在浏览器兼容问题,而且后者还能跨域,不过跨域的选择也是要谨慎的,跨域脚本也会带来隐形的安全风险,不管怎么说这两个方案使用场景基本上可以包括所有阻塞脚本加载的场景。
注意:无阻塞加载脚本的核心技术就是动态的创建script的dom节点。
无阻塞脚本加载技术还有个好处就是,那些和页面展示无关的脚本无须非要放在onload事件里执行,它随时随地可以运行简直就是完美。
不过无阻塞脚本有个很大的隐患,这个隐患是很多会使用无阻塞脚本技术的程序员都会忽视的问题,这个问题就是无阻塞脚本很容易产生“变量未定义”的问题,这个问题的本质就是无阻塞脚本会破坏js脚本加载顺序的问题,当某个脚本依赖于另一个脚本时候,而另一个脚本又没有加载执行完毕,最后就会产生“变量未定义”的问题,例如jQuery没有提前加载,因此使用$时候提示$变量未定义。
那么我们该如何解决这个问题了,我们的思路就是让那些依赖于无阻塞加载的脚本的js代码在脚本加载完毕后才会执行,我们需要一个办法将无序的脚本加载变得有序,上面我推荐的两种方法都是使用dom技术创建script节点,然后将该节点加入到文档的head头部,对于script节点,在非ie浏览器下有一个onload事件,该事件会在script加载完毕后才会执行,ie浏览器下有onreadystatechange事件,而ie下script的dom节点有一个readystate属性,它的取值如下:
1.uninitialized(未初始化):对象存在尚未初始化;
2.loading(正在加载):对象正在加载数据;
3.loaded(加载完毕):对象数据加载完成
4.interactive(交互):可以操作对象,但是还没有完全加载;
5.complete(完成):对象已经加载完毕。
具体用法如下所示:
scriptNode.onreadystatechange = function(){ if (scriptNode.readystate == 'complete'){// todo......} }
这个做法就是为dom加载定义了一个回调函数,当dom加载完毕后回调函数才会执行,这样就解决了代码执行顺序的问题了。
另外还有一个方式就是使用setTimeout,具体使用就是定义一个轮询,判断需要使用的变量是否存在,如果不存在,就继续轮询,如果变量存在则停止轮询,代码模式如下所示:
代码如下:
function lunxun(){ if ("undefined" == typeof(XXXX)){ setTimeout(lunxun,300); }else{ ftn(); } } lunxun();
无阻塞脚本的好处就是不会阻塞UI的执行,也不会影响其他同步js代码的执行,不过无阻塞脚本改变了脚本的加载顺序,所以在使用无阻塞脚本时候一定要更加注意脚本之间的依赖关系,保证整个页面的脚本都能正常执行。
在以前的文章里我多次提到了js的模块加载技术,时下流行的模块加载技术有进口货requirejs和国产货seajs,使用这些技术,我们会发现js文件加载都是按模块加载的,也就是说你页面定义了多少个js模块,那么这个页面就有多少个js文件,刚开始使用它们时候我很诧异,按照前端优化原则http请求越少越好,为什么先进的模块技术却会让js文件变得更多了,接着我分析了下它们加载js的请求,终于明白了,它们都使用的无阻塞脚本加载技术,即使用script节点方式加载脚本,这样就很容易屏蔽js带来的阻塞问题了。
上面的实例中我使用script节点将脚本都是嵌入到head节点里,这个似乎和将脚本置于html文档末尾的原则不同,这个是不是需要改进了,答案是不需要改进,将脚本置于文档末尾目的是为了避免js的阻塞,而我们使用无阻塞脚本了,这个问题不是解决了吗?所以代码置于head标签还是html文档底部也就无关紧要了。
最后我要纠正一个错误的观点,页面加载的总时间是衡量页面加载快捷的标准吗?答案是,的确是个标准,但是不是最精确的标准,页面同步阻塞加载的时间才是衡量页面加载效率的准确标准,非阻塞脚本加载可能会增加整个页面加载的时间,但是它可以减少页面阻塞加载的时间,而页面阻塞才是影响用户体验的元凶,页面优化最重要的关注点就是你所看到的的东西要加载的更加快。
无阻塞脚本可以分割外部脚本的下载和执行操作,这是程序员使用的hack技术,它很酷,但是会导致程序的复杂度增加,可读性下降,所以它应该是web前端架构师的技术,日常开发我们要慎用它。
本文共计1803个文字,预计阅读时间需要8分钟。
下面的图片是我在使用Firefox和Chrome浏览器访问百度首页时记录的HTTP请求:
Firefox:- 下面的图片是Firefox:+ 在浏览器百度首页前,我都将浏览器的缓存全部清理掉,让这个场景尽可能接近第一次访问百度首页。
Chrome:- 下面的图片是Chrome:+ 在浏览器百度首页前,我都将浏览器的缓存全部清理掉,让这个场景尽可能接近第一次访问百度首页。
下面的图片是我使用firefox和chrome浏览百度首页时候记录的anydomain.com/A.js"; document.getElementByTagName("head")[0].appendChild(scriptElem);
Script Defer
原生方案。利用defer属性来防止脚本阻塞。
代码如下:
<script defer src="A.js"></script>
不过许多浏览器不支持该属性。
document.write Script Tag
动态写脚本的另一种方案,不过只在IE中是并行下载的。
代码如下:
document.write("<script type='text/javascript' src='A.js'></script>");
script defer和document.write Srcipt Tag不是跨浏览器的方案这里不推荐。
页面嵌套iframe方案我没有详述,原因是我现在很讨厌iframe,iframe是dom元素里开销最大的元素,有它就意味着慢,而且我最近碰到一个生产问题就是iframe引起,原因就是对iframe跨域造成,iframe跨域以后,父窗体和子窗体代码就不能互访了,而且iframe写法的不正确(写的很类似跨站脚本挟持)还会导致浏览器启动默认的安全机制,最终出现用户无法正常使用页面的情况,所以我也不推荐使用iframe。
xhr eval也是我不会去使用的方式,因为它用eval命令,而eval的使用常常会为黑客留下破坏你网站的漏洞。
因此最好的方案就是xhr 注入和script dom element了,这两个方案不存在浏览器兼容问题,而且后者还能跨域,不过跨域的选择也是要谨慎的,跨域脚本也会带来隐形的安全风险,不管怎么说这两个方案使用场景基本上可以包括所有阻塞脚本加载的场景。
注意:无阻塞加载脚本的核心技术就是动态的创建script的dom节点。
无阻塞脚本加载技术还有个好处就是,那些和页面展示无关的脚本无须非要放在onload事件里执行,它随时随地可以运行简直就是完美。
不过无阻塞脚本有个很大的隐患,这个隐患是很多会使用无阻塞脚本技术的程序员都会忽视的问题,这个问题就是无阻塞脚本很容易产生“变量未定义”的问题,这个问题的本质就是无阻塞脚本会破坏js脚本加载顺序的问题,当某个脚本依赖于另一个脚本时候,而另一个脚本又没有加载执行完毕,最后就会产生“变量未定义”的问题,例如jQuery没有提前加载,因此使用$时候提示$变量未定义。
那么我们该如何解决这个问题了,我们的思路就是让那些依赖于无阻塞加载的脚本的js代码在脚本加载完毕后才会执行,我们需要一个办法将无序的脚本加载变得有序,上面我推荐的两种方法都是使用dom技术创建script节点,然后将该节点加入到文档的head头部,对于script节点,在非ie浏览器下有一个onload事件,该事件会在script加载完毕后才会执行,ie浏览器下有onreadystatechange事件,而ie下script的dom节点有一个readystate属性,它的取值如下:
1.uninitialized(未初始化):对象存在尚未初始化;
2.loading(正在加载):对象正在加载数据;
3.loaded(加载完毕):对象数据加载完成
4.interactive(交互):可以操作对象,但是还没有完全加载;
5.complete(完成):对象已经加载完毕。
具体用法如下所示:
scriptNode.onreadystatechange = function(){ if (scriptNode.readystate == 'complete'){// todo......} }
这个做法就是为dom加载定义了一个回调函数,当dom加载完毕后回调函数才会执行,这样就解决了代码执行顺序的问题了。
另外还有一个方式就是使用setTimeout,具体使用就是定义一个轮询,判断需要使用的变量是否存在,如果不存在,就继续轮询,如果变量存在则停止轮询,代码模式如下所示:
代码如下:
function lunxun(){ if ("undefined" == typeof(XXXX)){ setTimeout(lunxun,300); }else{ ftn(); } } lunxun();
无阻塞脚本的好处就是不会阻塞UI的执行,也不会影响其他同步js代码的执行,不过无阻塞脚本改变了脚本的加载顺序,所以在使用无阻塞脚本时候一定要更加注意脚本之间的依赖关系,保证整个页面的脚本都能正常执行。
在以前的文章里我多次提到了js的模块加载技术,时下流行的模块加载技术有进口货requirejs和国产货seajs,使用这些技术,我们会发现js文件加载都是按模块加载的,也就是说你页面定义了多少个js模块,那么这个页面就有多少个js文件,刚开始使用它们时候我很诧异,按照前端优化原则http请求越少越好,为什么先进的模块技术却会让js文件变得更多了,接着我分析了下它们加载js的请求,终于明白了,它们都使用的无阻塞脚本加载技术,即使用script节点方式加载脚本,这样就很容易屏蔽js带来的阻塞问题了。
上面的实例中我使用script节点将脚本都是嵌入到head节点里,这个似乎和将脚本置于html文档末尾的原则不同,这个是不是需要改进了,答案是不需要改进,将脚本置于文档末尾目的是为了避免js的阻塞,而我们使用无阻塞脚本了,这个问题不是解决了吗?所以代码置于head标签还是html文档底部也就无关紧要了。
最后我要纠正一个错误的观点,页面加载的总时间是衡量页面加载快捷的标准吗?答案是,的确是个标准,但是不是最精确的标准,页面同步阻塞加载的时间才是衡量页面加载效率的准确标准,非阻塞脚本加载可能会增加整个页面加载的时间,但是它可以减少页面阻塞加载的时间,而页面阻塞才是影响用户体验的元凶,页面优化最重要的关注点就是你所看到的的东西要加载的更加快。
无阻塞脚本可以分割外部脚本的下载和执行操作,这是程序员使用的hack技术,它很酷,但是会导致程序的复杂度增加,可读性下降,所以它应该是web前端架构师的技术,日常开发我们要慎用它。

