JavaScript的事件

目录

一、JavaScript事件的概念

二、事件流

2.1、事件冒泡

2.2、事件捕获

2.3、DOM事件流

三、事件处理程序

3.1、HTML事件处理程序

3.2、DOM0级事件处理程序

3.3、DOM2级事件处理程序

3.4、IE事件处理程序

3.5、跨浏览器的事件处理程序

四、事件对象

4.1、DOM中的事件对象

4.2、IE中的事件对象

4.3、跨浏览器的事件对象

五、事件类型

5.1、UI事件

5.2、焦点事件

5.3、鼠标事件

5.4、键盘与文本事件

5.5、变动事件

5.6、HTML5事件

六、内存和性能

6.1、事件委托

6.2、移除事件处理程序

七、模拟事件

7.1、事件模拟的基本步骤

7.2、模拟鼠标事件

7.3、模拟键盘事件

7.4、模拟变动事件

7.5、自定义DOM事件

7.6、IE中的事件模拟


一、JavaScript事件的概念

    JavaScript与HTML之间的交互是通过事件实现的。

    事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。

    可以使用监听器(或事件处理程序)来预订事件,以便事件发生时执行相应的代码。

二、事件流

    事件流描述的是从页面中接收事件的顺序。

    有两种事件流,事件冒泡事件捕获

2.1、事件冒泡

    事件冒泡即事件开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

2.2、事件捕获

    事件捕获即不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。

2.3、DOM事件流

    "DOM2级事件“规定的事件流包括三个阶段:事件捕获阶段处于目标阶段事件冒泡阶段

三、事件处理程序

3.1、HTML事件处理程序

    某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。

    <!-- 输出 "Click Me" -->
    <input type="button" value="Click Me" onclick="alert(this.value)" />

    在这个函数内部,this值等于事件的目标元素。

    这种方式绑定事件处理程序有如下缺点:

  • 触发事件时相应的JavaScript代码可能还没加载完
  • HTML与JavaScript代码紧密耦合

3.2、DOM0级事件处理程序

    可以将一个函数赋值给一个事件处理程序属性来指定事件处理程序。

        var btn = document.getElementById("myBtn");
        btn.onclick = function(){
            alert(this.id);             //"myBtn"
        }

    这时候的事件处理程序是在元素的作用域中运行,即程序中的this引用当前元素。

        btn.onclick = null;        //删除事件处理程序

    将事件处理程序属性的值设置为null,就可以删除指定的事件处理程序。

3.3、DOM2级事件处理程序

    "DOM2级事件"定义了指定和删除事件的操作:addEventListener()removeEventListener()

        var handler = function(){           //事件处理程序
            alert(this.id);
        };
        var btn = document.getElementById("myBtn");
        btn.addEventListener("click", handler, false);          //指定事件
        bt.removeEventListener("click", handler, false);        //删除事件

    最后的布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

    另外,传入removeEventListener()中的事件处理程序函数必须与传入addEventListener()中的相同。

3.4、IE事件处理程序

    IE8及更早版本使用attachEvent()deachEvent()

    由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。

        var handler = function(){           //事件处理程序
            alert(this === window);         //true
        };
        var btn = document.getElementById("myBtn");
        btn.attachEvent("onclick", handler)         //指定事件
        btn.detachEvent("onclick", handler);        //删除事件

    在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this等于window

3.5、跨浏览器的事件处理程序

    为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的JavaScript库。

        var EventUtil = {
            addHandler: function(element, type, handler){
                if(element.addEventListener){
                    element.addEventListener(type, handler, false);
                }else if(element.attachEvent){
                    element.attachEvent('on' + type, handler);
                }else{
                    element['on' + type] = handler;
                }
            },
            removeHandler: function(element, type, handler){
                if(element.removeEventListener){
                    element.removeEventListener(type, handler, false);
                }else if(element.detachEvent){
                    element.detachEvent('on' + type, handler);
                }else{
                    element['on' + type] = null;
                }
            }
        };

 

四、事件对象

    在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。

    所有的浏览器都支持event对象,但支持方式不同。

4.1、DOM中的事件对象

    触发的事件类型不一样,可用的属性和方法也不一样。不过所有事件都会有下表列出的成员:

4.2、IE中的事件对象

    IE的event对象同样也包含与创建它的事件相关的属性和方法。

4.3、跨浏览器的事件对象

    在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在

        var btn = document.getElementById('myBtn');
        btn.onclick = function(){
            var event = window.event;
            alert(event.type);      //"click"
        }

    虽然DOM和IE中的event对象不同,但基于它们之间的相似性依旧可以拿出跨浏览器的方案来:

        var EventUtil = {
            addHandler: function(element, type, handler){
                //省略的代码
            },
            removeHandler: function(element, type, handler){
                //省略的代码
            },
            getEvent: function(event){                  //获取事件对象
                return event || window.event;
            },
            getTarget: function(event){                 //获取事件源
                return event.target || event.srcElement;
            },
            preventDefault: function(event){            //阻止默认事件
                if(event.preventDefault){
                    event.preventDefault();
                }else{
                    event.returnValue = false;
                }
            },
            stopPropagation: function(event){           //禁止事件冒泡
                if(event.stopPropagation){
                    event.stopPropagation();
                }else{
                    event.cancelBubble = true;
                }
            }
        };

    测试一下这个代码库:

        var btn = document.getElementById('myBtn');
        btn.onclick = function(){
            event = EventUtil.getEvent(event);          //获取事件对象
            var target = EventUtil.getTarget(event);    //获取事件源
            EventUtil.preventDefault(event);            //阻止默认事件
            EventUtil.stopPropagation(event);           //禁止冒泡
        }

五、事件类型

5.1、UI事件

    UI事件指的是那些不一定与用户操作有关的事件。

    load事件

    当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。

    有两种方式定义load事件处理程序:

  • 使用addEventListener()方法
        window.addEventListener("load", function(){
            alert("Loaded!")
        }, false);

    根据"DOM2级事件"规范,应该在document而非window上面触发load事件。但是,所有浏览器都在window上面实现了该事件,以确保向后兼容。

  • 使用HTML特性
<body onload="alert('Loaded!')">

    unload事件

    与load事件对应的是unload事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。

    resize事件

    当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。

    这个事件在window上面触发。

        window.addEventListener("resize", function(){
            alert("Resized");
        });   

    scroll事件

    scroll事件会在文档被滚动期间触发,该事件是在window对象上发生的。

        window.addEventListener("scroll", function(){
            alert("scroll");
        });    

5.2、焦点事件

    焦点事件会在页面元素获得或失去焦点时触发。

  • focus    ——    在元素获得焦点时触发,不冒泡。
  • blur    ——     在元素失去焦点时触发,不冒泡。
  • focusin    ——    在元素获得焦点时触发,冒泡
  • focusout    ——    在元素失去焦点时触发,冒泡

5.3、鼠标事件

  • mousedown    ——    在用户按下了任意鼠标按钮时触发
  • mouseup    ——    在用户释放鼠标按钮时触发
  • click    ——    在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发
  • dblclick    ——    在用户双击主鼠标按钮(一般是左边的按钮)时触发
  • mouseenter    ——    在鼠标从元素外部首次移动到元素范围之内时触发,不冒泡
  • mouseleave    ——    在位于元素上方的鼠标光标移动元素范围之外时触发,不冒泡,而且在光标移动到后代元素上不会触发。
  • mouseover    ——    在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。
  • mouseout    ——    在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发,另一个元素可能是这个元素的子元素。

    鼠标事件都是在浏览器视口中的特定位置上发生的,这个位置信息保存在事件对像中:

        var btn = document.getElementById('myBtn');
        btn.addEventListener('click', function(event){
            console.log("Client coordinates: " + event.clientX + "," + event.clientY);  //客户区坐标位置
            console.log("Page coordinates: " + event.pageX + "," + event.pageY);        //页面坐标位置
            console.log("Screen coordinates: " + event.screenX + "," + event.screenY);  //屏幕坐标位置
        }, false);

    在按下鼠标时键盘上的某些键的状态也可以影响到所要采取的操作。

    修改键

    DOM为此规定了4个属性保存在event对象中,表示这些修改键的状态:

  • shiftKey
  • ctrlKey
  • altKey
  • metaKey

    当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否同时按下了其中的键。

    相关元素

    DOM通过event对象的relatedTarget属性提供了相关元素的信息,这个属性只对于mouseovermouseout事件才包含值。

    IE8之前版本不支持relatedTarget属性,但提供了fromElement属性和toElement属性保存着同样的信息

        var EventUtil = {
            //省略了其他代码
            getRelatedTarget: function(event){
                if(event.relatedTarget){
                    return event.relatedTarget;
                }else if(event.toElement){
                    return event.toElement;
                }else if(event.fromElement){
                    return event.fromElement;
                }else{
                    return null;
                }
            }
        };
        var myBtn = document.getElementById("myBtn");
        EventUtil.addHandler(myBtn, "mouseout", function(event){
            event = EventUtil.getEvent(event);
            var target = EventUtil.getTarget(event);
            var relatedTarget = EventUtil.getRelatedTarget(event);
            console.log("Moused out of " + target.tagName + " to " + relatedTarget.tagName);
        });

    鼠标按钮

    只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发click事件,因此检测按钮的信息并不是必要的。

    但对于mousedownmouseup事件来说,则在其event对象存在一个button属性,表示按下或释放的按钮。

    DOM的button属性可能有如下3个值:

  • 0表示鼠标左键
  • 1表示中间的鼠标按钮
  • 2表示鼠标右键

    IE8及之前版本也提供了button属性,但这个属性的值与DOM的button属性有很大差异:

  • 0表示没有按下按钮
  • 1表示按下了主鼠标按钮
  • 2表示按下了次鼠标按钮
  • 3表示同时按下了主、次鼠标按钮
  • 4表示按下了中间的鼠标按钮
  • 5表示同时按下了主鼠标按钮和中间的鼠标按钮
  • 6表示同时按下了次鼠标按钮和中间的鼠标按钮
  • 7表示同时按下了三个鼠标按钮
        var EventUtil = {
            //省略了其他代码
            getButton: function(event){
                if(document.implementation.hasFeature("MouseEvents", "2.0")){
                    return event.button;
                }else{
                    switch(event.button){
                        case 0:
                        case 1:
                        case 3:
                        case 5:
                        case 7:
                            return 0;
                        case 2:
                        case 6:
                            return 2;
                        case 4:
                            return 1;
                    }
                }
            }
        };

    通过检测“MouseEvents"这个特性,就可以确定event对象中存在的button属性中是否包含正确的值。

        var myBtn = document.getElementById("myBtn");
        EventUtil.addHandler(myBtn, "mousedown", function(event){
            event = EventUtil.getEvent(event);
            console.log(EventUtil.getButton(event));
        });

    鼠标滚轮事件

    当用户通过鼠标滚轮与页面交互、在垂直方向上滚动页面时,就会触发mousewheel事件。

    这个事件可以在任何元素上面触发,最终会冒泡到document(IE8)或window(IE9、Opera、Chrome及Safari)对象。

    与mousewheel事件对应的event对象除包含鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta属性。

    当用户向前滚动鼠标滚轮时,wheelDelta是120的倍数,当用户向后滚动鼠标滚轮时,wheelDelta是-120的倍数。

        EventUtil.addHandler(window, "mousewheel", function(event){
            event = EventUtil.getEvent(event);
            console.log(event.wheelDelta);
        });

5.4、键盘与文本事件

    有3个键盘事件:

  • keydown    ——    当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
  • keypress    ——    当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。
  • keyup    ——    当用户释放键盘上的键时触发。

    在用户按了一下键盘上的字符键时,首先会触发keydown事件,然后紧跟着是keypress事件,最后会触发keyup事件。

    其中,keydownkeypress都是在文本框发生变化之前被触发的;而keyup事件则是在文本框已经发生变化之后被触发的。

    如果用户按下的是一个非字符键,那么首先会触发keydown事件,然后就是keyup事件。

    键盘事件与鼠标事件一样,都支持相同的修改键。而且,键盘事件的事件对象中也有shiftKeyctrlKey、altKey和metaKey属性。

    键码

    在发生keydownkeyup事件时,event对象的keyCode属性中会包含一个代码,与键盘上被按下的键对应。

    对数字、字母字符键,keyCode属性的值与ASCII码中对应的编码相同。

    DOM和IE的event对象都支持keyCode属性。请看下面的示例:

        var textbox = document.getElementById("myText");
        EventUtil.addHandler(textbox, "keyup", function(event){
            event = EventUtil.getEvent(event);
            console.log(event.keyCode);
        });

    下表列出了所有非字符键的键码:

    字符编码

    发生keypress事件意味着按下的键会影响到屏幕中文本的显示。

    在所有浏览器中,按下能够插入或删除字符的键都会触发keypress事件。

    IE9、Firefox、Chrome和Safari的event对象都支持一个charCode属性,这个属性只有在发生keypress事件时才包含值,而且这个值是按下的那个键所代表字符的ASCII编码。

    要想以跨浏览器的方式取得字符编码,必须首先检测charCode属性是否可用,如果不可用则使用keyCode,如下所示:

        var EventUtil = {
            
            //省略的代码

            getCharCode: function(event){
                if(typeof event.charCode == "number"){
                    return event.charCode;
                }else{
                    return event.keyCode;
                }
            }
        }

    textInput事件

    "DOM3级事件"规范中引入了一个新事件,名叫textInput。当用户在可编辑区域中输入字符时,就会触发这个事件。

    textInput事件与keypress事件的区别之一就是任何可以获得焦点的元素都可以触发keypress事件,但只有可编辑区域才能触发textInput事件。

    区别之二是textInput事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。

    textInput事件的event对象中包含一个data属性,这个属性的值就是用户输入的字符(而非字符编码),例如:

        var textbox = document.getElementById("myText");
        EventUtil.addHandler(textbox, "textInput", function(event){
            event = EventUtil.getEvent(event);
            console.log(event.data);
        });

5.5、变动事件

    DOM2级的变动事件能在DOM中的某一部分发生变化时给出提示。DOM2级定义了如下变动事件:

  • DOMSubtreeModified    ——    在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。
  • DOMNodeInserted    ——    在一个节点作为子节点被插入到另一个节点中时触发。
  • DOMNodeRemoved    ——    在节点从其父节点中被移除时触发。
  • DOMNodeInsertedIntoDocument    ——    在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在DOMNodeInserted之后触发。
  • DOMNodeRemovedFromDocument    ——    在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved之后触发。
  • DOMAttrModified    ——    在特性被修改之后触发。
  • DOMCharacterDataModified    ——    在文本节点的值发生变化时触发。

    删除节点示例:

    html

    <ul id="myList">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>

    js

        EventUtil.addHandler(window, "load", function(event){
            var list = document.getElementById("myList");

            EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
                alert(event.type);          //"DOMSubtreeModified"
                alert(event.target);        //"[object HTMLBodyElement]"
            });

            EventUtil.addHandler(document, "DOMNodeRemoved", function(event){
                alert(event.type);          //"DOMNodeRemoved"
                alert(event.target);        //"[object HTMLUListElement]"
                alert(event.relatedNode);   //"[object HTMLBodyElement]"
            });

            EventUtil.addHandler(list.firstChild, "DOMNodeRemovedFromDocument", function(event){
                alert(event.type);          //"DOMNodeRemovedFromDocument"
                alert(event.target);        //[object Text]
            })

            list.parentNode.removeChild(list);
        })

    使用removeChild()删除list节点时,首先会触发DOMNodeRemoved事件,这个事件会冒泡,因此可以在DOM的任何层次上面处理它。这个事件的目标(event.target)是被删除的节点,而event.relatedNode属性中包含着对目标节点父节点的引用。

    DOMNodeRemoved事件触发之后,list节点及其所有子节点会相继触发DOMNodeRemovedFromDocument事件,但这个事件不会冒泡,所以只有直接指定给其中一个子节点的事件处理程序才会被调用。

    紧随其后触发的是DOMSubtreeModified事件。这个事件的目标是被移除的节点的父节点,这个事件会冒泡。

    插入节点示例:

    html

    <ul id="myList">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>

    js

        EventUtil.addHandler(window, "load", function(event){
            var list = document.getElementById("myList");
            var item = document.createElement("li");
            item.appendChild(document.createTextNode("Item 4"));

            EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
                alert(event.type);            //"DOMSubtreeModified"
                alert(event.target);          //[object HTMLUListElement]
                alert(event.relatedNode);     //null
            });

            EventUtil.addHandler(document, "DOMNodeInserted", function(event){
                alert(event.type);            //"DOMNodeInserted"
                alert(event.target);          //[object HTMLLIElement]
                alert(event.relatedNode);     //[object HTMLUListElement]
            });

            EventUtil.addHandler(item, "DOMNodeInsertedIntoDocument", function(event){
                alert(event.type);            //"DOMNodeInsertedIntoDocument"
                alert(event.target);          //[object HTMLLIElement]
            });

            list.appendChild(item);
        });

    使用appendChild()向DOM中插入节点时,首先会触发DOMNodeInserted事件,这个事件是冒泡的,因此可以在DOM的各个层次上处理它。这个事件的目标(event.target)是被插入的节点,而event.relatedNode属性中包含一个对父节点的引用。

    紧接着,会在新插入的节点上面触发DOMNodeInsertedIntoDocument事件。这个事件不冒泡。

    最后触发的事件是DOMSubtreeModified,触发于新插入节点的父节点。

5.6、HTML5事件

    DOM规范没有涵盖所有浏览器支持的所有事件,很多浏览器处于不同的目的——满足用户需求或解决特殊问题,还实现了一些自定义事件。HTML详尽列出了浏览器应该支持的所有事件。

    ①contextmenu事件

    contextmenu事件用于显示自定义上下文菜单(在Windows中,是右键单击;在Mac中,是Ctrl+单击)。

    contextmenu事件是冒泡的,因此可以为document指定一个事件处理程序,用以处理页面中发生的所有此类事件。

    示例:

    HTML

<body >
    <div id="myDiv">Right click or Ctrl+click me to get a custom context menu.
        Click anywhere else to get the default context menu.
    </div>
    <ul id="myMenu" style="position:absolute; visibility:hidden; background-color:siler;">
        <li><a href="http://www.nczonline.net">Nicholas' site</a></li>
        <li><a href="http://www.wrox.com">Wrox site</a></li>
        <li><a href="http://www.yahoo.com">Yahoo!</a></li>
    </ul>
</body>

    JS

        EventUtil.addHandler(window, "load", function(event){
            var div = document.getElementById("myDiv");

            EventUtil.addHandler(div, "contextmenu", function(event){
                event = EventUtil.getEvent(event);
                EventUtil.preventDefault(event);    //取消默认事件,以保证不显示浏览器默认的上下文菜单

                var menu = document.getElementById("myMenu");
                menu.style.left = event.clientX + "px";
                menu.style.top = event.clientY + "px";
                menu.style.visibility = "visible";  //显示自定义上下文菜单
            });

            EventUtil.addHandler(document, "click", function(event){
                document.getElementById("myMenu").style.visibility = "hidden";  //隐藏自定义上下文菜单
            })
        });

    ②beforeunload事件

    beforeunload事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面。

    beforeunload事件处理程序中,event.returnValue的值为要显示给用户的字符串(对IE及Firefox而言),同时作为函数的值返回(对Safari和Chrome而言),例如:

        EventUtil.addHandler(window, "beforeunload", function(event){
            event = EventUtil.getEvent(event);
            var message = "I'm really going to miss you if you go.";
            event.returnValue = message;
            return message;
        });

    ③DOMContentLoaded事件

    DOMContentLoaded事件在形成完整的DOM树之后就会触发,不理会图像、JavaScript文件、CSS文件或其他资源是否已经下载完毕。      例如:

        EventUtil.addHandler(document, "DOMContentLoaded", function(event){
            alert("Content loaded");
        });

    ④pageshow和pagehide事件

    

    ⑤haschange事件

    HTML5新增了haschange事件,以便在URL的参数列表(及URL中“#”号后面的所有字符串)发生变化时通知开发人员。

    haschange事件处理程序的event对象额外包括两个属性:oldURLnewURL,这两个属性分别保存着参数列表变化前后的完整URL。

        EvenUtil.addHandler(window, "haschange", function(event){
            alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL);
        });

 

六、内存和性能

    在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。

    导致这一问题的原因是:

  • 每个函数都是对象,都会占用内存
  • 事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间

    有一些方法能够提升性能:

6.1、事件委托

    事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

    例如:

    html

    <ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say hi</li>
    </ul>

    不使用事件委托绑定事件:

        var item1 = document.getElementById("goSomewhere");
        var item2 = document.getElementById("doSomething");
        var item3 = document.getElementById("sayHi");

        EventUtil.addHandler(item1, "click", function(event){
            location.href = "http://www.wrox.com";
        });

        EventUtil.addHandler(item2, "click", function(event){
            document.title = "I changed the document's title";
        });

        EventUtil.addHandler(item3, "click", function(event){
            alert("hi");
        });

    使用事件委托绑定事件:

        var list = document.getElementById("myLinks");

        EventUtil.addHandler(list, "click", function(event){
            event = EventUtil.getEvent(event);
            var target = EventUtil.getTarget(event);

            switch(target.id){
                case "doSomething":
                    document.title = "I changed the document's title";
                    break;
                case "goSomewhere":
                    location.href = "http://www.wrox.com";
                    break;
                case "sayHi":
                    alert("hi");
                    break;
            }
        });

6.2、移除事件处理程序

    在不需要的时候移除事件处理程序,能够增强性能。

    有两种情况会导致内存中留有不必要的事件处理程序:

    第一种情况是从文档中移除带有事件处理程序的元素,例如:

    HTML

    <div id="myDiv">
        <input type="button" value="Click Me" id="myBtn" />
    </div>

    JS

        var btn = document.getElementById("myBtn");
        btn.onclick = function(){

            //先执行某些操作
            
            document.getElementById("myDiv").innerHTML = "Processing...";  
        }

    在<div>元素上设置innerHTML可以把按钮移走,但事件处理程序仍然与按钮保持着引用关系。

    最好的办法是手工移除事件处理程序,例如:

        var btn = document.getElementById("myBtn");
        btn.onclick = function(){

            //先执行某些操作

            btn.onclick = null;         //移除事件处理程序
            
            document.getElementById("myDiv").innerHTML = "Processing...";  
        }

    第二种情况就是卸载页面的时候,如果在页面被卸载之前没有清理干净事件处理程序,那么它们就会滞留在内存中。

    一般来说,最好的做法是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。

七、模拟事件

    可以使用JavaScript在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的事件一样。

    这些事件该冒泡还会冒泡,而且照样能够导致浏览器执行已经指定的处理它们的事件处理程序。

    DOM规范规定了模拟特定事件的方式,IE9、Opera、Firefox、Chrome和Safari都支持这种方式,IE8及之前版本有它自己模拟事件的方式。

7.1、事件模拟的基本步骤

    ①创建event对象

    在document对象上使用createEvent()方法创建event对象。

    这个方法接收一个参数,即表示要创建的事件类型的字符串,可以是下列几个字符串之一:

  • UIEvents    ——    一般化的UI事件
  • MouseEvents    ——    一般化的鼠标事件
  • KeyboardEvent    ——    一般化的键盘事件
  • MutationEvents    ——    一般化的DOM变动事件
  • HTMLEvents    ——    一般化的HTML事件

    ②初始化事件对象

    创建了event对象之后,还需要使用与事件有关的信息对其进行初始化。

    每种类型的event对象都有一个特殊的方法,为它传入适当的数据就可以初始化该event对象。

    ③触发事件

    模拟事件的最后一步就是触发事件,这一步需要使用dispatchEvents()方法,所有支持事件的DOM节点都支持这个方法。

    调用dispatchEvent()方法时,需要传入一个参数,即表示要触发事件的event对象。

7.2、模拟鼠标事件

    创建鼠标事件对象的方法是为createEvent()传入字符串"MouseEvents"。

    返回的对象有一个名为initMouseEvent()方法,用于指定与该鼠标事件有关的信息,这个方法接收15个参数:

    示例

        var btn = document.getElementById("myBtn");

        btn.addEventListener("click", function(event){
            console.log("clicked!");
        }, false);

        //创建事件对象
        var event = document.createEvent("MouseEvents");

        //初始化事件对象
        event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0,
            false, false, false, false, 0, null);

        //触发事件
        btn.dispatchEvent(event);       //控制台会打印"clicked!"

7.3、模拟键盘事件

    调用createEvent()并传入"KeyboardEvent"就可以创建一个键盘事件。

    返回的事件对象会包含一个initKeyEvent()方法,这个方法接收下列参数:

    示例:

        var textbox = document.getElementById("myTextbox");

        textbox.addEventListener("key", function(event){
            console.log(event.keyCode);
        }, false);
        
        //创建事件对象
        var event = document.createEvent("KeyboardEvent");

        //初始化事件对象
        event.initKeyboardEvent("keypress", true, true, document.defaultView, "a", 0, "Shift", 0);

        //触发事件
        textbox.dispatchEvent(event);
        textbox.dispatchEvent(event);

7.4、模拟变动事件

    使用createEvent("MutationEvents")创建一个包含initMutationEvent()方法的变动事件对象来模拟变动事件。

    initMutationEvent()接受的参数包括:

  • type
  • bubbles
  • cancelable
  • relatedNode
  • preValue
  • newValue
  • attrName
  • attrChange

    示例

        document.addEventListener("DOMNodeInserted", function(event){
            console.log("DOMNodeInserted");
        }, false);
    
        //创建事件对象
        var event = document.createEvent("MutationEvents");
        
        //初始化事件对象
        event.initMutationEvent("DOMNodeInserted", true, false, null, "", "", "", 0);
        
        //触发事件  
        document.dispatchEvent(event);      //控制台打印"DOMNodeInserted"

    以上代码模拟了DOMNodeInserted事件。其他变动事件也都可以照这个样子来模拟,只要改一改参数就可以了。

7.5、自定义DOM事件

    DOM3级还定义了“自定义事件“,它的目的是让开发人员创建自己的事件。

    要创建新的自定义事件,可以调用createEvent("CustomEvent"),返回的对象有一个名为initCustomEvent()的方法,接收如下4个参数:

  • type(字符串)    ——    触发的事件类型,例如"keydown"
  • bubbles(布尔值)    ——    表示事件是否应该冒泡
  • cancelable(布尔值)    ——    表示事件是否可以取消
  • detail(对象)    ——    任意值,保存在event对象的detail属性中

    示例

        var div = document.getElementById("myDiv");
        var event;

        EventUtil.addHandler(div, "myevent", function(event){
            console.log("DIV: " + event.detail);      //DIV: Hello world!
        });
        EventUtil.addHandler(document, "myevent", function(event){
            console.log("DOCUMENT: " + event.detail);   //DOCUMENT: Hello world!
        });
        
        if(document.implementation.hasFeature("CustomEvents", "3.0")){
            event = document.createEvent("CustomEvent");
            event.initCustomEvent("myevent", true, false, "Hello world!");
            div.dispatchEvent(event);
        }

7.6、IE中的事件模拟

    IE8及之前版本有自己的模拟事件的方式:

    ①调用document.createEventObject()方法(不接受参数)可以在IE中创建event对象

    ②然后,手工为event对象添加所有必要的信息(没有方法来辅助完成这一步骤)

    ③在目标上调用fireEvent()方法,这个方法接收两个参数:事件类型的名称和event对象

    在调用fireEvent()方法时,会自动为event对象添加srcElementtype属性

    总之,模拟任何IE支持的事件都采用相同的模式。

    例如,模拟click事件过程:

        var btn = document.getElementById("myBtn");

        EventUtil.addHandler(btn, "click", function(event){
            console.log(event.screenX);  // 100
            console.log(event.screenY);  // 0
            console.log(event.ctrlKey);  // false
            console.log(event.type);     // click
        });

        //创建事件对象
        var event = document.createEventObject();

        //初始化事件对象
        event.screenX = 100;
        event.screenY = 0;
        event.clientX = 0;
        event.clientY = 0;
        event.ctrlKey = false;
        event.altKey = false;
        event.shiftKey = false;
        event.button = 0;

        //触发事件
        btn.fireEvent("onclick", event);

    采用相同的方式也可以模拟触发keypress事件:

        var textbox = document.getElementById("myTextbox");

        EventUtil.addHandler(textbox, "keypress", function(event){
            console.log(event.keyCode);     // 65
            console.log(event.type);        // keypress
        })
        
        //创建事件对象
        var event = document.createEventObject();

        //初始化事件对象
        event.altKey = false;
        event.ctrlKey = false;
        event.shiftKey = false;
        event.keyCode = 65;

        //触发事件
        textbox.fireEvent("onkeypress", event);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值