DOM事件

243 阅读5分钟

BOM(浏览器对象模型)和DOM(文档对象模型)的事件之间的关系并不十分清晰

1、事件流

<!DOCTYPY html>
<html>
    <head>
        <title>test</title>
    </head>
    <body>
        <div>xxx</div>
    </body>
<html>
  • 事件冒泡:从具体节点->文档(document),即div->body->html->document->window
  • 事件捕获:从文档(document)->具体节点,即window->documet->html->body->div

DOM2级事件流包括三个阶段:事件捕获、处于目标阶段、事件冒泡,并规定捕获阶段不会涉及事件目标。浏览器都会捕获阶段触发事件对象上的事件。结果,有两个机会在目标对象上操作事件。

注:IE8及更早版本不支持DOM事件流

2、事件处理程序

html事件处理程序

<input type="button" value="click me" onclick="alert('clicked')" id="myBtn"/>

使用onclick属性将js代码作为值,其中js不能使用非转义的html语法字符,如&、<、>、""

DOM0级事件处理程序

var btn = document.getElementById('myBtn');
btn.onclick = function(){
    alert(this === btn) //true
}
btn.onclick = null;//删除事件处理程序

在冒泡阶段调处理,一个元素只能添加一个。通常情况下事件监听函数如果返回一个值并且是false,则会阻止浏览器执行默认的动作

DOM2级事件处理程序

var btn = document.getElementById('myBtn');
function sayHi(){
    alert('clecked')
}
btn.addEventListener('click', sayHi , false);

//删除事件处理程序
btn.removeEventListener('click', sayHi, false);
  • addEventListener的第三个参数true表示在捕获阶段调用事件处理程序,false表示在冒泡阶段调用事件处理程序。
  • addEventListener添加事件,只能removeEventListener才能移除,并且参数一致,添加匿名函数无法移除。
  • 一个元素可以添加多个,按照添加顺序执行,先添加先执行

IE事件处理程序

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

冒泡阶段处理事件处理程序。移除函数必须和添加是相同参数才能移除成功。一个元素可以添加多个,按照添加顺序反向执行,先添加后执行(栈的形式)。

跨浏览器的事件处理程序

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

var btn = document.getElementById('myBtn');
function sayHi(){
    alert(123) 
}
EventUtil.addHandler(btn, 'click', sayHi);

//删除事件处理程序
EventUtil.removeHandler(btn, 'click', sayHi);

注:DOM1级的标准中并没有定义事件相关的内容,所以没有所谓的DOM1级事件模型

3、事件对象event

DOM中的事件对象

触发DOM上的某个元素时,就发生一个事件event对象,有如下属性和方法,都是只读

event.currentTarget //事件处理程序注册的那个元素=== this
event.target //实际事件触发的元素,即事件的实际目标
event.cancelable //是否可以取消事件的默认行为
event.type //事件类型,如'click'
event.eventPhase //确定事件当前正位于事件流的哪个阶段,值为1、2、3
event.trusted // false自定义, true浏览器生成的

event.preventDefault() //取消默认行为
event.stopPropagation() //阻止事件捕获和冒泡
event.stopImmediaPropagation()//阻止事件捕获和冒泡,阻止任何事件处理程序调用
document.body.onclick = function(event){
    console.log(event.currentTarget === document.body) //true
    console.log(this === event.body)  //true
    console.log(event.target === document.getElementById('myBtn')) // false
}

按下按钮时,冒泡到body,currentTarget和this等于body,target等于按钮元素

IE中的事件对象

event.cancelBubble //读/写,默认值false,不取消冒泡,类似stopPropagation()
event.retrunValue //读/写,默认值true,不取消事件的默认行为,类型preventDefault()
event.srcElement //读,事件的目标,类似target
var btn = document.getElementById('myBtn');
//event对象作为window对象的一个属性存在
btn.onclick = function(){
    var event = window.event;
    console,log(window.event.srcElement === this) //true
    window.event.returnValue = false; //阻止默认行为
    window.event.cancelBubble = true; //阻止冒泡
}
//
btn.attachEvent('onclick', function(event){
    console.log(event.type) //click
    console,log(event.srcElement === this) //false
});

跨浏览器的事件对象

var EventUtil = {
    getEvent: function(event){
        return event ? event : window.event;
    },
    getTarget: function(event){
        return event.target || event.srcElement;
    },
    preventdefault: function(event){
        if(event.preventdefault){
            event.preventdefault();
        } else {
            event.returnVlalue = false;
        }
    },
    stopPropagation: function(event){
        if(event.stopPropagation){
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
    }
}

var btn = document.getElementById('myBtn');
function sayHi(event){
    event = EventUtil.getEvent(event);
    let target = EventUtil.getTarget(event);
    EventUtil.preventdefault(event);
    EventUtil.stopPropagation(event);
}
EventUtil.addHandler(btn, 'click', sayHi);

4、事件类型

  • resize事件,ff只会在用户停止调整窗口大小时才会触发,其他浏览器会在浏览器窗口变化1像素时就触发,随着变化不断触发。
  • blur和focus事件,不冒泡;focusin和focusout事件,会冒泡
  • click鼠标点击或者按下回车键时触发
  • mouseenter和mouseleave事件,不冒泡。mouseenter元素外部移到元素内部,移到后代元素上不会触发。
  • mouseout和mouseover事件,会冒泡。mouseout,从一个元素移入另一个元素时触发,可能是这个元素的字元素,或者外部。mouseover首次移入另一个元素边界内时触发。
  • mousewheel滚轮事件

mousedown->mouseup->click->mousedown->mouseup->click->dbclick

鼠标事件的坐标位置

event.clinetX //事件发生时,鼠标指针在视口中的水平坐标
event.clienY

event.pageX //事件发生时,鼠标指针在页面中的水平坐标
event.pageY

event.screenX //事件发生时,鼠标指针在屏幕中的水平坐标
event.screenY

注:IE8及更早版本不支持pageX和pageY,可以通过event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft)

鼠标事件的相关元素

var EventUnit = {
    getRelatedTarget: function(event){
        if(event.relateTarget){
            return event.relateTarget;
        } else if(event.toElement){ //IE8及更低版本
            return event.toElement;
        } else if(event.fromElement){ //IE8及更低版本
            return event.fromElement;
        } else {
            return null;
        }
    }
}

event相关元素属性relatedTarget,针对mouseout和mouseover事件才有值,其他事件这个值为null。mouseover事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。mouseout事件主目标是失去光标的元素,而相关元素则是获得光标的元素。

鼠标按钮

event.button //0表示主鼠标,1表示中间鼠标(滚轮),2次鼠标按钮

注:IE8及以前版本,则0-7,含义也不同于DOM的button

鼠标滚轮事件

mousewheel事件的event对象包含一个特殊的wheelDelta属性,向上+120,向下-120

注:ff中DOMMouseScroll事件,event.detail,保存向上-3,向下+3

var EventUnit = {
    getWheelDelta: function(event){
        if(event.wheelDelta){
            return client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta;
        } else {
            return -event.detail * 40;
        }
    }
}

键盘与文本输入

  • keydown,按下任意键触发,按下不放会一直重复触发
  • keypress,按下字符键触发。
  • keyup,释放键盘
  • textInput事件,event.data属性,用户输入的字符。只有编辑区域才能触发改事件

keydown和keyup事件的event对象有keyCode属性,键码。keypress事件的event对象有charCode属性,字符编码

html5事件

  • contextmenu事件
  • beforeunload事件,会在浏览器卸载页面之前触发
  • DOMContentloaded事件,形成完整的DOM树之后就会触发。
  • readystatechange事件,和document.readyState属性配合使用,取值loading、loaded、interactive(交互)、complete
  • pageshow和pagehide事件,往返缓存bfcache,IE9及之前版本不支持。pageshow事件的event.persisted属性true,表示页面来自bfcache。
  • hashChange事件,URL的参数列表发生变化时触发

触摸与手势事件

5、内存和性能

事件委托

事件委托是利用事件冒泡实现

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

传统方法会给每个li绑定一个事件,有3个事件处理程序。利用事件委托,可以只给ul绑定一个事件处理程序

移除事件处理程序

某个元素即将被移除,最好手工移除事件处理程序。如在操作innerHTML时。卸载页面时,通过onunload事件移除所有事件处理程序

6、模拟事件