乐趣区

关于前端:DOM事件流和冒泡捕获

事件流

形容的是从页面中承受事件的程序。事件最早是在 IE3Netscape Navigator2中呈现的,在 IE4Navigator4公布时,这两种浏览器都提供了类似但不雷同的 APIIE 的事件流是事件冒泡流,而 Netscape 的事件流是事件捕捉流

事件冒泡

即事件开始时由最具体的元素接管,而后逐级向上流传到较为不具体的节点 —《JavaScript 高级程序设计》

<div id="box1">
      <div id="box2">
        <div id="box3"> 点击我 </div>
      </div>
</div>

如下面代码构造所示,三个嵌套元素,如果我给三个元素都绑定点击事件,并且在事件回调中输入它们各自的 ID, 而后点击最里层的元素指标(指标元素 box3),输入如下:

 box3
 box2
 box1

就像往水里扔了一块石头,水波会从里往外扩散,同样,因为三个元素嵌套,外面的元素是属于里面元素的,点击外面元素其实也是在点击里面的元素,所以事件的回调会从外向外流传执行。
所有古代浏览器都反对事件冒泡,不过还是有所区别:

  • IE9、Firefox、chrome、Safari: 指标元素 –> 外层元素 ….–>body–>html–>document–>window
  • IE9 以下:指标元素 –> 外层元素 ….–>body–>html–>document
  • IE5.5 及更早 IE: 指标元素 –> 外层元素 ….–>body–>document (有待验证)

事件捕捉

事件捕捉的思维是不太具体的的节点应该更早承受到事件,而最具体的节点应该最初承受到事件。事件捕捉的意义在于事件达到预约指标之前捕捉它。—《JavaScript 高级程序设计》

还是以下面的代码构造为例,点击box3, 如果事件是以捕捉的形式触发,那么会输入如下:

 box1
 box2
 box3

就像拆快递一样,从外往里,一层一层,直到看到你买的宝贝,同样,事件的回调也会从外往里执行,直到指标元素,也就是点击的那个元素box3。同样这个最外层元素对于不同浏览器也是有区别的,能够参考下面的事件冒泡

因为老版本的浏览器(IE8 及更早版本)不反对捕捉,因为很少有人应用事件捕捉. 举荐大家放心使用冒泡,因为古代浏览器都反对,在有非凡须要时再应用事件捕捉。

DOM 事件流

因为 IENetscpace两方提出的事件流概念相同,W3C为了统一标准,折中了一下,在 DOM2 级中提出了 DOM2 级事件

DOM2 级事件规定,事件的事件流包含三个阶段:事件捕捉阶段、处于指标阶段、事件冒泡阶段。三个阶段顺次产生。

在 DOM 事件流中,理论的指标在捕捉阶段不会承受到事件,捕捉阶段会在达到指标元素前完结,而后是“处于指标”阶段,绑定在指标上的事件会依据绑定程序执行,并在事件处理中被看成冒泡阶段的一部分。最初阶段是冒泡阶段。

IE9、Opera、Firefox、Chrome 和 Safari 都反对 DOM 事件流;IE8 及更早版本不反对 DOM 事件流

IE8及更早版本的 IE 浏览器不反对 DOM2 规范中指定事件处理程序的写法,所以不反对 DOM 事件流,留神是不反对 DOM 事件流,这里的 DOM 事件流 指的是 DOM2 级中提出的DOM 事件流,就是说不像下面说的那样,具备捕捉、指标、冒泡,而是只有冒泡。

事件处理程序

事件就是用户或浏览器本身执行的某种动作。比方 clickloadmouseover 等等,这都是事件的名字,而响应某个事件的函数就叫 事件处理程序(或事件侦听器)。事件处理程序的名字以 on 结尾,因而 click 事件的事件处理程序就是onclick。而为事件指定处理程序的形式有好几种。

HTML 事件处理程序

某个元素反对的每种事件,都能够应用一个与相应事件处理程序同名的 HTML 个性来指定。这个个性的值应该是可能执行的 Javascript 代码。

说人话,就是应用一个与相应事件处理程序同名的 HTML 属性来指定事件处理程序:

  • 写法 1
 <button onclick="console.log(this,event)"> 点击 </button>

点击输入如下:

 <button onclick="console.log(this)"> 点击 </button>
 MouseEvent {isTrusted: true, constructor: Object}

  • 写法 2
 <button onclick="clickBtn()"> 点击 </button>

js 代码:

 function clickBtn() {console.log(this); //window
     console.log(event); //MouseEvent {isTrusted: true, constructor: Object}
   }

这样把函数抽离进去独自定义时,this值为 window, 事件对象event 能够不必定义或者从参数中获取,间接在函数中应用 event 关键字获取,留神这种写法,是必须用 event 关键字,不能用其余的!

 <button onclick="clickBtn(event,123)"> 点击 </button>

js 代码:

  function clickBtn(a, b) {console.log(this); //window
      console.log(event) //MouseEvent {isTrusted: true, constructor: Object}
      console.log(a);    //MouseEvent {isTrusted: true, constructor: Object}
      console.log(b);    //123
    }

能够传递参数,应用对应地位的参数来承受即可,如果肯定要手动把事件对象 event 传进来,也能够,然而同样,在传递时也必须应用 event 关键字,在而在函数中,也必须应用对应地位的形参来接管,比方下面的 a, 然而同时,你还是能够应用evemt 关键字获取事件对象,所以我感觉没有必要手动传递事件对象

应用 try{}catch(err){} 优化, 避免报错

 <button onclick="try{clickBtn()}catch(err){console.log(err)}"> 点击 </button>

js 代码

  function clickBtn() {console.log(this); //window
      console.log(event); //MouseEvent {isTrusted: true, constructor: Object}
    }

特点:

  • 写法 1 间接把代码全副写在 html 标签内时,this值为事件指标元素
  • 写法 2 把函数抽离进去,this值为window,
  • 将事件函抽离进去,可能会呈现在函数解析之前点击的状况,会报错,能够应用 try/catch 包起来
  • 写法 1 和 2 都能够应用关键字 event 间接拜访事件对象, 不须要定义或者从参数中获取

DOM0 级事件处理程序

通过 JavaScript 指定事件处理程序的传统形式,就是将一个函数赋值给一个事件处理程序属性。

<button id="btn"> 点击 </button>

js 代码:

 var btn = document.getElementById("btn");
    btn.onclick = function() {console.log(this.id);          
      console.log(event);           
      console.log("被点击了");      
    };
    btn.onclick = function() {console.log(this.id);          //btn
      console.log(event);           //MouseEvent {isTrusted: true, constructor: Object}
      console.log("哈哈哈哈");      // 哈哈哈哈
    };
 // 删除事件处理程序
 btn.onclick = null
特点:
  • 这样增加的事件处理程序,被当做是元素的办法,所以是在元素的作用域运行,this指向以后元素
  • 这样增加的事件处理程序,会在事件流的冒泡阶段被解决
  • 同一元素的同一事件,只能指定一个事件处理程序,如果指定多个,前面的会笼罩后面,只执行一个
  • 删除事件处理函数,能够将事件处理程序,也就是 btn.onclick 置为null

DOM2 级事件处理程序

DOM2 级事件定义了两个办法,用于解决指定和删除事件处理程序的操作:addEventListener()removeEventListener()。所有 DOM 节点都蕴含这两个办法,并且它们都承受 3 个参数:要解决的事件名、作为事件处理程序的函数和一个布尔值。最初这个布尔值如果是true,示意在捕捉阶段调用事件处理程序;如果是false, 示意在冒泡阶段调用事件处理程序。

全为冒泡
    <div id="box1">
      <div id="box2">
        <div id="box3">
            <div id="box4"> 点击我 </div>
        </div>
      </div>
    </div>

js 代码:

    var box1 = document.getElementById("box1");
    var box2 = document.getElementById("box2");
    var box3 = document.getElementById("box3");
    var box4 = document.getElementById("box4");
    box1.addEventListener("click",function() {console.log("box1")})
    box2.addEventListener("click",function() {console.log("box2")});
    box3.addEventListener("click",function() {console.log("box3")});
    // 为 box4 的 click 事件绑定两个函数
    box4.addEventListener("click",function() {console.log("box4")});
    box4.addEventListener("click",function() {console.log("我是 box4 的输入")});
    // 输入 this 和 event
    box4.addEventListener("click",function() {console.log(this,event)});
    

点击 idbox4的元素输入:

box4 
我是 box4 的输入 
<div id="box4"> 点击我 </div>  MouseEvent {isTrusted: true, constructor: Object}
box3 
box2 
box1  
全为捕捉
    var box1 = document.getElementById("box1");
    var box2 = document.getElementById("box2");
    var box3 = document.getElementById("box3");
    var box4 = document.getElementById("box4");
    box1.addEventListener("click",function() {console.log("box1")},true);
    box2.addEventListener("click",function() {console.log("box2")},true);
    box3.addEventListener("click",function() {console.log("box3")},true);
    box4.addEventListener("click",function() {console.log("box4")},true);
    box4.addEventListener("click",function() {console.log("我是 box4 的输入");});

点击 idbox4的元素输入:

box1 
box2 
box3 
box4 
我是 box4 的输入
有捕捉有冒泡
    var box1 = document.getElementById("box1");
    var box2 = document.getElementById("box2");
    var box3 = document.getElementById("box3");
    var box4 = document.getElementById("box4");
    box1.addEventListener("click",function() {console.log("box1")});
    box2.addEventListener("click",function() {console.log("box2")},true);
    box3.addEventListener("click",function() {console.log("box3")});
    box4.addEventListener("click",function() {console.log("我是 box4 的输入")});
    box4.addEventListener("click",function() {console.log("box4")},true);
    

点击 idbox4的元素输入:

box2 
我是 box4 的输入 
box4 
box3 
box1

下面这个例子就有点意思了,事件流不是按捕捉、指标、冒泡的程序吗?那不应该是顺次输入 box2、box4、我是 box4 的输入、box3、box1 吗?为啥这里是先输入 我是 box4 的输入 再输入 box4
如果你有疑难,能够往前翻,事件流外面讲到 在 DOM 事件流中,理论的指标在捕捉阶段不会承受到事件 , 怎么了解?能够认为,在给指标元素增加事件时,第三个参数指定为true, 即在捕捉阶段调用,是有效的,所以就相当于第三个参数是false, 也就是默认值,这也就是为什么说 处于指标 阶段的事件会 被看成冒泡阶段的一部分 , 既然是冒泡,那就按绑定程序执行,所以就先输入 我是 box4 的输入

移除事件处理程序
 <div id="box"> 点击我 </div>

js 代码:

var btn = document.getElementById('box');
btn.addEventListener('click',clickHandler,true)
function clickHandler(){console.log('box')
}
// 移除事件处理程序
btn.removeEventListener('click',clickHandler,true)
特点:
  • 同一事件能够指定多个处理程序函数,会按绑定程序顺次执行
  • this 指向指标元素本身
  • 能够间接通过 event 关键字获取事件对象
  • 事件流在达到指标元素时,局部捕捉和冒泡,按绑定程序执行
  • 若要移除事件,则事件处理程序对应的函数必须是具名函数,且 removeEventListeneraddEventListener的三个参数必须统一

IE 事件处理程序

IE中实现了与 DOM2 级相似的两个办法:attachEvent()detachEvent()。这两个办法承受两个参数:事件处理程序名称和事件处理程序函数。因为IE8 及更早版本只反对事件冒泡,所以通过这种办法增加的事件处理程序都会被增加到冒泡阶段

只有 IE10 及以下版本的 IE 和 Opera 浏览器反对 IE 事件处理程序

增加事件处理程序
    <div id="box1">
      <div id="box2">
        <div id="box3">
          <div id="box4"> 点击我 </div>
        </div>
      </div>
    </div>

js 代码

    var box1 = document.getElementById("box1");
    var box2 = document.getElementById("box2");
    var box3 = document.getElementById("box3");
    var box4 = document.getElementById("box4");
    box1.attachEvent("onclick", function() {console.log("box1");
    });
    box2.attachEvent("onclick", function() {console.log("box2");
    });
    box2.attachEvent("onclick", function() {console.log("box2 正本");
    });
    box3.attachEvent("onclick", function() {console.log("box3");
    });
    box4.attachEvent("onclick", function() {console.log("box4");
    });
    box4.attachEvent("onclick", function() {console.log("我是 box4 的输入");
    });
    box4.attachEvent("onclick", function() {console.log(this, event);
    });

点击 idbox4元素,输入如下:

  • IE10、IE9
box4
我是 box4 的输入
[object Window] [object MSEventObj]
box3
box2
box2 正本
box1
  • IE8 及以下
[object Window] [object Object]
我是 box4 的输入
box4
box3
box2 正本
box2
box1
移除事件处理程序
<div id="box"> 点击我 </div>

js 代码

var btn = document.getElementById('box');
btn.attachEvent('click',clickHandler)
function clickHandler(){console.log('box')
}
// 移除事件处理程序
btn.detachEvent('click',clickHandler)
特点:
  • 第一个参数是事件处理程序,不是事件名,须要加on
  • 事件处理程序在全局作用域运行,this 指向window
  • 事件处理程序函数外部能够间接应用 event 关键字获取事件对象
  • 同一元素同一事件,能够指定多个事件处理程序,IE9、IE10按绑定程序执行,IE8及以下按倒序执行
  • 若要移除事件处理程序,事件处理程序对应函数应该应用具名函数,detachEvent()办法须要和 attachEvent() 办法两个参数统一

DOM0 级事件处理程序和 DOM2 级事件处理程序优先级

    <div id="box1">
      <div id="box2">
        <div id="box3">
          <div id="box4"> 点击我 </div>
        </div>
      </div>
    </div>

js 代码

    var box1 = document.getElementById("box1");
    var box2 = document.getElementById("box2");
    var box3 = document.getElementById("box3");
    var box4 = document.getElementById("box4");
     box1.onclick = function(){console.log('box1 的 DOM0 级冒泡')
    }
    box1.addEventListener("click", function() {console.log("box1 的 DOM2 级捕捉");
    },true);
    box2.addEventListener("click", function() {console.log("box2 的 DOM2 级捕捉");
    },true);
    box2.onclick = function(){console.log('box2 的 DOM0 级冒泡')
    }
    box3.onclick = function(){console.log('box3 的 DOM0 级冒泡')
    }
    box3.addEventListener("click", function() {console.log("box3 的 DOM2 级冒泡");
    });
    box4.addEventListener("click", function() {console.log("box4 的 DOM2 级");
    });
    box4.onclick = function(){console.log('box4 的 DOM0 级')
    }

点击 idbox4元素,输入如下:

box1 的 DOM2 级捕捉
box2 的 DOM2 级捕捉
box4 的 DOM2 级
box4 的 DOM0 级
box3 的 DOM0 级冒泡
box3 的 DOM2 级冒泡
box2 的 DOM0 级冒泡
box1 的 DOM0 级冒泡
特点
  • DOM0 级事件和 DOM2 级事件能够共存
  • 因为 DOM0 级只有冒泡,所以先执行 DOM2 级的捕捉阶段,而后在冒泡阶段,DOM0 级和 DOM2 级无优先级,按绑定程序执行

留神:DOM0 级、DOM1 级、DOM2 级这些都是 W3C 举荐的一种规范,之所以没说 DOM1 级事件,是因为 DOM1 级次要定义的是 HTML 和 XML 文档的底层构造,没有对于事件的局部,而 DOM2 级在 DOM1 级的根底上引入了更多交互能力,就包含 DOM2 级事件


创作不易,如果你感觉文章对你有帮忙,能够关注我的集体公众号 前端 V , 感激你的反对!!

退出移动版