多上stackoverflow总是有好处,昨天发现opera12之前一个比较致命的BUG,触发条件是script.onreadystatechange = script.onload = function(){},因此连同mass, jQuery, seajs,labjs, headjs,controljs在内都中招了。我们可以称之为连写回调引发的血案。这个我正文会详解。
本版本的第二个改进是IE6的自闭合base标签回避问题。以前不管是否存在base标签,所有浏览器都插入到head标签的第一个子节点之前。但这样倒序可能引发后插入的先解析。因此现在特地对IE6进行处理,如果存在base标签,不管它是否自闭合,插入到它的前面,那样也可以保证顺序插入。其他浏览器则使用head.appendChild(script)。
本版本的第三个改进是FF3.6之前不支持document.readyState问题,这个是用于domReady。以前我们先行判定DOC.readyState === "complete",但FF没有这东西,就每次进入DOMContentLoaded分支。但DOMContentLoaded事件只会触发一次,domReady后这分支就不起作用了。解决方案我是从labjs的源码中读到的:
/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?). The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. */ ( function (addEvent,domLoaded,handler){ if (document.readyState == null && document[addEvent]){ document.readyState = "loading" ; document[addEvent](domLoaded,handler = function (){ document.removeEventListener(domLoaded,handler, false ); document.readyState = "complete" ; }, false ); } })( "addEventListener" , "DOMContentLoaded" ); |
第四个改进是,框架最开头前的三个分号的回归!这是用于合并脚本时用。
好了,回归第一个问题,我们的大荤。我们推崇使用script加载模块,是因为它提供事件回调让我们掌握整个加载状况。IE6-8是使用onreadystatechange 事件,IE9-10开始支持onload,其他标浏览器则支持onload,但opera比较奇葩,onload与onreadystatechange 都支持,并且script也有readyState属性。如果opera抄袭抄得好也没所谓,但抄出BUG来。
看下面脚本,第一次加载它时,opera9.64 弹出interactive,loaded。然后我们关闭此页面,再打开它时,就弹出loaded,loaded(估计是浏览器缓存的原因),并且这情况一定没有改善。随机抽个版本,如opera10.52,加载个新JS文件,第一次就是弹出loaded,loaded。幸好在opera12.12,它已经不支持onreadystatechange。而IE6-10总是弹出loading,loaded。
window.onload = function (){ var script = document.createElement( "script" ); var array = [] script.onreadystatechange = function (){ array.push( this .readyState); } script.src = "jquery.js" // Opera 9.64 document.body.appendChild(script); setTimeout( function (){ alert(array) },2000) } |
换言之,这种连写法会让我们在opera没有加载完JS前执行用户回调,导致出错。解决方案是不要连写,并在回调中只对IE进行readyState判定!下面是改进后loadJS函数:
function loadJS( url ){ var node = DOC.createElement( "script" ) //, IE = node.uniqueID node.className = moduleClass; node[W3C ? "onload" : "onreadystatechange" ] = function (){ if (W3C || /loaded|complete/i.test(node.readyState) ){ //mass Framework会在_checkFail把它上面的回调清掉 var factory = parsings.pop() ; factory && factory.delay(node.src) if ( checkFail(node) ){ $.log( "已成功加载 " +node.src, 7); } } } node.onerror = function (){ checkFail(node, true ) } node.src = url if (base && !XMLHttpRequest ){ head.insertBefore(node, base); } else { head.appendChild(node) } $.log( "正准备加载 " +node.src, 7) } |
基本上就是这样。mass Framework许多想法总是跑在世界前面,兼容性巨无细漏,欢迎试用!