现在位置首页 / 前端开发 /正文

外部js/css异步加载/按需加载详细说明

作者: IT小兵 | 2014年4月25日| 热度:℃ | 评论: |参与:

在IE8、Firefox3.6之前页面加载外部的javascript文件(IE6和IE7会连同图片,样式资源和页面渲染一同阻塞)是阻塞式的,而在之后的版本中,浏览器都使用了瀑布式加载,这样页面的打开及渲染速度都会变快,请注意,我提到的瀑布式加载,仅仅指的是加载,而非JS的执行,在主流浏览器中JS的执行总是阻塞的。用简单一点的语言描述,就是同一时间,页面只会加载一个js文件。在第一个js文件加载并执行完之前,第二个要引入的js不会下载和执行。而页面中js的引入顺序以请求的顺序为定。

  1. 异步加载,加快了页面的加载时间,同时能够按需加载,节省流量

  2. 有序加载,解决js依赖问题

  3. 延时执行,在页面渲染完成后再执行js,防止undefined情况

我们都知道引入外部的js文件都是动态生成script标签节点,要么是通过设置src引入文件,要么是生成节点附加内容。但关键是如何得知文件加载成功的事件,这个确实重点关注的地方。但是为了保证不出问题,就需要对所有浏览器的异步加载成功事件实现进行一个测试。就目前而言,网络上比较成熟的解决方案有很多,但是没有具体使用过所以也没有一个结论,就去简单分析了一下主要几个框架或者插件是如何实现异步加载事件响应的。

外部函数库LABjs和RequireJS,可以帮助我们更有效地管理Javascript加载。


一.LABjs

这个项目位于github上面,其本意就是Loading And Blocking JavaScript,就是一个异步脚本加载器,相对于原始的粗暴script标签方式而言,提供了弹性的性能优化(做到js文件在浏览器中尽可能的并行加载,并且能够提供顺序执行的保证),还能在高级浏览器中实现先加载然后执行的功能。作为一个异步js加载器还是非常优秀的。其在异步加载的成功事件响应方面的实现如下:

// creates a script load listener
function create_script_load_listener(elem,registry_item,flag,onload) {
	elem.onload = elem.onreadystatechange = function() {
		if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") 
               || registry_item[flag]) return;
			elem.onload = elem.onreadystatechange = null;
			onload();
	};
}

从上面可见,基本上就是利用onload事件和onreadystatechange事件来完成的,最后一个registry_item[flag]就是对同源的文件可以通过AJAX来实现的。但是没有涉及到css文件的加载,也没提到js文件不存在的时候如何来检测。

二.RequireJS

RequireJS主要定位于a file and module loader for javascript,就是作为一种模块化开发过程中的模块加载器,由于模块是以文件形式存在,所以也就相当于一个文件加载器,但是实现了模块管理的功能。其主要也仍然是在处理js文件的加载,没有考虑css文件的加载。那我们看一下他主要的事件监听实现吧:

 //Set up load listener. Test attachEvent first because IE9 has
 //a subtle issue in its addEventListener and script onload firings
 //that do not match the behavior of all other browsers with
 //addEventListener support, which fire the onload event for a
 //script right after the script execution. See:
 //https://connect.microsoft.com/IE/feedback/details/648057/
 //script-onload-event-is-not-fired-immediately-after-script-execution
 //UNFORTUNATELY Opera implements attachEvent but does not follow the script
 //script execution mode.
if (node.attachEvent &&
    //Check if node.attachEvent is artificially added by custom script or
    //natively supported by browser
    //read https://github.com/jrburke/requirejs/issues/187
    //if we can NOT find [native code] then it must NOT natively supported.
    //in IE8, node.attachEvent does not have toString()
     //Note the test for "[native code" with no closing brace, see:
     //https://github.com/jrburke/requirejs/issues/273
     !(node.attachEvent.toString && node.attachEvent.toString()
     .indexOf('[native code') < 0)
     &&  !isOpera) {
      //Probably IE. IE (at least 6-8) do not fire
      //script onload right after executing the script, so
      //we cannot tie the anonymous define call to a name.
      //However, IE reports the script as being in 'interactive'
      //readyState at the time of the define call.
          useInteractive = true;
          node.attachEvent('onreadystatechange', context.onScriptLoad);
      //It would be great to add an error handler here to catch
      //404s in IE9+. However, onreadystatechange will fire before
      //the error handler, so that does not help. If addEventListener
      //is used, then IE will fire error before load, but we cannot
      //use that pathway given the connect.microsoft.com issue
      //mentioned above about not doing the 'script execute,
      //then fire the script load event listener before execute
       //next script' that other browsers do.
       //Best hope: IE10 fixes the issues,
      //and then destroys all installs of IE 6-9.
      //node.attachEvent('onerror', context.onScriptError);
} else {
      node.addEventListener('load', context.onScriptLoad, false);
      node.addEventListener('error', context.onScriptError, false);
}

从上面的注释可以明白对于IE6-8都是采用的onreadystatechange来监听加载成功事件,而对于404的事件是没有办法做出区分的,所以也就没能完成此功能,只是对于高级浏览器比如chrome之类的采用了onload监听成功事件,onerror监听失败事件。由于主要针对js文件的加载,所以也没有针对css文件的加载做出明确的实现。

三 jQuery

大名鼎鼎的jQuery在实现ajax封装了所有的异步加载功能的时候,为script加载专门分了文件的,具体可以看到如下实现:

script = jQuery("<script>").prop({
    async: true,
    charset: s.scriptCharset,
    src: s.url
}).on(
    "load error",
    callback = function( evt ) {
        script.remove();
        callback = null;
        if ( evt ) {
            complete( evt.type === "error" ? 404 : 200, evt.type );
        }
    }
);
document.head.appendChild( script[ 0 ] );

从上面代码看基本上和上面类似,看来对于js而言没有什么太多的方法,所以基本上按照以上几种实现即可。jquery并没有对css文件加载做专门的处理,所以还无从参考。

四.Seajs

Seajs在阿里系还是有很大的使用范围的,并且目前推广的还不错,所以陆续有很多公司开始采用了。其也主要是推行模块化开发的方式,因此也会涉及到异步记载模块文件的方式,所以也涉及到了文件的异步加载。其request模块实现如下:

function addOnload(node, callback, isCSS) {
  var missingOnload = isCSS && (isOldWebKit || !("onload" in node))
 
  // for Old WebKit and Old Firefox
  if (missingOnload) {
    setTimeout(function() {
      pollCss(node, callback)
    }, 1) // Begin after node insertion
    return
  }
 
  node.onload = node.onerror = node.onreadystatechange = function() {
    if (READY_STATE_RE.test(node.readyState)) {
 
      // Ensure only run once and handle memory leak in IE
      node.onload = node.onerror = node.onreadystatechange = null
 
      // Remove the script to reduce memory leak
      if (!isCSS && !configData.debug) {
        head.removeChild(node)
      }
 
      // Dereference the node
      node = undefined
 
      callback()
    }
  }
}
 
function pollCss(node, callback) {
  var sheet = node.sheet
  var isLoaded
 
  // for WebKit < 536
  if (isOldWebKit) {
    if (sheet) {
      isLoaded = true
    }
  }
  // for Firefox < 9.0
  else if (sheet) {
    try {
      if (sheet.cssRules) {
        isLoaded = true
      }
    } catch (ex) {
      // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
      // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
      // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
      if (ex.name === "NS_ERROR_DOM_SECURITY_ERR") {
        isLoaded = true
      }
    }
  }
 
  setTimeout(function() {
    if (isLoaded) {
      // Place callback here to give time for style rendering
      callback()
    }
    else {
      pollCss(node, callback)
    }
  }, 20)
}

从seajs的实现来看,主要完成了js和css的异步加载,其主要实现还是和YUI3的get模块实现方式基本一致。并且实现方式还是简单粗暴的,具体细节还不如YUI3的实现精细,但是对于大多数场景还是够用了的。



点击阅读本文所属分类的更多文章: 前端开发 。和高手一起交流:346717337
友荐云推荐

未注明转发、原文均为本站原创。分享本文请注明 原文链接

给您更多信息和帮助

在这里您可以找到更多:

技术交流群:346717337 Jquery插件交流

投稿:[email protected]

承接:企业网站门户/微网站/微商城/CMS系统/微信公众号运营/业务咨询

echarts教程系列
本月最热文章

微信扫一扫,徜徉悠嘻网,您的休闲乐园

微信公众号:快乐每一天

随机文章
标签

技术交流群:346717337

投稿:[email protected]

专业专注:企业网站门户/微网站/微商城/CMS系统/微信公众号运营/付费问题咨询