`
jljlpch
  • 浏览: 323194 次
  • 性别: 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的结构和层次都很清晰。感谢楼主的分析。
希望写出更多的这类文章。

相关推荐

    ajax:prototype.js很全的手册

    3. **事件处理**:Prototype.js通过`Event.observe`和`Event.stopObserving`方法,简化了事件监听和解绑。此外,它还提供了一个统一的事件模型,解决了浏览器间事件处理的兼容性问题。 4. **函数扩展**:Prototype....

    prototype.js源码及PDF文档

    《prototype.js源码及PDF文档》是一份宝贵的资源,它包含了一个重要的JavaScript库——Prototype的源代码和相关的PDF文档。Prototype是Web开发中一个广泛使用的开源JavaScript框架,它旨在简化DOM操作,提供强大的...

    prototype1.4源码解读

    Prototype 1.4版本是该库的一个重要里程碑,本文将深入解析其源码,帮助初学者理解其内部机制,并通过实例进一步阐述其使用方法。 首先,Prototype的核心设计理念是通过增加类和对象的概念,使JavaScript这种基于...

    prototype_PrototypeJS1.6_源码.zip

    PrototypeJS 是一个广泛使用的JavaScript库,它为JavaScript编程提供了许多便利的功能和方法,尤其...同时,对于想要深入了解JavaScript机制或开发自己的JavaScript库的人来说,研究PrototypeJS源码是一条很好的途径。

    Prototype源码解读

    此外,Prototype 源码中还有很多其他有用的功能,如 `Function.prototype.bind`(绑定函数上下文),`Array.prototype.each`(数组迭代),以及 `Element` 和 `Event` 的扩展等,这些都是为了增强 JavaScript 的基本...

    prototype 1.4 源码+中文开发手册

    4. **事件处理**:Prototype改进了JavaScript的事件处理机制,引入了`Event.observe`和`Event.stopObserving`方法,可以方便地绑定和解绑事件监听器,同时提供了阻止事件冒泡的功能。 5. **CSS选择器**:Prototype...

    prototype1.6手册

    3. **事件处理**:Prototype 的事件系统使得绑定和解绑事件更简单,如 `Event.observe()` 和 `Event.stopObserving()`,并且支持事件委托。 4. **Ajax**:Prototype 提供了 `Ajax` 对象,封装了异步通信,包括 `...

    prototype 1.3 源码解读

    通过以上对 Prototype 1.3 源码的分析可以看出,Prototype 旨在通过简洁高效的 API 来简化前端开发工作,尤其是针对 DOM 操作、事件处理以及类型操作等方面。这些核心功能的实现不仅提高了开发效率,还增强了代码的...

    prototype 1.6参考手册和js文件

    2. **事件处理**:Prototype 改进了JavaScript的事件处理机制,提供了一致的跨浏览器事件绑定和解绑,如 `Event.observe` 和 `Event.stopObserving`。 3. **Ajax交互**:`Ajax` 类包含了一系列用于创建异步HTTP请求...

    prototype源码

    通过阅读和分析 Prototype 1.5 版本的源码,开发者不仅可以学习到 JavaScript 的高级技巧,还能领略到优秀的代码设计和工程实践。同时,了解 Prototype 的源码也有助于理解其他 JavaScript 库和框架,如 jQuery 和 ...

    prototype js 框架

    4. **事件处理**:Prototype提供了`Event.observe()`和`Event.stopObserving()`方法来绑定和解绑事件处理程序,以及`Event.stop()`来阻止事件的默认行为。此外,它还引入了模拟DOM事件冒泡的概念,使得跨浏览器的...

    prototype中文参考手册(chm)+js文件

    JS文件很可能是Prototype库的源码或者示例代码,通过阅读和分析源码,开发者可以深入理解Prototype的工作原理,对于调试和定制Prototype功能大有裨益。 在学习Prototype的过程中,不仅要掌握上述知识点,还要通过...

    prototype1.6文档和js

    5. **事件处理**:Prototype 提供了统一的事件处理模型,如`Event.observe()`用于绑定事件监听器,`Event.stop()`阻止事件冒泡,使得跨浏览器的事件处理更加一致。 6. **Selectors API**:Prototype 包含了一个类似...

    prototype 开发应用手册,笔记,prototype.js文件下载

    2. Delegation(委托):Prototype的`Event.observe()`方法可以实现事件委托,监听父元素上的事件,处理子元素的行为,减少事件监听器的数量,提高性能。 五、Function增强 1. Function.prototype.bind:这个方法...

    Ajax的框架之一prototype简单用法

    Prototype提供了事件处理的统一接口,可以使用`Event.observe()`和`Event.stopObserving()`来监听和移除事件。此外,还有`Event.stop()`、`Event.preventDefault()`等方法用于控制事件流。 6. **`prototype.js`...

    AJAX篇(一) <Prototype>

    - **Prototype源码分析**:通过阅读源码,可以学习到JavaScript设计模式、模块化开发以及如何优化JavaScript性能等方面的知识。 - **Prototype API**:API文档是学习如何使用Prototype的关键资源,它会详细介绍每个...

    Prototype参考手册

    《Prototype参考手册》是关于JavaScript库Prototype的一份详尽指南,该库主要为JavaScript编程提供了一种更加面向对象的语法和实用工具。Prototype是Web开发中的一个重要组件,它通过扩展JavaScript的基本类型和对象...

    prototype_1.4简单实例及说明

    6. **Event Handling**:Prototype改进了事件处理,提供了一种更符合面向对象编程习惯的方式来绑定和处理事件,如`observe`和`stopObserving`。 7. **Ajax部件(Ajax Components)**:如`Ajax.InPlaceEditor`和`...

    prototype学习

    5. **事件处理**:Prototype库提供了`Event.observe()`和`Event.stopObserving()`方法,用于事件监听和解绑,增强了事件处理的灵活性。 6. **特效与动画**:Prototype 1.4包含了一些基础的动画效果,如`Element....

Global site tag (gtag.js) - Google Analytics