首页 > 代码库 > Web UI - Javascript之DOM Ready
Web UI - Javascript之DOM Ready
什么是DOM Ready
1 window.onload=function()2 {3 //registered in onl oad4 document.getElementById(‘demo‘).innerHTML=‘hello world‘;5 }
这是一个很简单的case:onload太慢了。小伙鸡如果你现在竟然还在用着window.onload那就真的图样图森破了。
DOMContentLoaded
感觉到onload太慢了,firefox为DOM纳入了一个全新的事件,叫做DOMContentLoaded,这也就是我们后来所说的DOM Ready。DOM Ready和onload不同之处在于:onload等待页面所有元素加载完毕才会执行,而DOM Ready,则 是在DOM树构建完毕即执行。这一过程可以这么形容:onload的时候页面已经呈现,而DOM Ready完毕的时候甚至用户都还没有看见页面。我们可以更快的处理一些事情,而不是等到onload之后。 而这一事件,逐渐的被广大浏览器纳入,除了那些古老的IE浏览器。
现在的前端js框架类库,例如jQuery、Zepto、YUI、mass没有个DOM Ready都不好意思称自己为库、框架。刚开始学习jQuery的时候,就对这个DOM Ready非常的感兴趣,当时因为自己的境界还停留在学习jQuery的时候,所以只能远远观望。当驾驭了jQuery的时候,我们就应该透过现象看本质——一窥jQuery源码,探索里面的秘密。
其实DOM Ready,实现上非常简单,就是注册个DOM Ready的事件,但是低版本的IE(IE678)要么没有DOMContentLoaded事件,要么就是DOMContentLoaded的实现上有bug。
所以,我们自己动手来实现DOM Ready吧。
1 //将一个函数注册到DOM Ready 2 function $(fn) { 3 if (document.addEventListener)//w3c browser 4 document.addEventListener("DOMContentLoaded", fn, false); 5 else if (document.attachEvent)//ie8+ browser 6 document.attachEvent("onreadystatechange", fn); 7 } 8 9 10 //注册一个DOM Ready事件,是不是和jQuery很像?11 $(function () {12 console.log(‘hello world!‘);13 });
不要忘记,在你编写了一段在现代浏览器中运行起来各种没问题so beautiful,正觉得自己碉堡了的时候,你打开了IE,然后眼睁睁的看着它对你的精神各种侮辱xxoo的那种感觉吧。没错,万恶的IE
上面的代码中,我们针对IE进行readystatechange进行绑定,IE为DOM的一部分提供readystatechange事件。然而,IE678,在页面中有iframe的情况下,DOM Ready的加载就会被影响,没错,它在等待iframe加载完毕才会执行readystatechange事件。
为此,我做了一番模拟,如下HTML:
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>IE下readystatechange事件测试 - linkFly</title> 6 <script type="text/javascript"> 7 8 //开始记录时间 9 var date = new Date();10 +function () {11 //注册readystatechange函数12 document.attachEvent("onreadystatechange", function () {13 //跟踪这个函数执行的时间14 document.getElementById(‘demo‘).innerHTML = Math.floor((new Date()).getTime() - date.getTime()) / 1000;15 });16 }();17 </script>18 </head>19 <body>20 <div id="demo"></div>21 <!--这里显示readystatechange时间差-->22 <iframe src="https://github.com/" height="20" width="800"></iframe>23 <div id="end"></div>24 <!--这里是DOM Ready时间差-->25 </body>26 <script type="text/javascript">27 //js写在这个位置,DOM可以说已经构建完毕,跟踪执行时间28 document.getElementById(‘end‘).innerHTML = Math.floor((new Date()).getTime() - date.getTime()) / 1000;29 </script>30 </html>
然后,就发生了一些有意思的事情,如下图:
iframe里面特意链到Github,所以访问起来很慢(天朝,你懂的...)。可以看见,下面的DOM Ready的代码执行的很快,但是上面的readystatechange,ie8下我们忍了,但是这ie6、7是个神马情况???12s后才执行,简直就是坑爹夫斯基啊!!!!!这玩意儿尼玛不靠谱啊。
我用写好的DOM Ready进行测试,效果如下:
这速度,这效果,so beautiful啊...总结起来就是,有iframe的页面,readystatechange并不靠谱,那么我们需要单独处理一下这个情况,针对这一情况,最著名的莫过于Diego Perini发现的hack:
1 +function(fn)//ie678检测iframe的DOMReady 2 { 3 try { 4 //IE下页面DOM没有加载完成,调用doScroll会报错 5 document.documentElement.doScroll("left"); 6 //没有报错,证明DOM已经加载完毕,执行DOM Ready里需要执行事件 7 fn(); 8 } catch (e) { 9 //重复调用自己10 setTimeout(arguments.callee, 1);11 }12 }(function(){13 document.getElementById(‘demo‘).innerHTML=‘hello world!‘;14 });
好的,现在我们也已经知道了怎么解决IE下的问题,那么现在,我们就应该把这两段代码合并,封装一个DOMReady库,下次使用我们直接就可以用了。没错,DOM Ready其实就这点东西,是不是so easy啊。那么我们准备封装类库吧。
让我们想想,jQuery的DOM Ready是不是可以多个事件绑定到DOM Ready?那么我们也来实现这样的效果,检测DOM Ready,然后执行事件集合,在Js,就用数组来实现。
其实代码上很大一部分参照了jQuery源码,但是jQuery解耦解的实在丧心病狂,所以一个DOM Ready跳转了不下30多个方法,当然,侦听DOM Ready的代码并不是很繁琐,jQuery主要在DOM Ready的将要执行的事件上进行了良好的解耦和封装。 好了话不多说,上源码:
源码
Javascript源码:
1 /*! 2 * by - linkfly 3 * cnblogs - http://www.cnblogs.com/silin6/ 4 */ 5 (function (window, undefined) { 6 /* 7 * checkReady 8 * event model:ready ->> bindReady ->> 9 * ie(iframe):doScrollCheck ->> checkReady10 */11 var readyList = [], //DOM Ready执行的数组12 document = window.document,13 DOMContentLoaded, //DOMReady事件14 isReady = false, //DOM是否准备完毕15 triggerReady = function () { //触发ready事件16 while (readyList.length) {17 readyList[0].call(window); //指定上下文18 readyList.shift();19 }20 };21 //ready执行方法,检测需要的环境是否已经准备好,它是DOM Ready最后一道关卡22 var checkReady = function () {23 if (!document.body) { return setTimeout(checkReady, 1); }24 isReady = true; //标识完成25 triggerReady();26 },27 //本来还想使用一个wait数组表示当前正在等待执行的数据,但是因为下面用的addEventListener和attachEvent,所以直接让js Event维护即可28 bindReady = function () { //绑定ready29 //body必须存在30 //如果DOM Ready,则直接触发,Firefox3.6之前并没有readyState,考虑市场因素抛弃这部分兼容31 if (document.readyState === ‘complete‘ || isReady) { return setTimeout(triggerReady, 1); }32 if (document.addEventListener) {//w3c33 document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);34 //如果没有赶上DOM Ready,则监听load35 window.addEventListener("load", checkReady, false);36 } else if (document.attachEvent) {//ie37 document.attachEvent("onreadystatechange", DOMContentLoaded);38 window.attachEvent("onload", checkReady);39 //ie下多iframe40 var toplevel = false;41 try {42 toplevel = window.frameElement == null;43 } catch (e) { }44 if (document.documentElement.doScroll && toplevel) {45 doScrollCheck();46 }47 }48 },49 doScrollCheck = function () { //ie678检测iframe的DOMReady50 try {51 //IE下页面DOM没有加载完成,调用doScroll会报错52 document.documentElement.doScroll("left");53 checkReady();54 } catch (e) {55 setTimeout(doScrollCheck, 1);56 }57 },58 ready = function (fn) { //DOM Ready59 //判定fn有效性代码略过...60 readyList.push(fn);61 bindReady();62 };63 ~function () {64 if (document.addEventListener) {65 DOMContentLoaded = function () {66 document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);67 checkReady();68 };69 } else if (document.attachEvent) {70 DOMContentLoaded = function () {71 if (document.readyState === "complete") {72 document.detachEvent("onreadystatechange", DOMContentLoaded);73 checkReady();74 }75 };76 }77 }();78 window.$ = window.ready = ready;79 })(window);
HTML源码:
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title>测试DOM Ready - linkFly</title> 5 <!--<script src="http://www.mamicode.com/jQuery/jquery-1.7.2.js" type="text/javascript"></script>--> 6 <script src="DOMReady.js" type="text/javascript"></script> 7 <script type="text/javascript"> 8 //http://localhost:2969/test.html 9 $(function () {10 document.getElementById(‘test‘).innerHTML += ‘<p>顶部:完毕</p>‘;11 alert(window.frames[0].document.firstChild.innerHTML);12 });13 </script>14 <style type="text/css">15 img { height: 20px; width: 20px; }16 </style>17 </head>18 <body>19 <div id="test">20 </div>21 <div id="test2">22 </div>23 <div id="test3">24 </div>25 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=0"26 alt="" />27 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=1"28 alt="" />29 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=2"30 alt="" />31 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=3"32 alt="" />33 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=4"34 alt="" />35 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=5"36 alt="" />37 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=6"38 alt="" />39 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=7"40 alt="" />41 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=8"42 alt="" />43 <img src="http://c.s-microsoft.com/en-us/CMSImages/MS_FavMoments_End_1600x540_EN_US.jpg?version=10582f1d-db23-3040-9cf3-3ec93ece226d&h=9"44 alt="" />45 <iframe src="https://github.com/" height="200" width="800"></iframe>46 <!--测试iframe跨域-->47 </body>48 <script type="text/javascript">49 $(function () {50 document.getElementById(‘test2‘).innerHTML += ‘<p>底部:完毕</p>‘;51 });52 </script>53 </html>
到了这里,是不是有点觉得onload就应该被舍弃呢?其实不然,onload也不是一无是处,例如chrome不同的图片渲染引擎,造成了无法在DOM中处理图片,当然这也是合理的,DOM树中或许图片都还没有下载呢,遇见过这样的情况,只能在onload中对图片进行处理。
源码下载地址
- sina:http://vdisk.weibo.com/s/BSINfl2rS-wLf
- baidu:http://pan.baidu.com/s/1gdIewQB