JavaScript框架设计
上QQ阅读APP看书,第一时间看更新

1.5 主流框架引入的机制——domReady

domReady其实是一种名为“DOMContentLoaded”事件的别称,不过由于框架的需要,它与真正的DOMContentLoaded有一点区别。在许多旧的JavaScript书藉中,它们都会教导我们把JavaScript逻辑写在window.onload回调中,以防DOM树还没有建完就开始对节点进行操作,导致出错。而对于框架来说,越早介入对DOM的干涉就越好,如要进行什么特征侦测之类的。domReady还可以满足用户提前绑定事件的需求,因为有时页面图片等资源过多,window.onload 就迟迟不能触发,这时若还没有绑定事件,用户点哪个按钮都没反应(除了跳转外)。因此主流框架都引入domReady机制,并且费了很大劲兼容所有浏览器,具体策略如下。

(1)对于支持DOMContentLoaded事件的使用DOMContentLoaded事件。

(2)旧版本IE使用Diego Perini发现的著名hack!

      //http://javascript.nwbox.com/IEContentLoaded/
      //by Diego Perini 2007.10.5
      function IEContentLoaded(w, fn) {
          var d = w.document, done = false,
                  init = function() {
              if (!done) {//只执行一次
                  done = true;
                  fn();
              }
          };
          (function() {
              try {//在DOM未建完之前调用元素doScroll抛出错误
                  d.documentElement.doScroll('left');
              } catch (e) {//延迟再试
                  setTimeout(arguments.callee, 50);
                  return;
              }
              init();//没有错误则执行用户回调
          })();
          // 如果用户是在domReady之后绑定这个函数呢?立即执行它
          d.onreadystatechange = function() {
              if (d.readyState == 'complete') {
                  d.onreadystatechange = null;
                  init();
              }
          };
      }

此外,IE还可以通过script defer hack进行判定。

      //http://webreflection.blogspot.com/search?q=onContent
      //by Andrea Giammarchi 2006.9.24
      document.write("<script id=__ie_onload defer src=//0><\/scr" + "ipt>");
      script = document.getElementById("__ie_onload");
      script.onreadystatechange = function() {//IE即使是死链也能触发事件
          if (this.readyState == "complete"){
              init(); // 指定了defer的script会在DOM树建完才触发
          };

不过有个问题是,如果我们的种子模块是动态加载的,在它插入DOM树时,DOM树已经建完呢?这该怎么触发我们的 ready 回调?jQuery 给出的方案是,连 onload 也监听了,但如果连onload也没赶上,就判定document.readyState是否等于complete!这样完美了吧,可惜Firefox3.6之前没有这属性!看mass给出的方案。

      var readyList = [];
      mass.ready = function(fn) {
          if (readyList) {
              fn.push(fn);
          } else {
              fn();
          }
      }
      var readyFn, ready = W3C ? "DOMContentLoaded" : "readystatechange";
      function fireReady() {
          for (var i = 0, fn; fn = readyList[i++]; ) {
              fn();
          }
          readyList = null;
          fireReady = $.noop; //惰性函数,防止IE9二次调用_checkDeps
      }
      function doScrollCheck() {
          try { //IE下通过doScrollCheck检测DOM树是否建完
              html.doScroll("left");
              fireReady();
          } catch (e) {
              setTimeout(doScrollCheck);
          }
      }
      //在Firefox3.6之前,不存在readyState属性
      //http://www.cnblogs.com/rubylouvre/archive/2012/12/18/2822912.html
      if (!DOC.readyState) {
          var readyState = DOC.readyState = DOC.body ? "complete" : "loading";
      }
      if (DOC.readyState === "complete") {
          fireReady(); //如果在domReady之外加载
      } else {
          $.bind(DOC, ready, readyFn = function() {
              if (W3C || DOC.readyState === "complete") {
                  fireReady();
                  if (readyState) { //IE下不能改写DOC.readyState
                      DOC.readyState = "complete";
                  }
              }
          });
          if (html.doScroll) {
              try { //如果跨域会报错,那时肯定证明是存在两个窗口的
                  if (self.eval === parent.eval) {
                      doScrollCheck();
                  }
              } catch (e) {
                  doScrollCheck();
              }
          }
      }