在 0 级 DOM 事件模型中,它只是简单的执行你为它绑定的事件,比如你为某个元素添加了一个 onclick 事件,当事件触发时,它只是去调用我们绑定的那个方法,不再做其他的操作。
在 2 级 DOM 事件模型中,就比较复杂一些,它将不再是单纯的调用一下自身绑定的事件就完事了,它还拥有机会去处理它的祖先节点,在 DOM2 级事件模型中,它有一个事件传播过程,分为 3 个阶段,从 "事件捕获"Document 开始来到 "目标节点" 再从 "目标节点" 冒泡回 Document 对象,举段代码
- <div id="div">
- <a href="javascript:;">
- DOM事件模型
- </a>
- </div>
- <script>
- var div = document.getElementById("div");
- var a = div.children[0];
- document.onclick = function() {
- console.log("document");
- };
- a.onclick = function() {
- console.log("a");
- };
- div.onclick = function() {
- console.log("div");
- };
- </script>
可以看到我只是点击了 a 元素,但是 div 和 document 绑定的事件也被触发了,这就是 DOM2 级和 1 级的区别,同时你也看到,它是先输出的 a,而不是 div 和 document,虽然说它有 3 个阶段,但浏览器默认是在冒泡阶段才执行的,如果不这样的话,我们点击 a 元素就会执行多次啦。
如果你想让浏览器在捕获阶段执行,那么就不能直接使用 onclick 添加事件了,而是要使用 addEventListener 添加事件,它的第三个参数就是用来设置在哪个阶段执行,具体可以看
event.currentTarget 获取到的是当前绑定事件的那个对象,也因为此原因,他获取到的常常和 this 一样,下面是一个示例:
- <a href="javascript:;" id="a">
- evnet.currentTarget
- </a>
- <script>
- var a = document.getElementById("a");
- a.onclick = function(event) {
- console.log("this:", this);
- console.log("currentTarget:", event.currentTarget);
- };
- </script>
当我点击 a 标签时,this 和 currentTarget 打印出来的都是 a 标签本身,如下图
既然如此这个 currentTarget 有啥用呢,这是一开始的想法,但随后发现不对,想到这个 currentTarget 始终获取到的是那个绑定事件的对象,但 this 却有很大的不同,因为 this 并不关心是谁绑定的它,它只关心是谁执行的它,因此如果再将上面那段代码改造改造,我们就会发现,它们真的是不一样的,代码如下:
- var a = document.getElementById("a");
- a.onclick = function(event){
- (function(){
- console.log("this:",this);
- console.log("currentTarget:",event.currentTarget);
- }());
- };
效果如图
这也就是说,某些时候如果不能通过 this 来获取绑定事件的对象时,就可以使用 event.currentTarget。
有些人认为 event.currentTarget 就是 this,其实不然,容易把 event.currentTarget 当成 this,主要原因就是,在事件处理器中,我们常常使用的是 this,而不是 event.currentTarget,至于为什么,反正我是因为从接触 js 开始,所看过的教程上都是那么用的,时间长了,竟然忘了一件事,event.currentTarget 才是真的属于事件处理器的,而 this 不过是个冒牌货。
event.target 获取到的是触发事件的那个元素。
网上常说的事件委派,就是通过 event.target 来实现的,所谓的事件委派,就是我并不给某个具体的东西添加事件,而通过给它的父辈添加事件,当我点击那个具体的元素时,父辈的事件会触发(因为有事件冒泡),而这时就需要用到 evnet.target 了,因为在父辈的事件中 this 并不指向当前点击的那个元素,但是 event.target 可以获取到是谁触发的当前事件。以下是一个示例
- <ul id="ul">
- <li>
- 111
- </li>
- <li>
- 222
- </li>
- <li>
- 333
- </li>
- </ul>
- <script>
- var ul = document.getElementById("ul");
- ul.onclick = function(event) {
- console.log(event.target);
- };
- </script>
当我点击第二个 li 时,输出如下值
当然我们也可以直接给这三个 li 添加事件,但是那样的话就绑定了 3 次事件,如果有 1 万个元素,就绑定了 1 万次,而通过事件委派则只需要绑定一次。
最主要倒也不是说不能给 li 添加事件,而是如果这些 li 并不是事先添加的,而是通过后端返回的数据,再渲染的,那么要是我们再放回数据之前就给 li 添加事件,那么就会有问题,因为根本就不存在 li 元素,也就是说后添加的元素无法事先去添加事件,比如下面这段代码就有些问题。
- <ul id="ul">
- <li>
- 1111
- </li>
- </ul>
- <script>
- var ul = document.getElementById("ul");
- var lis = ul.children;
- for (var i = 0; i < lis.length; i++) {
- lis[i].onclick = function() {
- console.log(this);
- };
- }
- var li = document.createElement("li");
- li.innerText = "2222";
- ul.appendChild(li);
- </script>
当我点击第二个 li 时,什么都没有输出,如下图
因为第二个 li 是在 for 循环以后添加的,所有并没有给第二个 li 添加上事件。而如果是给 ui 添加事件,那就不一样了,代码如下
- <ul id="ul">
- <li>
- 1111
- </li>
- </ul>
- <script>
- var ul = document.getElementById("ul");
- ul.onclick = function(event) {
- console.log(event.target);
- };
- var li = document.createElement("li");
- li.innerText = "2222";
- ul.appendChild(li);
- </script>
不管我点击第几个 li 都可以正常的输出,如下图
不过因为 event.target 获取到的是最终触发这个事件的元素,所以在写代码的时候,我们经常需要加上判断,因为通过 event.target 获取到的不一定是我们想要的元素,比如下面这个例子
- <ul id="ul">
- <li>
- <em>
- 111
- </em>
- </li>
- </ul>
- <script>
- var ul = document.getElementById("ul");
- ul.onclick = function(event) {
- console.log(event.target);
- };
- </script>
当我点击 111 的时候,获取到的是 em 标签,如下图
但我想要的是 li,因此我们就得加上判断,我经常使用的一招就是,通过判断元素的标签名,代码如下
- <ul id="ul">
- <li>
- <em>
- 111
- </em>
- </li>
- </ul>
- <script>
- var ul = document.getElementById("ul");
- ul.onclick = function(event) {
- var target = event.target;
- if (! (target.tagName.toLowerCase() === "li")) {
- target = target.parentNode;
- }
- if (target.tagName.toLowerCase() === "li") {
- console.log(target);
- }
- };
- </script>
tagName 可以获取到元素名,但是每个浏览器获取到的都有可能不同,有些浏览器获取到的是大写的标签名,有些浏览器获取到的是小写的标签名,因此在上面那段代码中,将标签名都转换成小写的,通过判断标签名来确定是不是我要的元素。
虽然这种判断可行,但仔细想想也能想到,这个方法,也是有缺陷的,如果 DOM 比较复杂,则容易判断错误,目前还没有想到更好的方法。
在 event 中有一个 relatedTarget 属性,它可以获取到和它相关的元素(通过谁来到这个元素上的,要到哪个元素上去),举个例子
- <div id="box">
- <p>
- 新的起点,新的梦想。
- </p>
- </div>
- <script>
- var box = document.getElementById("box");
- box.onmouseout = function(event) {
- console.log(event.relatedTarget);
- };
- </script>
在 onmouseout 中 relatedTarget 可以获取到它要到哪个元素上去,相反在 onmouseover 中 relatedTarget 获取到的是从哪个元素来的,代码如下
- <div id="box">
- <p>
- 新的起点,新的梦想。
- </p>
- </div>
- <script>
- var box = document.getElementById("box");
- box.onmouseover = function(event) {
- console.log(event.relatedTarget);
- };
- </script>
可以看到当我从 html 移入到 div 上时,relatedTarget 获取到的是 html,也就是它从 html 来到 div 的。
想起一句话:我从哪里来,又要到哪里去。
当第一次看到这个属性的时候,给我的感觉是,它肯定是很有用的,事实上也确实有些用处,如果细心的朋友,看上面的那个动画图,会发现一件事,onmouseover 和 onmouseout 存在一个问题,离开或进入它的子元素事件也会被触发。大多数情况我们是不希望这样的,因此如果使用 onmouseover 或 onmouseout 时,最好判断一下,是否真的移出了盒子。
在元素中有一个 contains 方法,可以用来判断某个元素是否是它的子元素,如果是返回 true,否则返回 false,而以上问题我们就可以通过这个方法来写,代码如下
- <div id="box">
- <p>
- 1111111
- <em>
- 新的起点,新的梦想。
- </em>
- </p>
- </div>
- <script>
- var box = document.getElementById("box");
- box.onmouseout = function(event) {
- if (!this.contains(event.relatedTarget)) {
- console.log(this);
- }
- };
- </script>
如果你只是想解决以上这个问题,那么大可不必这样写,因为在浏览器中,分别有两个和它们相同的事件,但它们并没有这个问题,分别是 onmouseenter 鼠标移入事件和 onmouseleave 鼠标移出事件。
需要注意的是 relatedTarget 属性只对 onmouseout 和 onmouseover 事件有用,虽然对 onmouseenter 和 onmouseleave 事件也有用,不过很少有人会那么去用,因为我发现 relatedTarget 属性除了用在解决上面那个问题以外,还真没发现有什么其他的用处。
需要注意一点,在普通函数中是不存在 event 对象的,只有在事件中才有,比如这么这段代码,如果使用 event 就会输出 undefined。
- (function(event){
- console.log(event); //undefined
- })();
官方提供了一些如 onclick、onmouseover、onscroll 等事件给我们使用,但难免也会有自己建立事件的需求,比如监听某个变量的变化,虽然听起来好像不太可能,但方法总是有的,我们不让使用者直接操作某个变量,而是提供一个方法给他操作,下面是一段实现思路。
- <script>
- var Foo = (function(){
- var a = 10;
- return {
- get:function(){
- return a;
- },
- set:function(value){
- if(a!==value){
- a = value;
- this.change();
- }
- },
- change:function(){
- console.log("a的值有变化");
- }
- };
- })();
- Foo.set(9);
- Foo.set(52);
- </script>
以上我将变量 a 封死在函数作用域里面,让外部无法直接操作变量 a,要操作就只能通过调用我事先设置的 set 方法,之所以要这样弄是因为我们是无法知道变量是什么时候改变了的,而如果让使用者操作我们提供事先提供的方法,那么我们就可以对其进行处理了。
以上并不是一个自定义事件,如果想要实现自定义方法,我们可以通过 javascript 提供的 3 个方法来弄,分别是 document.createEvent()、event.initEvent()、element.dispatchEvent()。
具体可以看
实现如下
- <script>
- var ev = document.createEvent("HTMLEvents");
- ev.initEvent("changeA", false, false);
- var Foo = (function() {
- var a = 10;
- return {
- get: function() {
- return a;
- },
- set: function(value) {
- if (a !== value) {
- a = value;
- document.dispatchEvent(ev);
- }
- }
- };
- })();
- document.addEventListener("changeA",
- function() {
- console.log("a有变化", Foo.get());
- });
- Foo.set(555);
- Foo.set(520);
- </script>
以上也就是将 set 中的 change 方法改成 dispatchEvent,用来触发 changeA 事件,如果你想要解绑这个事件,和你给 onclick 事件解绑是一样的。
当我们通过 attachEvent 添加事件时,需要注意一件事,我们为它添加的事件函数,IE 并没有将这个函数作为元素的方法调用,而是作为全局函数来调用,因此,它里面的 this 并不指向当前绑定的事件对象,而是 window,并且 event 也不指向当前事件对象,看下面这段代码
- <a href="javascript:;" id="a">
- addEventListener
- </a>
- <script>
- var a = document.getElementById("a");
- a.attachEvent("onclick",
- function() {
- console.log("this:", this);
- console.log("currentTarget:", event.currentTarget);
- });
- </script>
当我点击 a 标签时,输出如下:
来源: http://www.cnblogs.com/pssp/p/6382874.html