关于js的执行与加载

js在浏览器中性能,可以认为是开发者所面临的最严重的可用性问题了,这个问题因为js的阻塞特性变得很复杂,也就是说浏览器在执行js代码时,不能同时做其他任何事情。事实上,多数浏览器使用单一进程来处理用户界面刷新和js脚本的执行,所以只能同一时刻做一件事,js的执行过程耗时越久,浏览器等待响应的时间就越长。

简单的说,这意味着<script>标签每次出现都霸道地让页面等待脚本的解析和执行。无论当前的js代码时内嵌还是外链接,页面的下载和渲染都必须停下来等脚本的执行完成。这是页面生存周期中的必要环节,因为脚本执行过程中可能会修改页面内容。一个典型例子就死document.write().我们看到的广告就是这么搞的。

脚本的位置

html4规范指出<script>标签可以放在html文档的<head>或<body>中,并允许出现多次。按照惯例,<script>标签用来加载出现在css加载的<link>标签后。理论上来说,把样式和行为有关的脚本放在一起,并先加载它们,这样做有助于页面的渲染和交互的正确性。

但是,这样存在十分严重的性能问题,在<head>标签中加载js文件,由于脚本会阻塞页面的渲染,直到它们全部下载并执行完成后,页面的渲染的才会执行。要知道,浏览器在解析到<body>标签之前,不会渲染页面的任何内容,把脚本放在页面顶部会导致明显的延迟,会有明显的白屏时间,用户无法浏览内容,也无法与页面进行交互。瀑布图可以帮我们更清楚地理解性能发生的原因。因此js要放在<body>标签的底部。

组织脚本

每一个<script>标签初始下载时都会阻塞页面渲染,所以减少页面包含的<script>标签数量有助于改善这一情况,这不仅仅是针对外链脚本,内链脚本的数量也要限制,这个问题在处理外链脚本文件时略有不同,因为http请求还会带来额外的性能开销,因此下载单个100kb的文件将比下载四个25kb的文件更快,也就是说,减少页面中脚本文件数量将会改善性能。

通常一个大型网站或网络应用需要依赖数个js文件,我们可以把多个文件合并成一个,这样就只需引用一个<script>标签了。文件合并可以利用现在的很多构建工具,grunt,gulp等,都很方便。

无阻塞的脚本

js倾向于阻止浏览器的某些处理过程,如http请求和用户界面更新,这是开发者所面临的最显著的性能问题。减少js文件大小并限制http请求仅仅是创建响应迅速的Web应用的第一步,web应用的功能越来越强大丰富,所需要的脚本代码也就越多,所以精简代码并不总是可行,尽管下载单个较大的js文件只产生一次http请求,却会锁死浏览器一大段时间,这样显然不是良好的用户体验,为避免这种情况,我们需要的是向页面中逐步加载js文件,这样做从某种程度上不会阻塞浏览器。

无阻塞脚本的秘诀在于,在页面加载完后才加载js代码,用专业术语说,这意味着window对象的load事件触发后再下载脚本,有很多方式可以实现这一效果。

《1》延迟的脚本

html4为<script>标签定义了一个扩展属性,defer。defer属性指明本元素所含的脚本不会修改dom,因此代码可以安全的延迟执行。这个属性目前已经被所有的主流浏览器支持了。另外说说HTML5 中引入的async属性,用于异步加载脚本。async和defer的相同点是采用并行下载,在下载的过程不会产生阻塞,区别在于执行的时机,async是加载完成后自动执行,而defer需要等待页面完成后才执行。

带有defer属性的<script>标签可以放置在文档的任何位置,对应的js文件将在解析到<script>标签时开始下载,但不会执行,直到dom加载完成后(onload事件被触发前)因此这类文件可以与页面中的其他资源并行下载。

<script type=‘type/javascript ‘ src="xiaoai.js" defer></script>

示例:

<html>

<head>

<title> script defer</title>

</head>

<body>

<script defer>

alert(1);

</script>

<script>

alert(2);

</script>

<script>

window.onload=function(){

alert(3);

}

</script>

</body>

</html>

这段代码弹出三次提示框,若你的浏览器支持defer,弹出的顺序为2,1,3;而不支持defer的的浏览器则是1,2,3。请注意,带有defer属性的浏览器不是跟在第二个执行,而是在onload事件之前执行。

《2》动态脚本

由于DOM的存在,你可以用js创建HTML中几乎所有内容。其原因在于,<script>元素与页面其他元素并无差异:都能通过DOM进行引用,都能在文档中移动,删除或是被创建。用标准的DOM方法可以很容易的创建一个新的<script>元素:

var script=document.createElement(‘script’);

script.type="text/javascript";

script.src="file1.js";

document.getElementsByTagName(‘head‘)[0].appendChild(script);

这个新创建的<script>元素加载了file1.js文件。文件在该元素被添加到页面时开始下载。这种技术的重点在于:无论在何时启动下载,文件的下载和执行过程不会阻塞页面的其他进程。你甚至可以将代码放到页面<head>区域而不会影响页面其他部分(用于下载文件的http链接本身的影响除外)。

另外,要注意,把新创建的<script>标签添加到<head>标签里比添加到<body>里更保险,尤其是在页面加载过程中执行代码时更是如此。当<body>中的内容没有加载完成时,IE会抛出“操作已终止”的错误信息。

使用动态脚本节点下载文件时,返回的代码通常会立即执行(除了Firefox和opera,它们会等待此前所有动态脚本节点执行完毕)。当脚本‘自执行’时,这种机制运行正常。但是当代码只包含供页面其他脚本调用的接口时,就会有问题。在这种情况下,你必须跟踪并确保脚本下载完成且准备就绪。这可以用动态<script>节点触发的事件来实现。

Firefox,opera,Chrome和Safari以上的版本会在<script>元素接收完成时触发一个load事件。因此可以通过侦听此事件来获得脚本加载完成时的状态;

var script=document.createElement(‘script‘)

script.type=‘text/javascript‘;

script.onload=function(){

alert("script loaded");

};

script.src=‘file2.js‘;

document.getElementByTagName(‘head‘)[0].appendChild(script);

IE支持另一种实现方式,它会触发一个readyStatechange事件。<script>元素提供一个readyState属性,它的值在外链文件的下载过程的不同阶段会发生变化,该属性有五种取值:

“uninitialized”   初始状态

“loading” 开始下载

“loaded” 下载完成

“interactive” 数据完成下载但尚不可用

“complete” 所有数据已准备就绪

微软的相关文档表明,<script>元素生命周期中,并非readyState的每个取值都会被用到,实际应用中,最有用的两个状态就是“loaded”和“complete”。Ie在标识最终状态时的值并不一致,有时<script>元素达到“loaded”状态而从不会到达“complete”,有时候直接跳到“complete”而不经过“loaded”,使用这个属性时最靠谱的方式是同时检查这两个状态,只要其中任何一个触发,就删除事件处理器(以确保不会处理两次)。

var script=document.createElement(‘script‘)

script.type="text/javascript";

script.onreadystatechange=function(){

if(script.readyState=="loaded"||script.readyState=="complete"){

script.onreadystatechange=null;

alert(‘script loaded‘);

};

script.src=‘file3.js‘;

document.getElementsByTagName(‘head‘)[0].appendChild(script);

}

以上是针对IE的动态加载js文件方法。

我们需要一个兼容各浏览器的动态加载js文件的方法,下面是一个函数封装了标准和IE特有的实现方法

function loadscript(url,callback)

{

var script=document.createElement(‘script‘)

script.type=‘text/javascript‘;

if(script.readyState){//IE

script.onreadystatechange=function(){

if(script.readyState=="loaded"||script.readystate=="complete"){

script.onreadystatechange=null;

callback();

}else{//其他浏览器

script.onload=function()

{

callback();

};

}

script.src=url;

document.getElementsByTagName(‘head‘)[0].appendChild(script);

}

这个函数接收两个参数:JavaScript文件的URL和完成加载后的回调函数。函数中使用了特征检测来决定脚本处理过程中监听哪个事件。最后一步是给src属性赋值,然后将<script>元素添加到页面。loadscript()函数用法如下

loadscript("file1.js",function(){

alert(‘file is loaded’);

});

如果需要的话,你可以动态加载尽肯能多的jswenjian 到页面上,但一定要考虑清楚文件的加载顺序。在所有的主流浏览器中,只有Firefox和opera能保证脚本会按照你指定的顺序执行,其他浏览器会按照从服务端返回的顺序下载和执行代码。因此可以通过下面的串联方式以确保下载顺序。

loadscript(‘file1.js’,function(){

loadscript(‘file2.js‘,function(){

loadscript("file3.js",function(){

alert(‘all file is loaded‘);

});

});

});

下载顺序为 file1,file2,file3。

如果多个文件的下载顺序很重要,更好的做法是把他们按正确的顺序合并成一个文件。下载这个文件就会获得所有的代码(由于这个过程是异步的,因此文件大点没关系)

总而言之,动态脚本加载凭借着它在跨浏览器兼容性和易用的优势,成为最通用的无阻塞加载js的解决方案。

《3》XMLhttpRequest 脚本注入

另一种无阻塞加载脚本的方法是使用XMLHttpRequest(XHR)对象获取脚本并注入页面中。

此技术胡创建一个XHR对象,然后用它下载JavaScript文件,最后通过创建动态<script>元素将代码注入到页面中。

var xhr =new XMLHttpRequest();

xhr.open(‘get‘,‘file1.js‘,true);

xhr.onreadystatechange=funcition(){

if(xhr.readyState==4){

if(xhr.status>=200&&xhr.status<300||xhr.status==304){

var script=document.creat.createElement(‘script‘);

script.type="text/javascript";

script.text=xhr.responseText;

document.body.appendChild(script);

}

}

};

chr.send(null);

这段代码发送一个GET请求获取file.js文件。事件处理函数onreadychange检查readyState是否为4,同时检验http状态码是否有效(2xx代表有效响应,304代表从缓存中读取)。如果收到了有效响应,就会创建一个<script>元素,设置该元素的text属性为从服务器接收到的resposeText。这样实际上是创建一个带有内联脚本的<script>标签。一旦新创建的<script>元素被添加到页面,代码就会立刻执行然后准备就绪。

这种方法的优点是:你可以下载JavaScript代码但不立即执行。由于代码是在<script>标签之外返回的,因此它下载后不会自动执行,这使得你可以把脚本执行推迟到你准备好的时候。另一个优点是,同样的代码在所有浏览器都能正常工作。

这种方法的局限性是:js文件必须与所请求的页面处于相同的域,这意味着js文件不能从cdn下载。因此,大型的web应用通常不会采用XHR脚本注入技术。

小结:

   管理浏览器中js代码是个棘手的问题,因为代码执行过程会阻塞浏览器的其他进程,比如用户界面绘制。每次遇到<script>标签,页面都必须停下来等待所有的js代码下载并执行,然后恢复处理。尽管如此,还是有几种方法能减少js对性能的影响:

1.<body>闭合标签之前,将所有<script>标签放在页面底部。这能确保脚本执行前页面已经完成渲染。

2.合并脚本。页面中<script>标签越少,加载也就越快,响应也更迅速。无论外链文件还是内嵌的脚本都是如此。

3.有多种无阻塞下载js的方法:

---<script defer>

---使用动态创建的<script>元素来下载并执行代码

---使用XHR对象下载js代码并注入页面中。

时间: 2024-07-29 22:18:23

关于js的执行与加载的相关文章

head.js让网站并行加载但顺序执行JS

http://headjs.com/ 并行加载JS,但是执行的时候却按顺序执行,提高网站速度 <script src="js/head.min.js"></script> <script type="text/javascript"> head.js("js/jquery-1.6.1.min.js","js/jquery.validate.min.js","js/my_valida

JS实现图片预加载无需等待

网站开发时经常需要在某个页面需要实现对大量图片的浏览;用javascript来实现一个图片浏览器,让用户无需等待过长的时间就能看到其他图片 网站开发时经常需要在某个页面需要实现对大量图片的浏览,如果考虑流量的话,大可以像pconline一样每个页面只显示一张图片,让用户每看一张图片就需要重新下载一下整个页面.不过,在web2.0时代,更多人愿意用javascript来实现一个图片浏览器,让用户无需等待过长的时间就能看到其他图片. 知道了一张图片的地址,需要把它在一个固定大小的html容器(可以是

Sea.js 是一个模块加载器

1 模块定义define define(function(require,exports,module){ //require 引入需要的模块如jquery等 //var $ = require('./jquery'); //exports可以把方法或属性暴露给外部 exports.name = 'hi'; exports.hi = function(){ alert('hello'); } //module提供了模块信息 }); 2 使用定义好的模块seajs.use <!doctype ht

js与AMD模块加载

目的: 了解AMD规范与CMD规范,写一个模块加载器雏形. 基本概念: AMD是异步模块定义规范,而CMD是通用模块定义规范.其他的还有CommonJS Modules规范. 对于具体的规范,可以参考: https://github.com/amdjs/amdjs-api/wiki/AMD AMD规范 https://github.com/seajs/seajs/issues/242 CMD规范 http://www.zhihu.com/question/20351507/answer/1485

js 利用 ajax 加载 js ,显示加载进度 ,严格按照js的顺序先后加载到页面

js 利用 ajax 加载 js ,显示加载进度 ,严格按照js的顺序先后加载到页面 , 做手机端开发时,发现一个问题,有些浏览器,在网速比较慢的情况下,js文件没有加载完,后续的调用已经开始调用了,导致出错.后来使用此法,保证了任何时候都完全是按照js的先后顺序执行的. 源码: /** * js 利用 ajax 加载 js ,显示加载进度 ,严格按照js的顺序先后加载到页面 * 原理:利用 ajax 异步多线程快速加载, 每个文件加载完成后存入到加载完成数组中, * 显示到页面时完全按传入的顺

js中全局变量修改后的值不生效【jsp页面中各个js中内容的加载顺序】

一个老项目中,一个jsp文件中有很多个js文件, 现在要在页面上的一个地方判断一个状态,因为一直找不到原来是在哪里修改的那个状态,所以决定不找了,而是在比较靠前引入的一个js中定义一个全局变量,然后在这个js的 $(function(){}} 方法中通过一个ajax向后台获取正确的状态,然后在所有的js都加载完之后根据全局变量的值的状态来修改页面上的逻辑. 但是发现,全局变量被一个外部js修改赋值后,我在jsp页面的最下面居然取不到修改过的值. 请教同事才发现原来原因是 任何一个js文件中的 $

页面性能优化-原生JS实现图片懒加载

在项目开发中,我们往往会遇到一个页面需要加载很多图片的情况.我们可以一次性加载全部的图片,但是考虑到用户有可能只浏览部分图片.所以我们需要对图片加载进行优化,只加载浏览器窗口内的图片,当用户滚动时,再加载更多的图片.这种加载图片的方式叫做图片懒加载,又叫做按需加载或图片的延时加载.这样做的好处是:1.可以加快页面首屏渲染的速度:2.节约用户的流量. 一.实现思路 1.图片img标签自定义一个属性data-src来存放真实的地址. 2.当滚动页面时,检查所有的img标签,判断是否出现在事业中,如果

原生js实现上拉加载

原生js实现上拉加载:https://www.cnblogs.com/xinsir/p/10314694.html 原生js实现上拉加载其实超级简单,把原理整明白了你也会,再也不用去引一个mescroll啦~ 好了,废话不多说,开始进入正题:上拉加载是怎么去做的,原理就是监听滚动条滑到页面底部,然后就去做一次请求数据.那么只要我们对滚动监听即可. 现在,让我们来看代码: window.onscroll = function () { var scrollH = document.document

nginx设置反向代理后,页面上的js css文件无法加载

问题现象: nginx配置反向代理后,网页可以正常访问,但是页面上的js css文件无法加载,页面样式乱了. (1)nginx配置如下: (2)域名访问:js css文件无法加载: (3)IP访问:js css文件可以正常加载: 解决方法: nginx配置文件中,增加如下配置: location ~ .*\.(js|css)$ { proxy_pass http://127.0.0.1:8866; } 原因分析: 反向代理的路径下找不到文件,需要单独指定js css文件的访问路径.