• (转)高性能JavaScript:加载和运行(动态加载JS代码)


    浏览器是如何加载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
     
  • 相关阅读:
    SQL学习笔记 where子句用法,like关键字 嵌套查询
    Mvc视图构建辅助方法
    EXtjs 解决ie9 不支持extjs对象的 “createContextualFragment”属性或方法
    认识MVC
    Mvc基本切入及controller认识
    Extjs4.0 下载文件的说明
    Extjs4 Panel中使用autoload无法加载页面文件
    Mvc视图表单辅助方法
    js倒计时方法
    web confing 处理全局编码方式
  • 原文地址:https://www.cnblogs.com/susufufu/p/5764431.html
Copyright © 2020-2023  润新知