DOM 事件模型 或 事件机制

165 阅读4分钟

1. 什么是DOM

DOM文档对象模型(Document Object Model),以根元素开头展开成一颗树,描述了处理网页内容的方法和接口。DOM是W3C的标准,定义了访问 HTML 和 XML 文档的标准。

2. 什么是DOM事件

DOM使Javascript有能力对HTML上的事件做出反应。这些事件包括鼠标键盘的点击事件、移动事件以及页面中内容的变化等。HTML元素事件是浏览器内在自动产生的,当有事件发生时html元素会向外界(这里主要指元素事件的订阅者)发出各种事件,如click,onmouseover,onmouseout等等。

3. DOM事件模型

  DOM事件模型分为两类:一类是IE所使用的冒泡型事件(Bubbling);另一类是DOM标准定义的冒泡型与捕获型(Capture)的事件。 除IE外的其他浏览器都支持标准的DOM事件处理模型。

4. DOM事件流

DOM的结构是一个树形,每当HTML元素产生事件时,该事件就会在树的根节点和元素节点之间传播,所有经过的节点都会收到该事件。

4.1 事件捕获与事件冒泡

  • 捕获:当用户点击按钮,浏览器会从 window 从上向下遍历至用户点击的按钮,逐个触发事件处理函数。
  • 冒泡:浏览器从用户点击的按钮从下往上遍历至 window,逐个触发事件处理函数。
  • W3C 事件模型/事件机制:对每个事件先捕获再冒泡

一个事件发生之后,会在子元素和父元素之间传播,这种传播分成三个阶段

  • 第一阶段:从window对象传导到目标节点(从外到内),称之为"捕获阶段";
  • 第二阶段: 在目标节点上触发,称之为"目标阶段";
  • 第三阶段: 从目标节点传回window对象(从内到外),称之为"冒泡阶段";

image.png

如何去使用呢?

可以通过addEventListener的第三个参数控制

e.addEventLisenter('click',f2,true) // true按捕获方向执行函数

e.addEventLisenter('click',f2,false) // false按冒泡方向执行函数,默认为false

但有一个特例:捕获和冒泡是发生在父子元素之间的这个传播过程,如果在同一个元素上绑定同样的事件分别在冒泡和捕获阶段都执行的话,那么他执行顺序是怎样嘛?

demo.addEventListener("click",()=>{
  console.log("冒泡")
})

demo.addEventListener("click",()=>{
  console.log("捕获")
})

// 冒泡 捕获

那么只和监听函数的绑定顺序有关了,谁先监听谁先执行。

4.2 取消冒泡

通过 e.stopPropagation 中断冒泡事件的向下或向上传递

$list.addEventListener('click', (e) => {
  console.log('list capturing');
  e.stopPropagation();
}, true);

但是捕获是没有办法取消的

不过,有一点需要注意 stopPropagation 不能阻止同一节点的其他 listener 的执行

复制代码若想让同一节点的其他 listener 不被执行,我们可以使用 e.stopImmediatePropagation 方法。

4.3 e.target 与 e.currentTarget

  • target 是触发事件的某个具体的对象,只会出现在事件机制的目标阶段,即谁触发了事件,谁就是 target
  • currentTarget 是绑定事件的对象。

4.4 阻止默认的点击事件执行

<a id="list_item_link" href="#">click me</a>

// list_item_link 的冒泡
$list_item_link.addEventListener('click', (e) => {
  e.preventDefault();
}, false);

当我们点击超链接时,就不会执行原本的默认行为(新开分页或跳转)

注意到:不管是在捕获阶段还是在冒泡阶段,只要使用了 preventDefault 方法,即可取消默认行为的执行 。

5 事件列表

一共有100多种事件,可以通过 MDN 查询 列表 或者 自己再浏览器上输入可查

for (i in window) { 
    if ( /^on/.test(i)) { console.log(i); }
}

6 事件代理(委托)

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

而再事件委托其实就是把事件监听放在祖先元素上。 好处是: 节约监听数量;可以监听动态生成的元素

使用事件代理的优点

  • 减少内存消耗,提高性能 假如有大量的列表项,我们需要给每个添加一个响应事件
<div id="div1">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    ...
    <div>item n</div>
</div>
  • 动态绑定事件

代码实现:

div1.addEventListener('click', (e)=>{
    // 兼容性处理
    const e = e || window.event;
    if(t.target.tagName.toLowerCase() === 'div'){
        console.log('div', e.target.innerHTML);
    }
    // 或 t.target.nodeName.toLocaleLowerCase() === 'div' 也是可以的
    
})

jQuery 的事件委托

  • $.on: 基本用法: (.parent).on(click,a,function()console.log(clickeventontaga);),它是.parent元素之下的a元素的事件代理到('.parent').on('click', 'a', function () { console.log('click event on tag a'); }),它是 .parent 元素之下的 a 元素的事件代理到 ('.parent') 之上,只要在这个元素上有点击事件,就会自动寻找到 .parent 元素下的 a 元素,然后响应事件;

  • $.delegate: 基本用法: (.parent).delegate(a,click,function()console.log(clickeventontaga);),同上,并且还有相对应的('.parent').delegate('a', 'click', function () { console.log('click event on tag a'); }),同上,并且还有相对应的 .delegate 来删除代理的事件;\

  • $.live: 基本使用方法: (a,('a', ('.parent')).live('click', function () { console.log('click event on tag a'); }),同上,然而如果没有传入父层元素 (.parent),那事件会默认委托到(.parent),那事件会默认委托到 (document) 上;(已废除)

推荐文章

事件模型

事件机制

这篇也是事件机制😁