事实上说这个话题有点老生常谈,但是因为涉及到更多并行加载脚本的思考,所以还是觉得可以写一下。为了脚本资源的高并行加载提高页面加载速度,我们可能需要动态加载script。其中总是无法避免的一个方法是使用head.appendChild(script),因为这种方式可以直接跨域。 但是有时候 动态加载脚本可能是要保证他们的执行时序,最理想的状态就是 所有的脚本都可以在当前 http连接数允许的前提下。最大化并行加载量的同时可以选择按时序执行或按加载完成顺序执行,即先到先执行。 假设我们有3个js,分别为:a.js、b.js、c.js,传统的方式是这样的: 这样的加载大概只有Firefox3.0+和opera的比较新的版本,可以保证他们的并行加载。而其他浏览器则只会为他们开启一个http连接,一个一个的加载。 让他们在其他浏览器并行加载的方式有几种,比如借助iframe;比如document.write;比如上面说到的 head.appendChild(script) ;比如 xhr 请求然后eval;比如 xhr 注入(即 xhr 和head.appendChild(script) 相结合)。其中 document.write 写入脚本块.的方式有些问题 其中比较头疼的 是 有些浏览器 可能会 警告 说是 危险代码 .以及一些其他问题 . 详细问题可以去 yslow 作者 blog 去看看. 而另外的 方式 显然 都不能很方便的解决 跨域问题. 所以我们 来探讨下 本文的重点 head.appendChild(script) 方式. 这种方式在非ie下 我们可以 很容易的 捕捉 script.onload 以及 script.onerror 但是 ie 我们只能借助 script.onreadystatechange 来判断其加载执行状态. 而 onerror 则必须 和 被请求的.js文件 做某些 约定 才可以更好的判断. 该细节不在本文讨论范围... 在话题继续下去前 我们应该先明确 下 . 在动态添加script块的情况下 为什么需要 script.onload . 比如 上面的a.js b.js c.js 这三个脚本 假如他们的执行时序 是有依赖性的 话 那么我们就必须 保证a.js b.js c.js按次序执行... 但是 一单他们并行加载 的话 只有 firefox 和opera 才可以保证 他们按照请求发起的 顺序 来执行这些脚本. 而其他浏览器 则会是 无论谁先向服务器发起请求. 都只会是一个结果 即 哪个先加载完毕 先执行哪个. 为了 让我们的Loader API 更健壮... 不得不 在其他浏览器下 牺牲 并行加载 这个好处, 而 改用 加载好一个 执行完毕后 再加载下一个的方式来处理... 当然 这里我们有 另外的解决 方案 留到后面说. 现在 我们把需求简化一下 我们仅仅想要 script 加载并且 执行完毕后 回调我们指定的方法. 对于非ie 非常简单 如下面的代码 : var script = document.createElement('script'); script.onload=function () {alert('callBack');}; script.src="a.js" ; document.getElementsByTagName('head')[0].appendChild(script); 就是如此简单 对吧 . 但是遗憾的是 ie并不支持 script.onload事件 这时候我们 只好借助 script.onreadystatechange=function(){ script.readyState=='某个值'} 这种方式来判断 脚本是否 加载 并执行完毕 此时 readyState 的值 可能为 以下几个 : •“uninitialized” – 原始状态 •“loading” – 下载数据中.. •“loaded” – 下载完成 •“interactive” – 还未执行完毕. •“complete” – 脚本执行完毕. 现在 问题来了 . ie6 和 ie7 ie8 有些区别 大致分为以下几种情况 (以下状态 本人测试后 又请几位朋友帮忙测试 应该可以信任.) script.src="a.js" ; document.getElementsByTagName('head')[0].appendChild(script); 先写src 后append 的情况下 complete 和 loaded 只有一个 会出现. 他都标志着 脚本加载 并执行完毕 但出现哪个 有时候却不能确定 和 加载脚本时间 以及 脚本执行时间 都有关系. 而 document.getElementsByTagName('head')[0].appendChild(script); script.src="a.js" ; 先append 后给src 则 比较其开怪 ie 就可能同时 出现 complete 和loaded 而 其中 ie7 和ie8 总能保证 loaded 为 最后一个状态 即 此时 ie7 8 我们可以信任 loaded 状态时 脚本 已经加载 执行完毕 . 但是 ie6 就比较郁闷 它会 因为 脚本加载时间 和 脚本加载后执行时间不同 导致 loaded 和 complete 的出现先后次序 的不同...这时候我们无法得知 哪个状态 才是 脚本执行完毕的状态... 为了判断 这种状态下 脚本是否执行完毕 我们需要借助 额外的 开关变量来 信任 最后出现的 那个 状态 才是 脚本执行完毕的 状态. 经过大量测试后 得出一个 结论 即 如果 你想少惹麻烦的话 请 先给script 设置 src属性 然后 再 appedChild 他 到 DOM树中... 这样的话 我们 就只需要 这种代码 即可以 判断 脚本执行结束 了. if (/loaded|complete/.test(script.readyState)) //ie6 ie7 ie8 通用. 好了 解决了readyState问题后 我们 去看看另外的问题 大神 Nicholas C. Zakas 给出的方案如下 : view sourceprint? if (script.readyState) { //IE script.onreadystatechange = function(){ a.push(script.readyState); if (script.readyState == "loaded" || script.readyState == "complete") { script.onreadystatechange = null; callback && callback(); } }; } else { //Others script.onload = function(){ callback(); }; } 很明显 大神忽略了 两问题. 1. script.onreadystatechange=function(){} 这种方式 会造成ie6无法挽回的 cross page leak 内存泄露 所以即使 他具备 script.onreadystatechange = null; 对于ie6没有意义 2. opera 也支持 readyState这个事实 所以他这段脚本 在opera比较新的浏览器下 就会出问题... 另外 一但 将来的某个ie版本支持了 script.onload 我们可能 就无法享受到这个好处 了 对于1 建议使用attachEvent 请记得 对于ie6 任何非 attachEvent方式注册的事件 (除硬编码写到html中的) 都会引起ie6 无法挽回的 跨页内存泄露. 至于多少 就看回调函数 所在闭包 中的数据量了. 作用域链 越深 受影响的东西 就越多...危害也就越大. 对于2 我觉得 还是应该 优先判断是否支持 onload 才是正确的思路 比如这样: view sourceprint? function isImplementedOnload(script){ script = script || document.createElement('script') ; if('onload' in script) return true ; script.setAttribute('onload',''); return typeof elScript.onload == 'function' ; // ff true ie false . } 很显然 一来二去的 随着我们代码量的增加 现在 script 块 动态加载脚本 显得越来越靠谱了.... 那么我们说说 应用中的一些问题 理想状态下 所有脚本 都应该 最大化的 利用 当前可用的http连接数 即有几个我就用机个 去并行加载脚本 而不是一个一个的在那里阻塞... 但是很显然 想做到面面俱到 是不容易的事情. 那么 应该有以下一个 流程来处理他们 1. 优先考虑 xhr eval 或 xhr 注入 这两种方式 可以在保证并行下载资源的同时 很方便的 控制 执行的时序. 2. 一但无法解决跨域问题 则 使用 script 块 动态加载的方式 但应知道 此种方式 非Firefox opera浏览器 一旦要求执行时序 则我们 无法达成 并行加载 这一最初的 目的. 3. 群里的朋友 瓶子 给出的方案 是 可以借用 postMessage ie6 7使用 window.opener 漏洞 去实现域通信 然后借助一个 被引入的iframe页面 去请求脚本 然后 把 脚本内容 传给父页 方案3 瓶子给出的demo : http://www.webairness.com/apps/libs/local2.html 好吧回到最初的话题 请记得 对于动态加载 scirpt块 仍然可能 阻塞window.onload的情况 当然(硬编码的 轻轻是一定会阻塞的啦) 我们可以 把请求代码 写到 setTimeout 1ms 中 这样 就可以更早的 window.onload 越早onload的好处 就是 当我们 在 window.onunload 释放一些 事件侦听回调函数 以避免 内存泄露时 变的尤其重要 . 因为 有些浏览器 onload不发生 就永远不会发生 onunload 这是个很无奈的问题.... 这也是为什么推荐少使用iframe去 解决 一些跨域 问题的初衷...因为firame 会阻塞 主页面的 onload .... 暂时就到这里吧.... 如果有遗漏 或错误 欢迎指正..... 文章来源:http://www.cnblogs.com/_franky/archive/2010/06/20/1761370.html
标签:IE浏览器scriptIE Web