`
jljlpch
  • 浏览: 319413 次
  • 性别: Icon_minigender_1
  • 来自: 南昌
社区版块
存档分类
最新评论

Event 分析一:prototype 源码

阅读更多
/*
 * author:prk
 * date:2008-07-31
 * comment: 
 */

if (!window.Event) var Event = { };

// 为Event注册一些键名
Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,
  
  // 缓存
  cache: { },
  
  // 事件相关连的元素,只对mouseover,mouseout有效。
  relatedTarget: function(event) {
    var element;
    // event.type,事件名
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    // 通过Extend 包装原配的emement为Prototype的Element
    // 在YUI则进行了resolveTextNode的处理。觉得有必要。
    return Element.extend(element);
  }
});

Event.Methods = (function() {
	
	// 判断是按了鼠标的什么键,右,左,中间啊,
	// 解决Brower的兼容性
	
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };
    
  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };
    
  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }
  
   // Ext 通过下面三行解决兼容问题。
  // var btnMap = Ext.isIE ? {1:0,4:1,2:2} :
  // (Ext.isSafari ? {1:0,2:1,3:2} : {0:0,1:1,2:2});
  // this.button = e.button ? btnMap[e.button] : (e.which ? e.which-1 : -1);
  return {
  	
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },
    
    // 定位event 's element
    element: function(event) {    	
      // 包装Event
      event = Event.extend(event);
      var node = event.target, currentTarget = event.currentTarget, type = event.type;
      
      // target:Returns a reference to the target to which the event was
		// originally dispatched
      // currentTarget:Returns a reference to the currently registered target
		// for the event.
      // 解决FF的兼容
      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on
		// images, reporting the document as the target instead of the original
		// image.
        if (['load', 'error'].include(type) ||
         (currentTarget.tagName.toUpperCase() === "INPUT" && currentTarget.type === "radio" && type === "click"))
          node = currentTarget;
      }
      
     // node.nodeType == Node.TEXT_NODE ? node.parentNode
      // 是YUI Event中resolveTextNode
      return Element.extend(node && node.nodeType == Node.TEXT_NODE ?
       node.parentNode : node);
    },
    
    // 查找到event的原始节点的祖先节点。expression只能是标签名 如a
    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },
      
    // 事件的元素的x,y坐标,相对于页面的。
    pointer: function(event) {
    /*
	 * pageX/Y:coordinate of the event relative to the page layerX/Y: coordinate
	 * of the event relative to the current layer screenX/Y: position of the
	 * event on the screen Browser区域 clientX /Y: position of the event
	 * 页面在Browser中区域。
	 */
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX + 
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY + 
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },
    
    // 中断事件
    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

// 实现Event的确extend
Event.extend = (function() {
	
	// 作用就是把函数的调用对象变成函数是第一个参数。条理化。
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });
  
  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
       
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;
      
      var pointer = Event.pointer(event);
    
      // 为事件注册属性
      Object.extend(event, {
        _extendedByPrototype: Prototype.emptyFunction,
        target:        Element.extend(event.srcElement),
        relatedTarget: Event.relatedTarget(event),
        pageX:         pointer.x,
        pageY:         pointer.y
      });
     // 推迟到每次调用时,才把methods考到event
      return Object.extend(event, methods);
    };
    
  } else {
  	// 把条理化的方法注册到Event.prototype
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    // Event ID is stored as the 0th index in a one-item array so that it
    // won't get copied to a new node when cloneNode is called.
  	
  	// 这种处理递增的Id很妙,arguments.callee.id=getEventID.id,同时也设定getEventID.id
    if (element === window) return 1;
    if (element._prototypeEventID) return element._prototypeEventID[0];
    return (element._prototypeEventID = [arguments.callee.id++])[0];
  }
  getEventID.id = 2;
  
  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }
  
  // 根据Id从cache中取得或生成一个元素的缓存。
  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }
  // 根据Id,eventName从cache中取得或生成一个元素的的事件名数组
  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }
  // 包装handler,主要作用取Id,缓存。
  function createWrapper(element, eventName, handler) {
  	// create id,and create cache for id.
    var id = getEventID(element), c = getCacheForID(id);

    // Attach the element itself onto its cache entry so we can retrieve it for
    // cleanup on page unload. {3:{element:element}}
    if (!c.element) c.element = element;

    // {3:{element:element,eventName:[]}}
    var w = getWrappersForEventName(id, eventName);
    // 有的话,不加入了
    if (w.pluck("handler").include(handler)) return false;
    
    // wrapper FN
    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;      
      handler.call(element, Event.extend(event));
    };
    // add static method:handler=original handler
    // {3:{element:element,eventName:[{handler:handler,wrapper:wrapper}]}}
    wrapper.handler = handler;
    
    // store in cache.
    w.push(wrapper);
    
    return wrapper;
  }
  
  // 根据id, eventName, handler找已经cache的Wrapper
  function findWrapper(id, eventName, handler) {
  	
  	// {3:{element:element,eventName:[wrapper1,wrapper1]}}
  	// w=[wrapper1,wrapper1]
    var w = getWrappersForEventName(id, eventName);
    
    // 函数在数组中找到符合条件的数组元素,很方便
    return w.find(function(wrapper) { return wrapper.handler == handler });
  }
  
  // 从eventName对应的数组中去掉符合条件的。
  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }
  
  // Loop through all elements and remove all handlers on page unload. IE
  // needs this in order to prevent memory leaks.
  function purgeListeners() {
    var element, entry; 
   
    for (var i in Event.cache) {    	
     // cache={3:{element:element,eventName:[wrapper1,wrapper1]}}
      entry = Event.cache[i];
      // unscribe the element 's listen.
      Event.stopObserving(entry.element);
      entry.element = null;
    }
  }
  
  function onStop() {
    document.detachEvent("onstop", onStop);
    purgeListeners();
  }
  
  function onBeforeUnload() {
    if (document.readyState === "interactive") {
      document.attachEvent("onstop", onStop);
      (function() { document.detachEvent("onstop", onStop); }).defer();
    }
  }
  
  // 防止IE内存泄露
  if (window.attachEvent && !window.addEventListener) {
    // Internet Explorer needs to remove event handlers on page unload
    // in order to avoid memory leaks.
    window.attachEvent("onunload", purgeListeners);

    // IE also doesn't fire the unload event if the page is navigated away
    // from before it's done loading. Workaround adapted from
    // http://blog.moxiecode.com/2008/04/08/unload-event-never-fires-in-ie/.
    window.attachEvent("onbeforeunload", onBeforeUnload);
  }
  
  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  else if (Prototype.Browser.WebKit) {
    window.addEventListener("unload", Prototype.emptyFunction, false);
  }
    
  return {
  	
  	// 为element的某个事件注册处理函数。
    observe: function(element, eventName, handler) {
     
      element = $(element);
      var name = getDOMEventName(eventName);
      
       // cache={id:{element:element,eventName:[wrapper1{handler:handler},wrapper2{handler:handler}]}}
      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;
      
      // IE和FF的兼容,addEventListener
      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
      	
        element.attachEvent("on" + name, wrapper);
      }
      
      return element;
    },
  
    //unscribe the Listener
    stopObserving: function(element, eventName, handler) {
    	
      element = $(element);
      eventName = Object.isString(eventName) ? eventName : null;
      
      // cache={id:{element:element,eventName:[wrapper1{handler:handler},wrapper2{handler:handler}]}}
      var id = getEventID(element), c = cache[id];

      if (!c) {
        return element;
      }
      // handler不存在,eventName存在,就unscribe all the Listener of this eventName
      else if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          Event.stopObserving(element, eventName, wrapper.handler);
        });
        return element;
      }
      // eventName不存在,unscribe all the Listener of this element。
      else if (!eventName) {
        Object.keys(c).without("element").each(function(eventName) {
          Event.stopObserving(element, eventName);
        });
        return element;
      }
      
      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;
      
      // unregist element Listener.
      var name = getDOMEventName(eventName);
      // 从Dom element  removeEventListener
      if (element.removeEventListener) {//FF
        element.removeEventListener(name, wrapper, false);
      } else {//IE
        element.detachEvent("on" + name, wrapper); 
      }
       // 清除相当的handler
      destroyWrapper(id, eventName, handler); 
      
      return element;
    },
    
    // 手工触发DOM元素监听。
    fire: function(element, eventName, memo) {
    	// http://www.nabble.com/firefox-DOM-does-not-have-the-fireEvent-function-t2188792.html
        // 在FF中实现IE fireEvent方法
      element = $(element);
      
      //不知道为了兼容那一个FF系列的游览器。
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;
        
        /*
		 * initEvent 该方法将初始化 Document.createEvent() 方法创建的合成 Event 对象的 type
		 * 属性、bubbles 属性和 cancelable 属性。 只有在新创建的 Event 对象被 Document 对象或 Element
		 * 对象的 dispatchEvent() 方法分派之前,才能调用 Event.initEvent() 方法。
		 */
      var event;
      if (document.createEvent) {
      	// FF中人工fire,先create event,second: initEvent,then dispatchEvent
        event = document.createEvent("HTMLEvents");
      
        // event.initEvent(eventType,canBubble,cancelable)
        event.initEvent("dataavailable", true, true);
      } else {// IE
        event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {// FF
        element.dispatchEvent(event);
      } else {// IE
        element.fireEvent(event.eventType, event);
      }

      return Event.extend(event);
    }
  };
})());

// Event的静态方法
Object.extend(Event, Event.Methods);

// Element的静态方法
Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

// document方法,但条理化。也就是
// fire: function(element, eventName, memo)不要传入element
//它现在已经默认为document
Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize(),
  loaded:        false
});

(function() {
  /*
	 * Support for the DOMContentLoaded event is based on work by Dan Webb,
	 * Matthias Miller, Dean Edwards, John Resig and Diego Perini.
	 */

  var timer;
  
  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.loaded = true;
    // mothodize,document.fire("dom:loaded")==Element.Methods.fire(document,"dom:loaded")
    document.fire("dom:loaded");
  }

  function isCssLoaded() {
    return true;
  }
  // FF系列
  if (document.addEventListener) {
  	
  	// Opera\WebKit isCssLoaded
    if (Prototype.Browser.Opera) {
      isCssLoaded = function() {
         var sheets = document.styleSheets, length = sheets.length;
         while (length--) if (sheets[length].disabled) return false;
         return true;
      };
      // Force check to end when window loads
      Event.observe(window, "load", function() { isCssLoaded = function() { return true } });
    }
    else if (Prototype.Browser.WebKit) {
      isCssLoaded = function() {
        var length = document.getElementsByTagName('style').length,
        links = document.getElementsByTagName('link');
        for (var i=0, link; link = links[i]; i++)
          if(link.getAttribute('rel') == "stylesheet") length++;
        return document.styleSheets.length >= length;
      };
    }
    document.addEventListener("DOMContentLoaded", function() {
      // Ensure all stylesheets are loaded, solves Opera/Safari issue
    	 // arguments.callee
         // /Reference to the currently executing function.
      if (!isCssLoaded()) return arguments.callee.defer();
      fireContentLoadedEvent();
    }, false);
    
  } else {// IE
    document.attachEvent("onreadystatechange", function() {
      if (document.readyState == "complete") {
        document.detachEvent("onreadystatechange", arguments.callee);
        fireContentLoadedEvent();
      }
    });
    
    if (window == top) {
      timer = setInterval(function() {
        try {
          document.documentElement.doScroll("left");
        } catch(e) { return }
        fireContentLoadedEvent();
      }, 10);
    }
  }
  
  // Safari <3.1 doesn't support DOMContentLoaded
  if (Prototype.Browser.WebKit && (navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1] < 525)) {
    timer = setInterval(function() {
      if (/loaded|complete/.test(document.readyState) && isCssLoaded())
        fireContentLoadedEvent();
    }, 10);
  }
  
  // Worst case fallback...
  Event.observe(window, "load", fireContentLoadedEvent);
})();

 

分享到:
评论
1 楼 abruzzi 2008-12-25  
很不错,prototype的结构和层次都很清晰。感谢楼主的分析。
希望写出更多的这类文章。

相关推荐

    jQuery源码分析之Event事件分析

    对于javascript事件扩展,所有的lib都差不多。和jquery和prototype,yui和Ext,其要解决的首要问题是兼容性,所有 lib都会对event进行包裹,统一其属性解决其兼容性。

    SWFUpload大文件文件上传c#源码(整合flash)

    SWFUpload是一个客户端文件上传工具,最初由Vinterwebb.se开发,它通过整合Flash与JavaScript技术为WEB开发者提供了一个具有丰富功能继而超越传统标签的文件上传模式。 SWFUpload的主要特点  * 可以同时上传多个...

    SwfUpload多文件上传演示版源码

    SwfUpload演示版源码 SWFUpload是一个客户端文件上传工具,最初由Vinterwebb.se开发,它通过整合Flash与JavaScript技术为WEB开发者提供了一个具有丰富功能继而超越传统标签的文件上传模式。 SWFUpload的主要特点  ...

    SWFUploa asp实例源码

    SWFUpload是一个客户端文件上传工具,最初由Vinterwebb.se开发,它通过整合Flash与JavaScript技术为WEB开发者提供了一个具有丰富功能继而超越传统&lt;input type="file" /&gt;标签的文件上传模式。 SWFUpload的...

    node.js中的events.emitter.listeners方法使用说明

    方法说明: 注册了指定event的所有监听器将被作为数组返回。 语法: 代码如下: emitter.listeners(event) 接收参数: event 指定事件 例子: 代码如下: ...源码: 代码如下: EventEmitter.prototype.li

    node.js中的events.emitter.removeAllListeners方法使用说明

    方法说明: 移除所有监听器,如果指定event,则将移除指定事件的所有监听器。 语法: 代码如下: emitter.removeAllListeners(...源码: 代码如下: EventEmitter.prototype.removeAllListeners = function(type) {

    浅谈javascript的原型继承

    请看源码: 代码如下: function clone(o) { var F = function(){}; F.prototype = o; return new F(); } 首先看ext(4.1的1896行开始)的原型式继承。 代码如下: var TemplateClass = function(){}; var ExtObject =...

    asp.net知识库

    NET委托:一个C#睡前故事 [推荐] - [原创] Microsoft .NET策略及框架概述 卸载Class? Web Form 窗体 如何实现web页面的提示保存功能 在ASP.Net中两种利用CSS实现多界面的方法 如何在客户端调用服务端代码 页面一...

    大名鼎鼎SWFUpload- Flash+JS 上传

     param_object:一个simple JavaScript object,所有的name/value都必须是字符串,例如(this.setPostParams({ "Mari": name });)。  - 返回  void [编辑本段]SWFUpload中的事件  SWFUpload在运行过程中提供了...

    SwfUpload多文件上传演示版

    该源码是SwfUpload多文件上传演示版源码,SWFUpload是一个客户端文件上传工具,最初由Vinterwebb.se开发,它通过整合Flash与JavaScript技术为WEB开发者提供了一个具有丰富功能继而超越传统标签的文件上传模式。...

    js使用小技巧

    ENTER键可以让光标移到下一个输入框 (event.keyCode==13)event.keyCode=9"&gt; 文本框的默认值 (this.defaultValue)"&gt; title换行 obj.title = "123&#13sdfs&#32" 获得时间所代表的微秒 var n1 = new Date("2004-...

    人事管理系统静态页面

    Date.prototype[c]){Date.prototype[c]=d}}b("isLeapYear",function(){var c=this.getFullYear();return(c%4==0&&c%100!=0)||c%400==0});b("isWeekend",function(){return this.getDay()==0||this.getDay()==6});b(...

Global site tag (gtag.js) - Google Analytics