你真的了解DOM事件么?

你真的了解DOM事件么?

我们大家都知道,人与人之间的交流可以通过语言,文字,肢体动作,面部微表情等,但是你知道Javascript和HTML之间是通过什么进行交互的么?你又知道Javascript和HTML之间是如何进行交互的么?如果你不是那么清楚,可以看这篇文章。。。

前面的话:

这篇博文由浅入深,先介绍两种事件流,然后介绍常见的事件处理程序以及它们之间的差异(涉及到一个简单的兼容的处理函数),后面还会针对事件对象进行深入学习,最后则是通过DOM事件实现的小案例。

事件流:

关于事件流,简单的来说就是:事件流描述的是从页面中接受事件的顺序。下面是《Javascript高级程序设计》里面的说法:

当浏览器发展到第四代时(IE4和Netscape Communicator 4),浏览器团队遇到一个很有意思的问题:页面的哪一部分会拥有特定的事件?想象下在一张纸上有一组同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是一组圆。两家公司的开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,那么同时你也单击了按钮的容器元素,甚至整个页面。

事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和Netscape开发团队居然提出了两个截然相反的事件流概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流。

事件冒泡

IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。看下面的例子

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>事件流</title>
    </head>
    <body>
        <input type="button" id="btn" value="" />
    </body>
</html>

点击按钮,那么这个click事件会按照这样传播:
<input-->body-->html-->document

事件捕获

Netscape团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件。

针对上面同样的例子,点击按钮,那么此时click事件会按照这样传播:
document-->html-->body-->input

虽然事件捕获是Netscape唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也都支持这种事件流模型。但由于老版本的浏览器不支持,因此很少有人使用事件捕获。

事件处理程序

HTML事件处理程序

HTML事件处理程序(现在不建议使用了):事件直接加在HTML代码中,例如:

<input type="button" id="btn" value="HTML事件处理程序" onclick="alert('HTML事件处理程序')" />

或者:

<input type="button" id="btn1" value="HTML事件处理程序" onclick="demo1()" />
function demo1(){
    alert("HTML事件处理程序");
}

缺点:HTML和js代码高耦合,如果修改,就要修改两个地方--HTML元素内和script函数。

DOM0级事件处理程序

DOM0级事件处理程序,是一种较传统的方式:把一个函数赋值给一个事件的处理程序属性。例如:

var btn2 = document.getElementById("btn2");
btn2.onclick = function(){
    alert("DOM0级事件处理程序");
}

现在用得比较多,优势是简单(只需要考虑JS不考虑HTML)且跨浏览器。如果想取消点击事件,将其设为null即可。

var btn2.onclick = null;//这时点击不会弹出窗口,表示已经取消了该点击事件

下面是我学习时候的一些思考:

思考一:DOM0级事件是否可以同时添加多个不同事件处理方法

var btn2 = document.getElementById("btn2");
btn2.onclick = function(){
    console.log("DOM0级事件处理程序-click-01");
}
btn2.onmouseover = function(){
    console.log("DOM0级事件处理程序-mouseover");
}

结果:鼠标经过,控制台出现:DOM0级事件处理程序-mouseover;鼠标点击,控制台出现:DOM0级事件处理程序-click-01。
说明:DOM0级事件可以同时添加多个不同事件处理方法

思考二:DOM0级事件是否可以同时添加多个相同事件处理方法

我们在前面的基础上添加如下代码:

btn2.onclick = function(){
    console.log("DOM0级事件处理程序-click-02");
}

结果:鼠标经过,控制台出现:DOM0级事件处理程序-mouseover;鼠标点击,控制台出现:DOM0级事件处理程序-click-02。(在这里你可能以为会出现DOM0级事件处理程序-click-01和DOM0级事件处理程序-click-02)
说明:DOM0级事件不可以同时添加多个相同事件处理方法(这样说好像不太严格,大概意思就是直接忽略了前面的onclick,只显示最后的onclick的结果。
那么这里就设下一个疑问:我们应该如何让DOM同时绑定多个相同事件呢???我们接着往下看

DOM2级事件处理程序

DOM2级事件处理程序定义了两个方法--用于处理指定和删除事件处理程序的操作:

  addEventListener()添加事件监听程序

  removeEventListener()移除事件监听程序

  三个参数设置;事件名称,处理方法(函数),布尔值--false:表示在冒泡阶段调用事件处理程序(可以最大限度的兼容浏览器),一般设为false;true表示在捕获阶段调用事件处理程序

来看下面的例子:

var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
    console.log("DOM2级事件处理程序-click-01");
},false)
//DOM2级事件处理程序-click-01

与DOM0级不同的是,我们移除需要使用removeEventListener(),代码如下:

var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
    console.log("DOM2级事件处理程序-click-01");
},false)
btn3.removeEventListener('click',function(){
    console.log("DOM2级事件处理程序-click-移除成功");
},false)
//DOM2级事件处理程序-click-01

在这里,你很有可能和我想的一样,DOM2级事件处理程序已经移除完了。但是结果是DOM2级事件处理程序-click-01依然还在,按照理论上来说不应该是被移除了么?但是为什么还在?

原因:通过addEventListener方法添加的事件处理程序只能用removeEventListener来移除,移除时传入的参数与添加处理程序时使用的参数相同。这就意味着通过addEventListener添加的匿名函数将无法移除。真的是这样么?我们继续来看例子:

var btn3 = document.getElementById("btn3");
var handler = function demo3(){
    alert("DOM2级事件处理程序");
}
btn3.addEventListener('click',handler,false)
//在不加后面的代码的时候弹框显示DOM2级事件处理程序
btn3.removeEventListener('click',handler,false)
//加了removeEventListener后点击,没有弹框,说明该点击事件已经被移除了

接下来我们同样思考几个问题:
思考一:DOM2级事件是否可以同时添加多个不同事件处理方法

var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
    console.log("DOM2级事件处理程序-click-01");
},false)
btn3.addEventListener('mouseover',function(){
    console.log("DOM2级事件处理程序-mouseover");
},false)

结果:鼠标经过,控制台出现:DOM2级事件处理程序-mouseover;鼠标点击,控制台出现:DOM2级事件处理程序-click-01。
说明:DOM2级事件可以同时添加多个不同事件处理方法

思考二:DOM2级事件是否可以同时添加多个相同事件处理方法

var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
    console.log("DOM2级事件处理程序-click-01");
},false)
btn3.addEventListener('mouseover',function(){
    console.log("DOM2级事件处理程序-mouseover");
},false)
btn3.addEventListener('click',function(){
    console.log(this.id);
},false)

结果:鼠标经过,控制台出现:DOM2级事件处理程序-mouseover;鼠标点击,控制台出现:DOM2级事件处理程序-click-01和btn3。
说明:DOM2级事件可以同时添加多个不同事件处理方法,同时在这里可以用this引用被触发的元素。

注意:
  1、要处理的事件名(事件名去掉“on”,如点击事件需写成click);
  2、作为事件处理程序的函数名(函数不需要带括号,只要函数名或者直接写函数体也可以);
  3、布尔值(true表示使用事件捕获流,false表示使用事件冒泡流,一般使用false兼容各种浏览器)。
  4、通过addEventListener添加的事件只能通过removeEventListener来移除;DOM 0级和DOM 2级事件可以在一个DOM上添加多个事件,按顺序执行
  5、DOM2级事件处理程序适用于:IE9、Firefox、Safari、Chrome和Opera

思考:那么如果我们想要兼容低版本的IE该怎么办呢?请继续往下阅读

IE事件处理程序

(声明:IE事件处理程序的代码没有进行测试)

IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),这两个方法接受相同的两个参数:事件处理程序名称与事件处理函数程序函数(因为IE8及更早版本只支持时间冒泡,所以没有第三个参数)。下面我们来看代码:

var btn4 = document.getElementById("btn4");
btn4.attachEvent('onclick',function(){
    console.log("IE事件处理程序-onclick-01");
});

这里需要注意的是,第一个参数是onclick,而不是click。另外与DOM0级中的不同在于作用域,看下面的代码:

var btn4 = document.getElementById("btn4");
btn4.attachEvent('onclick',function(){
    console.log(this === window);  //true
});

其他需要注意的有:
1、attachEvent方法也可以为一个元素添加一个或多个事件处理程序,当添加多个相同的事件处理程序的时候,这些事件处理程序不是以添加它们的顺序而执行,而是以相反的顺序被触发。比如下面这个例子:

var btn4 = document.getElementById("btn4");
btn4.attachEvent('onclick',function(){
    console.log("hello");
});
btn4.attachEvent('onclick',function(){
    console.log("World");
});

这段代码用DOM0级的方法得到的结果为World,用DOM2级的方法得到结果为先是hello然后是World,而在这里的结果为先是World然后是hello。

2、detachEvent()方法和DOM2级中移除的方法一样,同样也需要注意中间添加的匿名函数不能被移除,这里就不再举例说明了。

从HTML事件处理程序到DOM0级事件处理程序,再到DOM2级处理程序,还有IE事件处理程序,我们在使用的时候为了让用户体验最佳,我们该让我们的代码兼容各种浏览器,那么我们有什么好的方法让我们的代码兼容到各种浏览器中呢?

跨浏览器的事件处理程序

为了解决跨浏览器的问题,我们不得不集中的把前面几种方法柔和在一起,然后就出现了跨浏览器的事件处理程序。(我的思路如下)

    1、先考虑添加事件的情况,在添加的时候,我们要知道给谁添加?添加的是什么事件?然后事件触发的是什么函数?
        给谁添加?当然是我们的元素了,因为还不知道,这里我们先设置为element
        添加的是什么事件?click?onclick?onmouseover?我们也还是不知道,先设置为type吧
        添加什么函数?前面的例子中有用到过handler这里也先就用这个

function(element, type, handler) {……}

    2、我们要判断我们的浏览器是否支持相关的操作,这里我们可以用if-else来判断

if(element.addEventListener) {
    //DOM2级事件处理程序
} else if(element.attachEvent) {
    //IE事件处理程序
} else {
    //DOM0级事件处理程序
}

    3、针对DOM2级事件处理程序、IE事件处理程序和DOM0级事件处理程序它们有相似的地方,然后我们通过前面可以知道,它们对于事件的处理有点不和谐,我们该怎样呢?很明显,添加"on"比去掉"on"更简单,所以比如点击事件的type应该为click,然后在需要用onclick的地方,使用'on'+type,然后各自保留各自的特点    
DOM2级事件处理程序:element.addEventListener(type, handler, false);
IE事件处理程序:element.attachEvent('on' + type, handler);
DOM0级事件处理程序:element['on' + type] = handler;

    4、到这里基本的添加情况以完成,接下来用同样的方法来考虑移除事件的情况(步骤省略)

    5、然后分别给添加事件函数和移除事件函数进行命名addHandler和deleteHandler,并封装起来,得到我们的最终效果:

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;
        }
    },
    deleteHandler: 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中的事件对象

  前面我们已经封装好一个函数了,我们后面的操作直接引用前面的那个函数。说到事件对象,我们先不得不说事件对象的两个重要属性type和target
  其中type是获取事件的类型,而target是获取事件的目标。直接看代码:

var btn = document.getElementById("btn");
eventUtil.addHandler(btn,'click',showMes);
function showMes(event){
    alert(event.type);//结果是弹出click
}

结果是弹出click,说明了该事件的类型是点击事件

var btn = document.getElementById("btn");
eventUtil.addHandler(btn,'click',showMes);
function showMes(event){
    alert(event.target);//结果是弹出[object HTMLInputElement]
}

结果是弹出[object HTMLInputElement],说明该事件的目标是input元素

下面我们运行一段代码,来看一个有趣的现象:

<div id="box">
    <input type="button" id="btn" value="DOM中的事件对象" />
    <a href="www://baidu.com" id="go">百度</a>
</div>

function showMes(event){
    alert(event.target.nodeName);
}

function showBox(){
    alert("这是放盒子的box")
}

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

eventUtil.addHandler(btn,'click',showMes);
eventUtil.addHandler(box,'click',showBox);

 运行结果:鼠标点击按钮,分别弹出了“INPUT”和“这是放盒子的box”,我们只点击了按钮,为什么按钮所在的div也会显示呢?查资料知道这是因为事件冒泡机制导致的。

那么我们该如何阻止事件冒泡呢?

我们的事件对象给我们提供了一种方法:通过stopPropagation()方法来实现,代码示例如下:

function showMes(event){
    alert(event.target.nodeName);
    event.stopPropagation();//阻止事件冒泡
}

function showBox(){
    alert("这是放盒子的box")
}

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

eventUtil.addHandler(btn,'click',showMes);
eventUtil.addHandler(box,'click',showBox);

光知道阻止事件冒泡行为还不行我们还需要知道的一种方法:阻止事件默认行为-->preventDefault()方法

比如我们知道的a标签有一个默认行为就是跳转到目标地址,如果我们想要阻止这种行为我们就可以使用该方法,代码如下:

<a href="www://baidu.com" id="go">百度</a>

var go = document.getElementById("go");
function showGo(event){
    event.stopPropagation();//阻止事件冒泡
    event.preventDefault();//阻止事件的默认行为
}
eventUtil.addHandler(go,'click',showGo);

运行结果是,无论怎样点击,都跳转不到百度页面。
在a标签里面阻止事件默认行为还有一种方法就是直接在a标签里面添加,比如<a href="javascript:;">aa</a>

补充:在需要通过一个函数处理多个事件时,可以使用type属性,比如:

var btn = document.getElementById("btn");
var handler = function(event){
    switch(event.type){
        case"click":
            alert("Clicked");
            break;
        
        case"mouseover":
            event.target.style.backgroundColor = "red";
            break;
        
        case"mouseout":
            event.target.style.backgroundColor = "";
            break;
    }
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;

IE中的事件对象

  【type属性】用于获取事件类型
  【srcElement属性】用于获取事件的目标(DOM中用的是target),所以,可以使用

event=event || window.event;(考虑IE8以前的版本)
var ele=event.target  event.srcElement;(考虑非IE与IE)

  【cancelBubble属性】用于阻止事件冒泡,设为true表示阻止冒泡,false表示不阻止冒泡。
  【returnValue属性】用于阻止事件的默认行为,设为false表示阻止。
  这里只介绍IE事件对象的常用属性,关于它的具体例子就不做介绍了

跨浏览器的事件对象:

搞清楚前面的问题,下面的一段代码看起来就简单了。它直接将前面提到的所有方法和属性一起封装起来,并保存到一个单独的js文件中,命名为event.js,其代码如下:

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;
        }
    },
    //获取事件
    getEvent: function(event) {
        return event ? event : window.event;
    },
    //获取事件类型
    getType: function(event) {
        return event.type;
    },
    //获取元素
    getElement: 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;
        }
    }
}

 我们可以用如下代码来测试点击a事件

window.onload=function(){
  var go=document.getElementById('go'),
      box=document.getElementById('box');

  eventUtil.addHandler(box,'click',function(){
      alert('我是整个父盒子');
  });

  eventUtil.addHandler(go,'click',function(e){
      //e=eventUtil.getEvent(e);
      e=e || window.event;
      alert(eventUtil.getElement(e).nodeName);
      eventUtil.preventDefault(e);
      eventUtil.stopPropagation(e);
  });

}

 

事件类型

因为事件类型比较多,这里主要通过两个具体的案例来展示,不做详细介绍

案例一:用鼠标或键盘事件来控制抽奖。

效果截图:

查看地址:http://sandbox.runjs.cn/show/0pibyaes

案例二:模仿扣扣登陆时的拖曳效果(主要学习其中如何实现拖曳效果的思维)

效果截图:

查看地址:http://sandbox.runjs.cn/show/7mhb7pfw

更多参考资料:

  参考书籍:《Javascript高级程序设计(第三版)》

  参考博客: https://my.oschina.net/u/1403185/blog/184960

    http://www.cnblogs.com/luozhihao/p/5934935.html

    http://www.cnblogs.com/xiaohuochai/p/5867195.html

posted @ 2016-10-14 19:23  foodoir  阅读(1037)  评论(0编辑  收藏  举报