实现高性能JavaScript之执行与加载


Posted in Javascript onJanuary 30, 2016

浏览器在处理HTML页面渲染和JavaScript脚本执行的时候是单一进程的,所以在当浏览器在渲染HTML遇到了<script>标签会先去执行标签内的代码(如果是使用src属性加载的外链文件,则先下载再执行),在这个过程中,页面渲染和交互都会被阻塞。

...虽然会有阻塞,但还是有几招可以减少JavaScript对性能的影响的。

1.script标签的位置

当<script>出现在<head>中的时候,比如:

<head>
  <script type="text/javascript" src="js1.js"></script>
  <script type="text/javascript" src="js2.js"></script>
  <script type="text/javascript" src="js3.js"></script>
</head>

这种加载多个js文件的时候,浏览器就会因先下载执行js代码而阻塞页面渲染从而出现白屏页面(浏览器解析到<body>标签之前,不会渲染页面任何内容),没法预览也没法交互,很差劲的用户体验。

注意:

现代浏览器支持资源并行下载,只限于<script>下载外部资源的时候不会阻塞其他<script>标签,但会阻塞其他资源的下载。
下载JavaScript资源是异步的,但是执行JavaScript代码的时候仍是同步的,同样会造成阻塞。
所以把<script>后置到<body>标签的底部,保证执行脚本之前已完成页面渲染,是一种比较常用的JavaScript优化手段。

2.合并多个script标签

浏览器解析HTML时遇到<script>都会因为执行脚本而有一定的延迟,对于有src属性的外链则<script>更加,多HTTP请求则会带来更多的性能开销,尽量减少这种延迟,也是一种优化手段,可以合并多个js文件来减少HTTP请求的次数,减少三次握手的次数和多余的HTTP头传输,降低响应时间提高用户体验。网上有许多合并js的方案以及工具,在这不叙述了。

3.使用无阻塞下载JavaScript的方法

  1. 使用script标签的defer和async属性
  2. 使用动态创建的script标签来下载执行JavaScript代码
  3. 使用XHR对象下载JavaScript代码并注入页面

3.1.使用script标签的defer和async属性

async和defer属性都是用于异步加载js文件,期间不会才生阻塞浏览器其他进程,区别在于async是加载完之后自动执行,而defer需要等到页面加载之后再执行,需要注意的一点是这两个属性必须在有src属性的<script>标签中(外链脚本)才有效。下面是demo:

<!DOCTYPE html>
<html>
<head>
  <title>defer example</title>
</head>
<body>
  <script type="text/javascript" src="defer.js" defer></script>
  <script>
    alert("script");
  </script>
  <script>
    window.onload= function(){
      alert("load");
    }
  </script>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
  <div class="demo">defer demo</div>
</body>
</html>

//defer.js的文件下只有alert("defer");一行代码
async的例子也是相同的页面结构,这里就不摆例子了,可以戳下面的链接。
defer example的链接戳这里!
async example的链接戳这里!

虽然页面结构一样,但不一样的是

打开defer.html依次看到是: 弹出"script"的alert框=>页面渲染出文字=>弹出"defer"的alert框=>弹出"load"的alert框
打开async.html依次看到是: 弹出"script"的alert框=>弹出"async"的alert框=>页面渲染出文字=>弹出"load"的alert框

3.2.使用动态创建的script标签来下载执行JavaScript代码

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "file.js";
document.getElementByTagName("head")[0].appendChild(script);

file.js在script元素添加到页面时就启动下载,使用这种方式的优势在于file.js的下载和执行不会阻塞页面其他进程。

从demo上可以明显的看出动态加载方式可以在alert框弹出之前先看到页面上的文字,但是普通的方式只有在alert框弹出之后才可以看到页面上的文字。

我们可以封装一个跨浏览器的读取script脚本并动态创建script标签的函数:

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);
}

这类动态加载脚本的方法兼容性好,也比较简单,是一种常用的无阻塞解决方案。

3.3.使用XHR对象下载JavaScript代码并注入页面

另一种无阻塞加载脚本的方式是使用XMLHttpRequest(XHR)对象获取脚本并注入页面中。
这种技术会先创建一个XHR对象,然后用他下载JavaScript文件,最后通过常见动态<script>元素将代码注入页面中。

var xhr = new XMLHttpRequest();
xhr.open("get","file.js",true);
xhr.onreadystatechange = function(){
  if(xhr.readyState===4){
    if(xhr.status>=200&&xhr.status<300||xhr.status==304){
      var script = document.createElement("script");
      script.type = "text/javascript";
      script.text = xhr.responseText;
      document.body.appendChild(script);
    }
  }
}

以上代码发送GET请求file.js文件,onReadyStateChange检测readyState是否为4(4表示请求完成)和HTTP状态吗是否有效(200表示有效响应,304表示读取缓存)。判断响应有效之后,就动态创建一个<script>标签,内容就是服务器接收到的responseText。

这种方法的优点以及缺点:

优点:下载JavaScript代码可以不立即执行,且兼容性好适合所有主流浏览器。
缺点:JavaScript文件必须与所请求页面处于同一个域,这种情况下JavaScript文件不能从CDN下载,不适合大型的Web应用。

4.一种推荐的无阻塞方案

如果页面有大量的JavaScript代码需要添加,可以先在页面中去外链之前我们封装好的动态读取script脚本的函数loadScript,然后再使用它去加载其他所需脚本,例如:

<script type="text/javascript" src="loader.js"></script>
<script type="text/javascript">
  loadScript("file.js",function(){
    //do something
  });
</script>

这样只需在第一个<script>下载比较精简的loader.js文件时对页面有些许影响,之后的<script>并不会有太多影响。

Javascript 相关文章推荐
javascript form 验证函数 弹出对话框形式
Jun 23 Javascript
jquery tab插件精简版分享
Sep 10 Javascript
jQuery.buildFragment使用方法及思路分析
Jan 07 Javascript
兼容IE和Firefox火狐的上下、左右循环无间断滚动JS代码
Apr 19 Javascript
javascript常用方法汇总
Dec 02 Javascript
JavaScript中string转换成number介绍
Dec 31 Javascript
利用Javascript实现BMI计算器
Aug 16 Javascript
JS 学习总结之正则表达式的懒惰性和贪婪性
Jul 03 Javascript
基于Bootstrap实现城市三级联动
Nov 23 Javascript
vue父组件向子组件传递多个数据的实例
Mar 01 Javascript
webpack4实现不同的导出类型
Apr 09 Javascript
JavaScript Event Loop相关原理解析
Jun 10 Javascript
深入探秘jquery瀑布流的实现
Jan 30 #Javascript
深入分析Javascript事件代理
Jan 30 #Javascript
详解javascript实现瀑布流列式布局
Jan 29 #Javascript
详解javascript实现瀑布流绝对式布局
Jan 29 #Javascript
理解Javascript文件动态加载
Jan 29 #Javascript
JavaScript操作select元素和option的实例代码
Jan 29 #Javascript
JavaScript学习总结之JS、AJAX应用
Jan 29 #Javascript
You might like
咖啡是不是喝了会上瘾?咖啡是必须品吗!
2021/03/04 新手入门
Jquery 表单取值赋值的一些基本操作
2009/10/11 Javascript
js 变量类型转换常用函数与代码[比较全]
2009/12/01 Javascript
js获取URL的参数的方法(getQueryString)示例
2013/09/29 Javascript
js 实现菜单左右滚动显示示例介绍
2013/11/21 Javascript
使用JavaScript判断图片是否加载完成的三种实现方式
2014/05/04 Javascript
jQuery左右滚动支持图片放大缩略图图片轮播代码分享
2015/08/26 Javascript
微信小程序前端源码逻辑和工作流
2016/09/25 Javascript
JavaScript中的子窗口与父窗口的互相调用问题
2017/02/08 Javascript
详解Vuex管理登录状态
2017/11/13 Javascript
微信小程序3种位置API的使用方法详解
2019/08/05 Javascript
微信小程序实现拼图小游戏
2020/10/22 Javascript
[02:26]2016国际邀请赛8月3日开战 中国军团出征西雅图
2016/08/02 DOTA
python实现进程间通信简单实例
2014/07/23 Python
Python中使用摄像头实现简单的延时摄影技术
2015/03/27 Python
Python中List.index()方法的使用教程
2015/05/20 Python
Python中的左斜杠、右斜杠(正斜杠和反斜杠)
2016/08/30 Python
Python 正则表达式实现计算器功能
2017/04/29 Python
Pycharm代码无法复制,无法选中删除,无法编辑的解决方法
2018/10/22 Python
python 接收处理外带的参数方法
2018/12/03 Python
Python常见数据结构之栈与队列用法示例
2019/01/14 Python
Python 移动光标位置的方法
2019/01/20 Python
python读取指定字节长度的文本方法
2019/08/27 Python
python json 递归打印所有json子节点信息的例子
2020/02/27 Python
python filecmp.dircmp实现递归比对两个目录的方法
2020/05/22 Python
详解python方法之绑定方法与非绑定方法
2020/08/17 Python
CSS3+font字体文件实现圆形半透明菜单具体步骤(图解)
2013/06/03 HTML / CSS
HTML5 canvas基本绘图之绘制线段
2016/06/27 HTML / CSS
竞选学生会主席演讲稿
2014/04/24 职场文书
保护环境建议书100字
2014/05/13 职场文书
五四青年节演讲稿
2014/05/26 职场文书
校园绿化美化方案
2014/06/08 职场文书
端午节寄语2015
2015/03/23 职场文书
2015年度个人业务工作总结
2015/04/27 职场文书
Django实现drf搜索过滤和排序过滤
2021/06/21 Python
JavaWeb实现显示mysql数据库数据
2022/03/19 Java/Android