详谈LABJS按需动态加载js文件


Posted in Javascript onMay 07, 2015

LABjs 是一个很小的 JavaScript 工具,用来根据需要加载 JavaScript 文件,通过使用该工具可以提升页面的性能,避免加载不需用到的 JavaScript 文件,可以实现动态并行加载脚本文件,以及管理加载脚本文件的执行顺序。

简单示例

$LAB
.script("script1.js", "script2.js", "script3.js")
.block(function(){
  // wait for all to load, then do something
  script1Func();
  script2Func();
  script3Func();
});

介绍下LABJS的几个实例:
实例1:

$LAB
  .script("script1.js")
  .script("script2.js")
  .script("script3.js")
  .wait(function(){ // 等待所有script加载完再执行这个代码块
    script1Func();
    script2Func();
    script3Func();
  });

实例2:

$LAB 
  .script({ src: "script1.js", type: "text/javascript" })
  .script("script2.js")
  .script("script3.js")
  .wait(function(){ // 等待所有script加载完再执行这个代码块
    script1Func();
    script2Func();
    script3Func();
  });

实例3:

$LAB
  .script("script1.js", "script2.js", "script3.js")
  .wait(function(){ // 等待所有script加载完再执行这个代码块
    script1Func();
    script2Func();
    script3Func();
  });

实例4:

$LAB
  .script( [ "script1.js", "script2.js" ], "script3.js")
  .wait(function(){ // 等待所有script加载完再执行这个代码块
    script1Func();
    script2Func();
    script3Func();
  });

实例5:

$LAB
  .script("script1.js").wait() // 空的wait()只是确保script1在其他代码之前被执行
  .script("script2.js") // script2 和 script3 依赖于 script1
  .script("script3.js").wait() // 但是script2 和 script3 并不互相依赖,可以并行下载
  .script("script4.js") // script4 依赖于 script1, script2 及 script3
  .wait(function(){script4Func();});

实例6:

$LAB
  .script("script1.js") // script1, script2, and script3 之间没有依赖关系,
  .script("script2.js") // 所以可以任意顺序执行
  .script("script3.js")
  .wait(function(){ // 如果需要,这里当然可以执行javascript函数
    alert("Scripts 1-3 are loaded!");
  })
  .script("script4.js") // 依赖于 script1, script2 及 script3
  .wait(function(){script4Func();});

实例7:

$LAB
  .setOptions({AlwaysPreserveOrder:true}) // 设置每个脚本之间等待
  .script("script1.js") // script1, script2, script3, script4 互相依赖
  .script("script2.js") // 并且并行下载后循序执行
  .script("script3.js")
  .script("script4.js")
  .wait(function(){script4Func();});

实例8:

$LAB
  .script(function(){
    // `_is_IE`的值ie为true ,非ie为false
    if (_is_IE) {
      return "ie.js"; // 如果是ie则这个js会被加载
    }
    else {
      return null; //如果不是ie这个代码就会被略过
    }
  })
  .script("script1.js")
  .wait();

LABjs加载方式

LABjs里的动态加载脚本文件,是指页面的js脚本执行时,通过多种方法去加载外部的js(主要区别于html页面里,通过<script>标签静态加载的脚本)

动态加载脚本的方式有很多,优缺点不一,此处不赘述,有兴趣的童鞋可以参见本文末尾的参考链接 :)。

LABjs里主要使用了三种技巧,分别为Script Element、XHR Injection以及Cache Trick

首先对这三种加载方式进行简单介绍,第四部分再分析LABjs源码实现里面对着三种方式分别的使用场景

Script Element(LABjs默认采用加载方式)

最常见的脚本动态加载方式,优点很多,包括:1、实现简单 2、可跨域 3、不会阻塞其他资源的加载 等

Opera/Firefox(老版本)下:脚本执行的顺序与节点被插入页面的顺序一致

IE/Safari/Chrome下:执行顺序无法得到保证

注意:

新版本的Firefox下,脚本执行的顺序与插入页面的顺序不一定一致,但可通过将script标签的async属性设置为false来保证顺序执行

老版本的Chrome下,脚本执行的顺序与插入页面的顺序不一定一致,但可通过将script标签的async属性设置为false来保证顺序执行

XHR Injection
通过ajax请求加载脚本文件,然后再通过以下方式执行:
eval:常见方式
XHR injection:创建一个script元素,并将请加载的脚本文件的内容注入
主要限制:无法跨域
Cache Trick(强依赖于浏览器的特性实现,不推荐使用)
当你将script元素的type属性设置为浏览器不认识的值,比如”text/cache”、”text/casper”、”text/hellworld”等,不同浏览器的行为如下:
IE/Safari/Chrome(老版本)里:脚本照常加载,但不会执行,假设浏览器没有禁用缓存,加载后的脚本会被浏览器缓存起来,当需要用到 的时候,只需要重新创建个script标签,将type设为正确的值,src指向之前请求的文件url即可(相当于从缓存里读文件)
Opera/Firefox:不加载
备注:
强依赖于浏览器的特性实现,有可能随着浏览器特性实现的改变而失效,不推荐使用
新版本的chrome浏览器,将script元素的type设置为非”text/javascript”,不会再对脚本文件进行加载。

LABjs里关于脚本加载采用方案的判断

忽略技术细节,通过一段伪代码来描述LABjs里面的实现,大致为:
首先判断是否对请求的脚本进行预加载(是否进行预加载的判断条件看伪代码注释);
如进行预加载,再判断浏览器是否支持真正的预加载;如支持真正的预加载,则预加载之;如否,判断请求的脚本是否跟当前页面同域,如实,采用XHR Injection,如否,采用Cache Trick;
如不进行预加载,判断浏览器支不支持script元素的async属性(见伪代码注释),如是,设置async属性,并请求脚本文件;如否,直接通过script元素加载脚本文件;
 

if(ifPreloadScript){  //当请求的脚本文件是否进行预加载:1、需要预加载 2、浏览器支持预加载
  if(supportRealPreloading){  //如果支持真正的预加载
    if(supportPreloadPropNatively){  //支持通过设置script标签的preload属性,实现script的预加载,以及分离加载和执行
                    //Nicholas C. Zakas大神的美好愿望,尚未有浏览器支持:/blog/2011/02/14/separating-javascript-download-and-execution/
      script.onpreload = callback;
      script.newPreload = true;
      script.src = targetUrl;
    }else{
      script.onreadystatechange = callback;  //其实就是指IE浏览器,假设指定了script元素的src属性,IE浏览器里会立即加载
      script.src = targetUrl;  //即使script元素没有被插入页面,callback为预加载后的回调
    }
  }
  else if(inSameDomain){  //非跨域,采用XHR Injection:请求的脚本与当前页面处于同一个域
    xhr = new XMLHttpRequest();  //由于上个判断已经将IE无情地抛弃在这个条件分支之外,所以大胆地用 new XMLHttpRequest()吧
    xhr.onreadystatechange = callback;
    xhr.open("GET",targetUrl);
    xhr.send();
  }
  else{  //最无奈的后招,Cache Trick,新版chromei已经不支持
    script.onload = callback;
    script.type = 'text/cache';  
    script.src = targetUrl;
  }
}else{
  if(canContrlExecutionOrderByAsync){  //如果能够通过script元素的async属性来强制并行加载的脚本顺序执行
                    //kyle大神着力推进的提案,目前已被html5小组接受并放入草案:/Dynamic_Script_Execution_Order#My_Solution
    script.onload = callback;
    script.async = false;  //将script元素的async设为false,可以保证script的执行顺序与请求顺序保持一致
    script.src = targetUrl;
  }
  else{
    script.onload = callback;
    script.src = targetUrl;  
  }
}

实际上,当你在页面创建一个img节点,并将其src指向一个脚本文件,在部分浏览器里同样能够起到文件预加载的作用,那么LABjs的作者是不是没有想到这一点呢?

Javascript 相关文章推荐
javascript下给元素添加事件的方法与代码
Aug 13 Javascript
Javascript中定义方法的另类写法(批量定义js对象的方法)
Feb 25 Javascript
深入理解JavaScript系列(10) JavaScript核心(晋级高手必读篇)
Jan 15 Javascript
扒一扒JavaScript 预解释
Jan 28 Javascript
js实现图片点击左右轮播
Jul 08 Javascript
jQuery mobile类库使用时加载导航历史的方法简介
Dec 04 Javascript
jQuery实现横向带缓冲的水平运动效果(附demo源码下载)
Jan 29 Javascript
Angularjs 设置全局变量的方法总结
Oct 20 Javascript
AngularJS实现ajax请求的方法
Nov 22 Javascript
详解使用mpvue开发github小程序总结
Jul 25 Javascript
vue+axios 前端实现的常用拦截的代码示例
Aug 23 Javascript
使用JQuery自动完成插件Auto Complete详解
Jun 18 jQuery
JavaScript改变CSS样式的方法汇总
May 07 #Javascript
js实现非常简单的焦点图切换特效实例
May 07 #Javascript
jQuery中closest和parents的区别分析
May 07 #Javascript
js实现键盘上下左右键选择文字并显示在文本框的方法
May 07 #Javascript
基于jQuery.Hz2Py.js插件实现的汉字转拼音特效
May 07 #Javascript
JavaScript更改字符串的大小写
May 07 #Javascript
JQUERY简单按钮轮换选中效果实现方法
May 07 #Javascript
You might like
用PHP连mysql和oracle数据库性能比较
2006/10/09 PHP
Zend Framework动作助手Json用法实例分析
2016/03/05 PHP
php生成图片验证码的方法
2016/04/15 PHP
javascript之对系统的toFixed()方法的修正
2007/05/08 Javascript
纯JS实现的批量图片预览加载功能
2011/08/14 Javascript
js实现在页面上弹出蒙板技巧简单实用
2013/04/16 Javascript
使用JS 清空File控件的路径值
2013/07/08 Javascript
jQuery判断checkbox(复选框)是否被选中以及全选、反选实现代码
2014/02/21 Javascript
Nodejs进程管理模块forever详解
2014/06/01 NodeJs
js实现iGoogleDivDrag模块拖动层拖动特效的方法
2015/03/04 Javascript
JS 对象属性相关(检查属性、枚举属性等)
2015/04/05 Javascript
node.js学习之base64编码解码
2016/10/21 Javascript
jQuery ajax请求struts action实现异步刷新
2017/04/19 jQuery
Node.js中环境变量process.env的一些事详解
2017/10/26 Javascript
vue项目移动端实现ip输入框问题
2019/03/19 Javascript
使用异步组件优化Vue应用程序的性能
2019/04/28 Javascript
微信小程序常用赋值方法小结
2019/04/30 Javascript
手把手教你 CKEDITOR 4 扩展插件制作
2019/06/18 Javascript
跟老齐学Python之集合(set)
2014/09/24 Python
Python的Socket编程过程中实现UDP端口复用的实例分享
2016/03/19 Python
Python使用re模块正则提取字符串中括号内的内容示例
2018/06/01 Python
详解python while 函数及while和for的区别
2018/09/07 Python
对pandas将dataframe中某列按照条件赋值的实例讲解
2018/11/29 Python
Python基础学习之函数方法实例详解
2019/06/18 Python
django 环境变量配置过程详解
2019/08/06 Python
Stutterheim瑞典:瑞典高级外套时装品牌
2019/06/24 全球购物
简单而又朴实的个人求职信分享
2013/12/12 职场文书
九年级化学教学反思
2014/01/28 职场文书
ktv中秋节活动方案
2014/01/30 职场文书
优质服务活动实施方案
2014/05/02 职场文书
微笑服务标语
2014/06/24 职场文书
遗愿清单观后感
2015/06/09 职场文书
Canvas三种动态画圆实现方法说明(小结)
2021/04/16 Javascript
python实现Thrift服务端的方法
2021/04/20 Python
用python批量解压带密码的压缩包
2021/05/31 Python
python多次执行绘制条形图
2022/04/20 Python