Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS事件:捕获与冒泡、事件处理程序、事件对象、跨浏览器、事件委托 #38

Open
amandakelake opened this issue Mar 28, 2018 · 1 comment

Comments

@amandakelake
Copy link
Owner

amandakelake commented Mar 28, 2018

一、捕获与冒泡

事件流描述的是从页面中接收事件的顺序
IE 的事件流是事件冒泡流
而 Netscape Communicator 的事件流是事件捕获流

DOM2级事件规定的事件流包括三个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段
    首先发生的是事件捕获,为截获事件提供了机会。
    然后是实际的目标接收到事件。
    最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应
    a192fc27-28d8-46c6-8400-fd3831e239c5

画重点(这部分内容可以先跳过,看完下面的内容再回头消化)
1、当处于目标阶段,没有捕获与冒泡之分,执行顺序会按照addEventListener的添加顺序决定,先添加先执行

2、使用stopPropagation()取消事件传播时,事件不会被传播给下一个节点,但是,同一节点上的其他listener还是会被执行

// list 的捕获
$list.addEventListener('click', (e) => {
  console.log('list capturing');
  e.stopPropagation();
}, true)
  
// list 捕获 2
$list.addEventListener('click', (e) => {
  console.log('list capturing2');
}, true)

// list capturing
// list capturing2

如果想要同一层级的listener也不执行,可以使用stopImmediatePropagation()

3、preventDefault()只是阻止默认行为,跟JS的事件传播一点关系都没有

4、一旦发起了preventDefault(),在之后传递下去的事件里面也會有效果

二、事件处理程序

共有三种事件处理程序:DOM0、DOM2、IE

var btn = document.getElementById('btn');

btn.onClick = () => {
  console.log('我是DOM0级事件处理程序');
}
btn.onClick = null;

btn.addEventListener('click', () => {
  console.log('我是DOM2级事件处理程序');
}, false);
btn.removeEventListener('click', handler, false)

btn.attachEvent('onclick', () => {
  console.log('我是IE事件处理程序')
})
btn.detachEvent('onclicn', handler);

画重点:

DOM2级的好处是可以添加多个事件处理程序;DOM0对每个事件只支持一个事件处理程序

通过DOM2添加的匿名函数无法移除,上面写的例子就移除不了,addEventListenerremoveEventListenerhandler必须同名

作用域:DOM0的handler会在所属元素的作用域内运行,IE的handler会在全局作用域运行,this === window

触发顺序:添加多个事件时,DOM2会按照添加顺序执行,IE会以相反的顺序执行,请谨记

跨浏览器的事件处理程序

var EventUtil = {
  // element是当前元素,可以通过getElementById(id)获取
  // type 是事件类型,一般是click ,也有可能是鼠标、焦点、滚轮事件等等
  // handle 事件处理函数
  addHandler: (element, type, handler) => {
    // 先检测是否存在DOM2级方法,再检测IE的方法,最后是DOM0级方法(一般不会到这)
    if (element.addEventListener) {
      // 第三个参数false表示冒泡阶段
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent(`on${type}`, handler)
    } else {
      element[`on${type}`] = handler;
    }
  },

  removeHandler: (element, type, handler) => {
    if (element.removeEventListener) {
      // 第三个参数false表示冒泡阶段
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent(`on${type}`, handler)
    } else {
      element[`on${type}`] = null;
    }
  }
}

// 获取元素
var btn = document.getElementById('btn');
// 定义handler
var handler = function(e) {
  console.log('我被点击了');
}
// 监听事件
EventUtil.addHandler(btn, 'click', handler);
// 移除事件监听
// EventUtil.removeHandler(button1, 'click', clickEvent);

三、事件对象

DOM0和DOM2的事件处理程序都会自动传入event对象

IE中的event对象取决于指定的事件处理程序的方法(上面说过)

IE的handler会在全局作用域运行,this === window
所以在IE中会有window.eventevent两种情况

只有在事件处理程序期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁

event对象里需要关心的几个属性

this、currentTarget、target

这三个属性跟冒泡和捕获有关
target永远是被添加了事件的那个元素,thiscurrentTarget就不一定了(延伸思考:事件处理程序在父节点中的情况)

eventPhase

调用事件处理程序的阶段,有三个值
1:捕获阶段
2:处于目标
3:冒泡阶段

阻止默认preventDefault与传播stopPropagation

preventDefault:比如链接被点击会导航到其href指定的URL,这个就是默认行为

stopPropagation:立即停止事件在DOM层次中的传播,包括捕获和冒泡事件

IE中的对象的对应属性

srcElement => target
returnValue => preventDefaukt()
cancelBubble => stopPropagation()
IE 不支持事件捕获,因而只能取消事件冒泡,但stopPropagation可以同时取消事件捕获和冒泡

四、跨浏览器的事件对象

根据上面对不同类型的事件以及属性区分

var EventUtil = {
  addHandler: (element, type, handler) => {},

  removeHandler: (element, type, handler) => {}
  // 获取event对象
  getEvent: (event) => {
    return event ? event : window.event
  },
  // 获取当前目标
  getTarget: (event) => {
    return event.target ? event.target : event.srcElement
  },
  // 阻止默认行为
  preventDefault: (event) => {
    if (event.preventDefault) {
      event.preventDefault()
    } else {
      event.returnValue = false
    }
  },
  // 停止传播事件
  stopPropagation: (event) => {
    if (event,stopPropagation) {
      event.stopPropagation()
    } else {
      event.cancelBubble = true
    }
  }
}

五、事件委托

这一小节在《高程》P403
我自认不能写的比它更精简更好,有些地方就直接搬过来了

事件委托用来解决事件处理程序过多的问题

页面结构如下

<ul id="myLinks">
  <li id="goSomewhere">Go somewhere</li>
  <li id="doSomething">Do something</li>
  <li id="sayHi">Say hi</li>
</ul>

按照传统的做法,需要像下面这样为它们添加 3 个事 件处理程序。

var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function(event){
    location.href = "http://www.wrox.com";
});
EventUtil.addHandler(item2, "click", function(event){
    document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function(event){
    alert("hi");
});

如果在一个复杂的 Web 应用程序中,对所有可单击的元素都采用这种方式,那么结果就会有数不 清的代码用于添加事件处理程序。此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序,如下面的例子所示

var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event) {
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  switch(target.id) {
  case "doSomething":
      document.title = "I changed the document's title";
      break;
  case "goSomewhere":
      location.href = "http://www.wrox.com";
      break;
  case "sayHi": 9 alert("hi");
    break; 
  }
}

子节点的点击事件会冒泡到父节点,并被这个注册事件处理

最适合采用事件委托技术的事件包括 clickmousedownmouseupkeydownkeyupkeypress。 虽然 mouseovermouseout 事件也冒泡,但要适当处理它们并不容易,而且经常需要计算元素的位置。

可以考虑为 document 对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件,需要跟踪的事件处理程序越少,移除它们就越容易(移除事件处理程序关乎内存和性能)。
只要是通过 onload 事件处理程序添加的东西,最后都要通过 onunload 事件处理程序将它们移除

在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。

后记

感谢您耐心看到这里,希望有所收获!

如果不是很忙的话,麻烦右上角点个star⭐,举手之劳,却是对作者莫大的鼓励。

我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步,更多文章请看【amandakelake的Github博客】

参考

DOM 的事件傳遞機制:捕獲與冒泡 | TechBridge 技術共筆部落格
What Is Event Bubbling in JavaScript? Event Propagation Explained

@caichuanwang
Copy link

有两个建议:
1.当处于目标阶段,没有捕获与冒泡之分,执行顺序会按照addEventListener的添加顺序决定,先添加先执行 -------这句话现在在chrome的91版本已经不适用了,现在的91版本是按照先捕获在冒泡的顺序执行的,不是按代码顺序
2.使用stopPropagation()取消事件传播时------------stopPropagation()应该是取消了事件的冒泡,传播会让人误解
以上是个人的愚见,希望博主采纳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants