zoukankan      html  css  js  c++  java
  • jQuery源码学习笔记九

    最近几天搞了一个基于事件代理的事件系统,但即便是事件代理还是要依赖于事件注册,因此深入研究了jQuery的事件系统,整理出来分享一下。

    由于IE与标准浏览器闹别扭,我们通过虽然弄一个叫addEvent的函数来屏蔽差异。以下就是一个经典的addEvent函数:

    var addEvent = function( obj, type, fn ) {
        if (obj.addEventListener)
            obj.addEventListener( type, fn, false );
        else if (obj.attachEvent) {
            obj["e"+type+fn] = fn;
            obj.attachEvent( "on"+type, function() {
                obj["e"+type+fn]();
            } );
        }
    };
    

    但这简洁函数有许多缺点,如不能处理IE下绑定的回调函数的执行顺序问题,也根本无法消除事件对象的差异。于是有

     
    //http://dean.edwards.name/weblog/2005/10/add-event/
    //http://dean.edwards.name/weblog/2005/10/add-event2/
    function addEvent(element, type, handler) {
        // assign each event handler a unique ID
        //在每个回调函数上绑定了一个UUID
        if (!handler.$$guid) handler.$$guid = addEvent.guid++;
        // create a hash table of event types for the element
        //在要绑定事件的元素节点上设置一个特殊的属性,用来储存事件
        if (!element.events) element.events = {};
        // create a hash table of event handlers for each element/event pair
        //evets函数的键名为事件的类型名,或者说把事件按类型来按理
        var handlers = element.events[type];
        if (!handlers) {
            handlers = element.events[type] = {};
            // store the existing event handler (if there is one)
            if (element["on" + type]) {
                handlers[0] = element["on" + type];//DOM1.0
            }
        }
        // store the event handler in the hash table
        //让一个类型对应多个回调函数
        handlers[handler.$$guid] = handler;
        // assign a global event handler to do all the work
        element["on" + type] = handleEvent;
    };
    // a counter used to create unique IDs
    addEvent.guid = 1;
     
    function removeEvent(element, type, handler) {
        // delete the event handler from the hash table
        if (element.events && element.events[type]) {
            //移除当前类型对应的某个回调函数
            delete element.events[type][handler.$$guid];
        }
    };
     
    function handleEvent(event) {
        var returnValue = true;
        // grab the event object (IE uses a global event object)
        event = event || fixEvent(window.event);
        // get a reference to the hash table of event handlers
        var handlers = this.events[event.type];
        // execute each event handler
        for (var i in handlers) {
            this.$$handleEvent = handlers[i];
            if (this.$$handleEvent(event) === false) {
                returnValue = false;
            }
        }
        return returnValue;
    };
     
    function fixEvent(event) {
        // add W3C standard event methods
        event.preventDefault = fixEvent.preventDefault;
        event.stopPropagation = fixEvent.stopPropagation;
        return event;
    };
    fixEvent.preventDefault = function() {
        this.returnValue = false;
    };
    fixEvent.stopPropagation = function() {
        this.cancelBubble = true;
    };
    

    完美的解决了IE的执行顺序问题,并为IE的事件对象添加了两个方法preventDefault与stopPropagation,jQuery的事件系统就是基于它发展而来。下面jQuery1.0.1的代码:

     
    event: {
        // Bind an event to an element
        // Original by Dean Edwards
        add: function (element, type, handler) {
            // For whatever reason, IE has trouble passing the window object
            // around, causing it to be cloned in the process
            if (jQuery.browser.msie && element.setInterval != undefined) element = window;
            // Make sure that the function being executed has a unique ID
            if (!handler.guid) handler.guid = this.guid++;
            // Init the element's event structure
            if (!element.events) element.events = {};
            // Get the current list of functions bound to this event
            var handlers = element.events[type];
            // If it hasn't been initialized yet
            if (!handlers) {
                // Init the event handler queue
                handlers = element.events[type] = {};
                // Remember an existing handler, if it's already there
                if (element["on" + type]) handlers[0] = element["on" + type];
            }
    
            // Add the function to the element's handler list
            handlers[handler.guid] = handler;
    
            // And bind the global event handler to the element
            element["on" + type] = this.handle;
            //上面基本和DC大神的一致
            // Remember the function in a global list (for triggering)
            if (!this.global[type]) this.global[type] = [];
            this.global[type].push(element);
        },
    
        guid: 1,
        global: {},
    
        // Detach an event or set of events from an element
        remove: function (element, type, handler) {
            if (element.events) if (type && element.events[type]) if (handler) delete element.events[type][handler.guid];
            else for (var i in element.events[type]) delete element.events[type][i];
            else for (var j in element.events) this.remove(element, j);
        },
        //触发,为什么不叫fire呢?!
        trigger: function (type, data, element) {
            // Touch up the incoming data
            data = data || [];
    
            // Handle a global trigger
            if (!element) {
                var g = this.global[type];
                if (g) for (var i = 0; i < g.length; i++) this.trigger(type, data, g[i]);
                // Handle triggering a single element
            } else if (element["on" + type]) {
                // Pass along a fake event
                data.unshift(this.fix({
                    type: type,
                    target: element
                }));
                // Trigger the event
                element["on" + type].apply(element, data);
            }
        },
    
        handle: function (event) {
            if (typeof jQuery == "undefined") return;
            event = event || jQuery.event.fix(window.event);
            // If no correct event was found, fail
            if (!event) return;
            var returnValue = true;
            var c = this.events[event.type];
            for (var j in c) {
                if (c[j].apply(this, [event]) === false) {
                    event.preventDefault();
                    event.stopPropagation();
                    returnValue = false;
                }
            }
            return returnValue;
        },
    
        fix: function (event) {
            if (event) {
                event.preventDefault = function () {
                    this.returnValue = false;
                };
                event.stopPropagation = function () {
                    this.cancelBubble = true;
                };
            }
            return event;
        }
    }
    

    我们来看一个这个经典的基于事件注册的事件系统,几年前主流的事件系统基于是这个样子。首先设置一个或几个顶层对象,用于管理事件句柄与相关的东西,这里正如我们看到的那样,是用一个叫global的对象。它装载的是元素,因为它是基于事件注册,回调函数都是直接绑定在元素上,后来IE7把内存泄漏的问题放大后,jQuery进一步改进,在unload时把这些注册了事件的元素上面事件全部去掉,现在还没有。在这些元素上有一个叫events的自定义属性,它是一个对象,按事件类弄型管理绑定在它上面的回调函数,目的是让事件按绑定时的顺序执行。当我们触发事件时,并不是直接执行我们绑定的回调函数,因为这里用的是DOM0的事件方式,无论绑定多少个同类型的事件,最后都只一个此类型的。因此都把它们放到一个handle函数中(DE大神的handleEvent)。handle做了三件事,让事件对象总是作为函数的第一个参数,改造事件对象,按顺序执行既定的回调函数。最后我们留意到它有返回值,这是用来决定它是否执行浏览器的默认行为。

    我没有闲情把它所有的版本都看了,只看了几个版本,jQuery1.0.4基于还是那个样子。到jQuery1.1增加了几种绑定方式,著名的one,同时对toggle ,hover与ready进行大幅改进。在jQuery中,一个方法基本上都有两个版本,一个jQuery命名空间的静态方法,另一个是jQuery对象的实例方法。实例方法都是对应复数个元素(因为一个jQuery往往包含几个DOM元素),而静态方法基于上是对应一个。实例方法都是往外围调用这些静态方法,因此静态方法的地位相当高。改进的重点都是这些静态方法,因此我重点讲它们。有兴趣可以下jQuery1.1来看看,这时John Resig开始着手解决事件对象的差异问题,为IE的事件对象添加了pageX与pageY与target 属性。unbind与one事件实现得相当傻瓜,因为事件都是用顶层对象管理,把顶层对象的事件删掉,就是unbind了,删除后用一个拷收贝继续执行就是one。hover由于还没有事先搞定relatedTarget ,因此有点复杂。

    由于事件系统是个复杂的东西,我还没有开始讲jQuery最新版本的情形,就已达这样的篇幅了。最后我总结下jQuery的事件运行流程吧:用户为元素绑定事件(bind)=>add=>为回调函数设置UUID=>交由顶层对象管理=>handle=>fix=>开始等待用户触发事件或直接调用trigger 。

  • 相关阅读:
    jdbc-------JDBCUtil类 工具类
    jdbc --- javabean
    MapReduce 找出共同好友
    mapReducer 去重副的单词
    用户定义的java计数器
    mapReducer第一个例子WordCount
    win10 Java环境变量,hadoop 环境变量
    Writable序列化
    io 流操作hdfs
    [常用命令]OSX命令
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1627746.html
Copyright © 2011-2022 走看看