Dynamic Script Loading

近年来前端技术可谓发展迅猛,受益于计算机与浏览器内核的性能提升,如今网站前端架构可以设计得更加庞大,许多原本服务器端处理的业务逻辑都挪到了前端进行,因此也相应地出现了许多MVC(Backbone)、MVVM(KnockoutJS)等前端框架和CommonJSRequireJS等接口规范。而为了更好的用户体验,许多现代的站点都采用了B/S只通过数据接口通信的策略,用户一次加载,随后的浏览与操作中网页内容都由JS代码控制加载与渲染完成。这带来的挑战是,一个页面请求将带来更多的代码网络传输(若是没有进行压缩合并那影响就更明显了),同时在一个页面容器内浏览器必须解析更多的JS代码,这二者都严重地影响着页面加载时间。对此,一个比较好的解决办法是将前端结构模块化,然后在初始载入中只读取核心功能部分的代码,随后根据用户的操作加载对应的模块,也就是代码动态加载技术。除了通过Ajax配合eval实现外,一般脚本的动态加载都是通过生成Script节点完成,下面是一个简单的实现:

;(function () {
  "use strict"

  var doc = document
  var head = doc.head ||
        doc.getElementsByTagName('head')[0] ||
        doc.documentElement

  function scriptOnload(node, callback) {
    node.onload = node.onerror = node.onreadystatechange = function () {
      if (/loaded|complete|undefined/.test(node.readyState)) {
        node.onload = node.onerror = node.onreadystatechange = null
        if (node.parentNode) { head.removeChild(node) }
        node = undefined
        if (typeof callback !== 'undefined') { callback() }
      }
    }
  }

  window.scriptOnload = scriptOnload
})()

其中在回调方法中head.removeChild(node)等移除节点的语句是为了避免IE8以下浏览器中的内存泄露。以加载jQuery为例,使用方式为:

var ns = {}

var jqueryNode = doc.createElement('script')
jqueryNode.async = 'async'
jqueryNode.src = '/path/to/jquery.js'
window.scriptOnload(jqueryNode, function () {
  ns.$ = jQuery.noConflict(true)
})
head.appendChild(jqueryNode)