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

4.2 事件的支持侦测

Prototype的核心成员kangax 写了一篇叫《Detecting event support without browser sniffing》文章http://perfectionkills.com/detecting-event-support-without-browser-sniffing/,来判定浏览器对某种事件的支持。里面给出的实现如下。

      var isEventSupported = (function() {
          var TAGNAMES = {
              'select': 'input', 'change': 'input',
              'submit': 'form', 'reset': 'form',
              'error': 'img', 'load': 'img', 'abort': 'img'
          }
          function isEventSupported(eventName) {
              var el = document.createElement(TAGNAMES[eventName] || 'div');
              eventName = 'on' + eventName;
              var isSupported = (eventName in el);
              if (!isSupported) {
                  el.setAttribute(eventName, 'return;');
                  isSupported = typeof el[eventName] == 'function';
              }
              el = null;
              return isSupported;
          }
          return isEventSupported;
      })();

现在jQuery与mass使用的脚本都是其简化版,其中mass对IE内存泄漏做了优化。

      $.eventSupport = function(eventName, el) {
          el = el || document.documentElement
          eventName = "on" + eventName;
          var ret = eventName in el;
          if (el.setAttribute && !ret) {
              el.setAttribute(eventName, "");
              ret = typeof el[eventName] === "function";
              el.removeAttribute(eventName);
          }
          el = null;
          return ret;
      };

不过哪一个也好,这种检测只对DOM0事件凑效,像DOMMouseScroll、DOMContentLoaded、DOMFocusIn、DOMFocusOut、DOMSubtreeModified、DOMNodeInserted、DOMNodeRemoved、DOMNodeRemovedFromDocument、DOMNodeInsertedIntoDocument、DOMAttrModified、DOM-CharacterDataModified这些以DOM开头的事件就无难为力了。

这些事件中有的非常有用,如:DOMMouseScroll,Firefox一直不支持mousewheel,只能用它做替代品;DOMContentLoaded 是实现domReady的重要事件;DOMNodeRemoved 是判定元素是否从其父节点移除,父节点可能是其他元素节点或文档碎片;DOMNodeRemovedFromDocument是移离DOM树;DOMAttrModified以前经常用于模拟IE的onpropertychange;DOMCharacterData-Modified用于监听contenteditable为true的元素内容变动。

在mass Framework中,是使用以下方法判定的。

      //https://github.com/RubyLouvre/mass-Framework/blob/1.4/event.js
      try {
          //如果浏览器支持创建MouseScrollEvents事件对象,那么就用DOMMouseScroll
          document.createEvent("MouseScrollEvents");
          eventHooks.mousewheel = {
              bindType: "DOMMouseScroll",
              delegateType: "DOMMouseScroll"
          };
          //如果某一天,Firefox回心转意支持mousewheel,那么我们就不需要这个钩子
          if ($.eventSupport("mousewheel")) {
              delete eventHooks.mousewheel;
          }
      } catch (e) {
      }

此外,mass还对focusin进行识别。focusin与focusout是一对,判定当中一个就明白另一个情况。这两个事件也很重要,用于实现focus与blur的事件代理,因为focus与blur不支持冒泡,需要用它们的冒泡版实现(假若不支持focusin与focusout,jQuery也找到办法了,不过有原生的就用原生的)。

      //https://github.com/RubyLouvre/mass-Framework/blob/1.4/support.js#L108
      //首先判定它是否是W3C阵营,IE肯定支持
      $.support.focusin = !!window.attachEvent;
      $(function() {
          var div = document.createElement("div");
          document.body.appendChild(div);
          div.innerHTML = "<a href='#'></a>";
          if (!support.focusin) {
              a = div.firstChild;
              a.addEventListener('focusin', function() {
                  $.support.focusin = true;
              }, false);
              a.focus();
          }
      });

CSS3添加两种动画,一种是transition动画,第一种是keyframe补间动画,它们在结束时都有相应的事件回调。但在标准化过程中,浏览器给它们起的名字相当没规则。这个也需预先侦测出来。

下面是bootstrap的实现,听说来源于modernizr,比较粗糙。比如说你现在用的Opera已经支持不带前缀的标准事件名,它还是返回oTransitionEnd。

      $.support.transition = (function() {
          var transitionEnd = (function() {
              var el = document.createElement('bootstrap'),
                      transEndEventNames = {
                  'WebkitTransition': 'webkitTransitionEnd',
                  'MozTransition': 'transitionend',
                  'OTransition': 'oTransitionEnd otransitionend',
                  'transition': 'transitionend'
              };
              for (var name in transEndEventNames) {
                  if (el.style[name] !== undefined) {
                      return transEndEventNames[name]
                  }
              }
          }())
          return transitionEnd && {
              end: transitionEnd
          }
      })()

keyframe补间动画的检测来自mass 的fx_neo模块,以后会说到的。

      var eventName = {
          AnimationEvent: 'animationend',
          WebKitAnimationEvent: 'webkitAnimationEnd'
      }, animationend;
      for (var name in eventName) {
          if (/object|function/.test(typeof window[name])) {
              animationend = eventName[name]
              break
          }
      }