浏览器是如何加载JS的
当浏览器遇到一个<script>标签时,浏览器首先根据标签src属性下载JavaScript代码,然后运行JavaScript代码,继而继续解析和翻译页面。如果需要加载的js文件很多很大,则会让人感觉页面加载很慢,影响页面的交互。浏览器在遇到<body>之前,不会渲染页面的任何部分,如果此时<head>中需要加载的js文件很大的话,可能用户开始看到的页面就是一个“白板”,这种情况会立马让用户崩溃。

Internet Explorer 8, Firefox 3.5, Safari 4, 和Chrome 2 允许并行下载JavaScript 文件。这表明当一个script文件正在下载时,不会阻塞其他script的下载。并行下载script加快了script的下载时间,但还是要阻塞其他资源的下载,如图片。

解决方案
1.将脚本放在页面底部

一种常见的做法就是将<script>标签放在闭合的</body>之前。这样就可以先把页面展示给用户,让页面的加载速度显得不是很慢。这时最好将CSS文件放在head中,一边加载DOM一边渲染样式。
2.成组下载脚本
我们知道多个HTTP请求也会降低页面性能。因此我们可以采用将多个脚本文件合并到一个文件下载。这虽然减少了HTTP请求,但这给我们增加了较大的额外工作,因为每次发布我们都要合并文件。为此,我们可以采用成组下载的方式来达到目的。成组下载就是一次请求下载多个脚本文件。例如以下的URL:
每次向服务器的固定服务请求下载多个文件。服务器将多个文件合并一起返回给客户端。这是HTML页面包含多个外部JavaScript的最佳方法。
3.延时加载
如果script文件又大又多,那么恐怕我们怎么压缩文件怎么减少http请求数,script的加载都会锁定浏览器一大段时间。这时比较好的方法就是等页面加载完成后再加载script,也就是在window.onload事件发出之后开始下载代码。
 
HTML4中的defer属性可以实现下载script时不阻塞浏览器其他处理过程,代码下载完成后也不会被执行,而是等到DOM加载完成后(window.onload之前)才被执行。但defer属性只被IE4+和FF3.5+支持,其他浏览器不支持该属性,因此这不是一个理想的跨浏览器解决方案。
 
延时加载的一种通用方法就是动态创建脚本元素。一个新的<script>元素可以非常容易地通过标准DOM函数创建:
 
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "xxx.js";
document.getElementsByTagName("head")[0].appendChild(script);
当script元素被添加到页面之后便开始下载脚本文件。该技术的优点是:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。当script文件下载完成后,返回的代码通常被立即执行(除了FF和Opera,它们将等待此前的所有动态脚本节点执行完毕。)
当动态加载的script只是其他script调用的接口时,就会出现问题。因为调用代码不知道被调用的接口是否已准备完毕。不过还好,目前主流浏览器都能够跟踪到节点是否加载完成。

FF, Opera, Chrome和Safari3+会在节点接收完成后发出一个load事件;IE则是发出一个readystatechange事件,<script>元素有一个readyState属性,它的值随着下载过程而改变。readyState有5种取值:uninitialized(默认状态),loading(下载开始),loaded(下载完成),interactive(下载完成但尚不可用),complete(所有数据已准备好)。微软文档上说,这些取值不一定全部出现,有时script会得到loaded不出现complete,有时script会得到complete不出现loaded。最安全的做法就是,在readystatechange事件中检查这两种状态,当出现两种状态之一时,删除readystatechange句柄,以避免事件不会被执行两次。

根据上面的描述,新的动态加载代码如下所示:

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{ // FF, Chrome, Opera, ...
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
如果要加载多个文件,并且要保证顺序,则可以采用将上述函数串联的方式实现。

请看下面的使用示例:

loadScript.js:实现loadScript函数

页面文件:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>动态加载</title>
<script type="text/javascript" src="../scripts/loadScript.js"></script>
<script type="text/javascript">
loadScript("../scripts/test1.js", function(){
loadScript("../scripts/test2.js", function(){
loadScript("../scripts/test3.js", function(){
test3.print("hi, i'm abc.");
})
});
});
</script>
</head>
<body>
</body>

用于测试的test1.js:

用于测试的test2.js,test2依赖于test1:

var test2 = {
    print: function(msg){
        test1.print(msg);
        alert("from test2: " + msg);
    }
};
用于测试的test3.js,test3依赖于test2:

var test3 = {
    print: function(msg){
        test2.print(msg);
        alert("from test3: " + msg);
    }
};
运行页面,将先后弹出“from test1: hi, i'm abc.”, "from test2: hi, i'm abc.", "from test3: hi, i'm abc."。

动态脚本加载是最常用的JavaScript非阻塞下载方式,因为它跨浏览器而且简单易用。

XMLHttpRequest脚本注入
利用XHR对象下载JavaScript代码,然后动态创建script元素将JavaScript代码注入到页面中。

这种方法需要用到ajax,先把ajax的辅助方法列出来:

/**
* create xmlhelper
* */
(function(window){
var xhrhelper = (function(){
var xhrhelper = function(){};
xhrhelper.extend = function(){
var len = arguments.length, target = this;
for(var i = 0; i < len; i++){
var source = arguments[i];
for(var p in source){
if(target[p] == undefined){
target[p] = source[p];
}else{
throw new Error("extend error: "+ p +" is already existed.");
}
}
}
};
return xhrhelper;
})();
window.xhrhelper = xhrhelper;
})(window);
/**
* extend xmlhelper
* */
xhrhelper.extend({
/**
* create XmlHttpRequest object.
* */
createXmlHttp: function(){
var xmlhttp;
/*@cc_on
@if (@_jscript_version >= 5)
try{
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e){
try{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); 
} catch(E){
xmlhttp = false;
}
}
@else
xmlhttp = false;
@end
@*/
if(!xmlhttp && typeof XMLHttpRequest != 'undefined'){
try{
xmlhttp = new XMLHttpRequest();
} catch(e){
xmlhttp = false;
}
}
return xmlhttp;
},
/**
* send request.
* */
req: function(options){
var defaults = {
method: "get", // GET,POST,PUT,DELTE
url: null,
data: null,
async: true,
username: null,
password: null,
onloading: function(xhr){},
onloaded: function(xhr){},
oninteractive: function(xhr){},
oncomplete: function(xhr){},
onabort: function(xhr){},
onerror: function(xhr){}
};
for(var p in options){
defaults[p] = options[p];
}
var xmlhttp = xhrhelper.createXmlHttp();
if(xmlhttp){
xmlhttp.onreadystatechange = function(){
if(xmlhttp.readyState == 1){ // 未初始化
defaults.onloading(xmlhttp);
} else if(xmlhttp.readyState == 2){ // 已初始化
defaults.onloaded(xmlhttp);
} else if(xmlhttp.readyState == 3){
defaults.oninteractive(xmlhttp);
} else if(xmlhttp.readyState == 4){
if(xmlhttp.status == 0){
defaults.onabort(xmlhttp);
} else if(xmlhttp.status >= 200 && xmlhttp.status <= 300 || xmlhttp.status == 304){
defaults.oncomplete(xmlhttp);
} else{
defaults.onerror(xmlhttp);
}
}
};
if(defaults.username != null && defaults.password != null &&
defaults.username.length != 0 && defaults.password.length != 0){
xmlhttp.open(defaults.method, defaults.url, defaults.async,
defaults.username, defaults.password);
}else{
xmlhttp.open(defaults.method, defaults.url, defaults.async);
}
if(defaults.method.toLowerCase() == "get"){ }else if(defaults.method.toLowerCase() == "post"){
xmlhttp.setRequestHeader('Content-Type', 'text/html; charset=UTF-8');
}
xmlhttp.send(defaults.data);
}
return xmlhttp;
}
});

然后是利用xhr下载js文件的函数:

function xhrload(url, callback){
xhrhelper.req({
method: "get",
url: url,
oncomplete: function(xhr){
var script = document.createElement("script");
script.type = "text/javascript";
script.text = xhr.responseText;
document.body.appendChild(script);
callback();
}
});
}

在页面中可以通过如下方式调用xhrload函数:

xhrload("../scripts/test1.js", function(){
     test1.print("ABC");
});
这种方法的优点是:可以下载不立即执行的JavaScript代码。由于代码下载在了<script>标签之外,它下载后不立即执行。另一个优点是几乎所有的浏览器都支持这种方法。

这种方法的限制是:JavaScript文件必须与页面放在同一个域内。因为XmlHttpRequest不能跨域访问。

第三方库
YUI
LazyLoad
LABjs

 

(转)高性能JavaScript:加载和运行(动态加载JS代码)的更多相关文章

  1. Delphi静态加载DLL和动态加载DLL示例

    下面以Delphi调用触摸屏动态库xtkutility.dll为例子,说明如何静态加载DLL和动态加载DLL. 直接上代码. 1.静态加载示例 unit Unit1; interface uses W ...

  2. Vue加载组件、动态加载组件的几种方式

    https://cn.vuejs.org/v2/guide/components.html https://cn.vuejs.org/v2/guide/components-dynamic-async ...

  3. vc静态加载dll和动态加载dll

    如果你有a.dll和a.lib,两个文件都有的话可以用静态加载的方式: message函数的声明你应该知道吧,把它的声明和下面的语句写到一个头文件中 #pragma comment(lib, &quo ...

  4. c# 创建Excel com加载项Ribbon动态加载工作簿和工作表

    使用 VSTO 创建外接程序,Gallery控件动态加载工作簿名称 代码如下: 加载工作簿名称: private void Gallery1_ItemsLoading(object sender, R ...

  5. 高性能javascript学习总结(1)--加载与运行

    一.脚本的位置         我们知道,一个<script>标签可以放在 HTML 文档的<head>或<body>标签中,但是浏览器是怎么加载和执行这些java ...

  6. android左右滑动加载分页以及动态加载数据

    android UI 往右滑动,滑动到最后一页就自动加载数据并显示 如图: package cn.anycall.ju; import java.util.ArrayList; import java ...

  7. 页面加载的时候自动的执行js代码

    <script> window.onload=MyAutoRun; function MyAutoRun(){ alert("函数自动执行哦!"); } </sc ...

  8. 可加装广告的swf播放器JS代码

    加载flash动画前可以加载代码,设定广告显示秒数这些,还有些小bug,等有空了修复好法上来给大家​​ 1. [代码][Java]代码<!DOCTYPE html PUBLIC "-/ ...

  9. 高性能javascript学习笔记系列(1) -js的加载和执行

    这篇笔记的内容主要涉及js的脚本位置,如何加载js脚本和脚本文件执行的问题,按照自己的理解结合高性能JavaScript整理出来的 javascript是解释性代码,解释性代码需要经历转化成计算机指令 ...

  10. 使用js加载器动态加载外部Javascript文件

    原文:http://www.cnblogs.com/xdp-gacl/p/3927417.html 今天在网上找到了一个可以动态加载js文件的js加载器,具体代码如下: JsLoader.js var ...

随机推荐

  1. Docker 有什么优势?

    1.什么是容器? 依托与linux 内核功能的虚拟化技术 2. docker 是什么? 能够把应用程序自动部署到容器的开源引擎 3. docker 跟原有的工具有何区别? 传统的部署模式是:安装(包管 ...

  2. 哈夫曼树(三)之 Java详解

    前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:htt ...

  3. 深入了解ibatis源码----简单ibatis示例代码

    搭建源码环境: 1.创建sql数据库. CREATE TABLE USER_ACCOUNT ( USERID INT(3) NOT NULL AUTO_INCREMENT, USERNAME VARC ...

  4. HTML5移动Web开发(四)——移动设计

    桌面网站的设计趋势是固定布局(fixed layout)或流体布局(fluid layout),而在移动网站中我们应该始终使用流体布局,它可以使你的网站适应不同的设备尺寸. 固定宽度布局(Fixed ...

  5. 开发人员看测试之TDD和BDD

    前言: 已经数月没有来园子了,写博客贵在坚持,一旦松懈了,断掉了,就很难再拾起来.但是每每看到自己博客里的博文的浏览量每天都在增加,都在无形当中给了我继续写博客的动力.最近这两天有听到Jbehave这 ...

  6. [New Portal]Windows Azure Virtual Machine (21) 将本地Hyper-V的VM上传至Windows Azure Virtual Machine

    <Windows Azure Platform 系列文章目录> 本章介绍的内容是将本地Hyper-V的VHD,上传到Azure数据中心,并且保留OS中的内容. 注意:笔者没有执行Syspr ...

  7. JS的一些日期操作

    以下语句,作者都亲自整理,并调试过,转载请注明出处 var nowDate = new Date(); nowDate.getYear(); //获取当前年份(2位) nowDate.getFullY ...

  8. 浅谈 block(1) – clang 改写后的 block 结构

    这几天为了巩固知识,从 iOS 的各个知识点开始学习,希望自己对每一个知识理解的更加深入的了解.这次来分享一下 block 的学习笔记. block 简介 block 被当做扩展特性而被加入 GCC ...

  9. DedeCMS学习

    也许有些读者并不了解dedecms,这里简单介绍一下:DedeCMS是一个自由和开放源码的内容管理系统,它是一个可以独立使用的内容发布系统(CMS).织梦内容管理系统(DedeCms) 以简单.实用. ...

  10. JavaScript基础—闭包,事件

    Js基础-闭包,事件 1:js中的闭包 概念:在一个函数内部又定义了一个函数,内部函数能访问到外部函数作用域范围内的变量,这时这个内部函数就叫做闭包,无论这个内部函数在哪里被调用都能访问到外部函数作用 ...