3

DOM 事件机制

  • 事件是元素天生具备的行为方式(和写不写 JS 代码没关系),当我们去操作元素的时候会触发元素的很多事件
  • 给当前元素的某一个事件绑定方法,目的是为了让当前元素某个事件被触发的时候,可以做一些事情

DOM0 事件

DOM0 事件绑定

element.onclick=function(e){
  //=>this:element
   e=e||window.event;

element.onmouseenter=function(){}
  • 给当前元素对象的某一个私有属性(onxxx 这样的私有属性)赋值的过程(之前属性默认值是 null,如果我们给赋值一个函数,相当于绑定了一个方法)
  • 当赋值成功(赋值一个函数),此时浏览器会把 DOM 元素和赋值的函数建立关联,以及建立 DOM 元素行为操作的监听,当某一个行为被用户触发,浏览器会把相关行为赋值的函数执行
  • 1、只有 DOM 元素天生拥有这个私有属性(onxxx 事件私有属性),我们赋值的方法才叫做事件绑定,否则属于给当前元素设置一个自定义属性而已
  • 2、移除事件绑定的时候,我们只需要赋值为 null 即可
  • 3、在 DOM0 事件绑定中,只能给当前元素的某一个事件行为(某一个事件私有属性)绑定一个方法,绑定多个方法,最后一次绑定的会把之前绑定的都替换掉
btn.onclick = function() {
  console.log("this is a click event");
};

click 事件并没有像其他函数一样,必须要调用才可以执行,click 事件并不确定什么时候发生,而当浏览器发现用户点击该按钮时,浏览器就检测 btn.onclick 是否有值,如果有,就会执行 btn.onclick.call(btn,event),此时函数执行,call() 方法接收两个参数,第一个指向调用当前方法的对象,也就是 this

需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 nullundefinedthis 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

另一个参数则是事件对象 event,该对象也可以通过 arguments[0] 来访问,它包含了事件相关的所有信息,如本例子中,则包含了点击事件的全部信息。可以通过给函数传参来获取事件信息。

btn.onclick = function(e) {
  console.log("this is a click event");
  console.log(e); //  事件对象
};

在 DOM0 级中,如果想要实现一个对象绑定多个函数,可以这样实现。

function fn1() {
  // do something
}
function fn2() {
  // do something
}
btn.onclick = function(e) {
  fn1.call(this, xxx);
  fn2.call(this, yyy);
};
https://github.com/HarleyWang...

DOM2 事件

当前这个实例 document.body 之所以能用 addEventListener 这个方法,说明这个方法肯定在它的原型上出现。

可以用 instanceof 检测

DOM2 级事件定义了两个方法,用于处理指定和删除事件处理程序的操作,addEventListener()removeEventListener(),所有的 DOM 节点中都包含这两个方法,它们都接收 3 个参数

  • 要处理的事件名
  • 作为事件处理程序的函数
  • 布尔值,true 代表在捕获阶段调用事件处理程序,false 表示在冒泡阶段调用事件处理程序,默认为 false
btn.addEventListener("click", function() {
  //  do something
});
btn.addEventListener("click", function() {
  //  do something else
});
element.addEventListener(
  "click",
  function(event) {
    // 只执行一次的代码
  },
  { once: true }
);
  • 1、 DOM2 事件绑定使用的 addEventListener/attachEvent 都是在 EventTarget 这个内置类的原型上定义的,调取使用的时候,首先通过原型链找到这个方法,然后执行完成事件绑定的效果
  • 浏览器首先会给当前元素的某一个事件行为开辟一个事件池(事件队列)[其实是浏览器有一个统一的事件池,我们每个元素的某个行为绑定的方法都放在这个事件池中,只是通过相关的标识来区分的],当我们通过 addEventListener 做事件监听的时候,会把绑定的方法存放在事件池中
  • 当元素的某一个行为触发,浏览器会到对应的事件池中,把当前存放在事件池中的所有方法,依次按照存放的先后顺序执行

DOM0 是私有属性赋值,DOM2 是事件池的事件机制

  • 1、所有 DOM0 支持的事件行为,DOM2 都可以使用,不仅如此,DOM2 还支持一些 DOM0 没有的事件行为:DOMContentLoaded...
  • 2、DOM2 中可以给当前元素的某一个事件行为绑定‘多个不同的方法’(不同的方法=>不同的空间地址,比如一个私有 fn 和全局的 fn 地址不一样,所以不是同一个 fn 方法,)(因为绑定的所有方法都存放在事件池中了;而 DOM0 是通过给私有属性赋值来绑定方法的,一个私有属性只能附一个值)
  • 3、DOM2 事件绑定的移除比较麻烦一些,需要和绑定的时候:事件类型、绑定的方法、传播阶段,三个完全一致才可以移除掉

DOM3 事件

DOM3 级事件在 DOM2 级事件的基础上添加了更多的事件类型,全部类型如下

事件类型 说明 举例
UI 事件 当用户与页面上的元素交互时触发 {load、scroll
焦点事件 当元素获得或失去焦点时触发 blur、focus
鼠标事件 当用户通过鼠标在页面执行操作时触发 dbclick、mouseup
滚轮事件 当使用鼠标滚轮或类似设备时触发 mousewheel
文本事件 当在文档中输入文本时触发 textInput
键盘事件 当用户通过键盘在页面上执行操作时触发 keydown、keypress
合成事件 当为 IME(输入法编辑器)输入字符时触发 compositionstart
变动事件 当底层 DOM 结构发生变化时触发 DOMsubtreeModified

同时 DOM3 级事件也允许开发人员自定义一些事件。

var event = new Event('build');

// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);

// Dispatch the event.
elem.dispatchEvent(event);

添加自定义数据 – CustomEvent()

var event = new CustomEvent("build", { detail: elem.dataset.time });

冒泡事件&捕获事件

img

事件委托

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

那么利用事件冒泡或捕获的机制,可以对事件绑定做一些优化。 在 JS 中,如果我们注册的事件越来越多,页面的性能就越来越差,因为:

  • 函数是对象,会占用内存,内存中的对象越多,浏览器性能越差
  • 注册的事件一般都会指定 DOM 元素,事件越多,导致 DOM 元素访问次数越多,会延迟页面交互就绪时间。
  • 删除子元素的时候不用考虑删除绑定事件

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

<script>
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 判断是否匹配目标元素
    if (target.nodeName.toLocaleLowerCase() === 'li') {
        console.log('the content is: ', target.innerHTML);
    }
});
</script>

EventTarget

DOM 的事件操作(监听和触发),都定义在 EventTarget 接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequestAudioNodeAudioContext)也部署了这个接口

该接口主要提供三个实例方法。

  • addEventListener:绑定事件的监听函数;用于在当前节点或对象上,定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。
  • removeEventListener:移除事件的监听函数;方法用来移除 addEventListener 方法添加的事件监听函数;removeEventListener 方法的参数,与 addEventListener 方法完全一致
  • dispatchEvent:触发事件;当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了 Event.preventDefault(),则返回值为 false,否则为 true;参数是 Event 实例

Event 对象常见的方法和属性

  • event. preventDefault();如果调用这个方法,默认事件行为将不再触发。什么是默认事件呢?例如表单一点击提交按钮(submit)刷新页面、a 标签默认页面跳转或是锚点定位等
  • event.stopPropagation() & event.stopImmediatePropagation() event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行; stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发。而 stopPropagation 只能实现前者的效果
https://juejin.im/post/5be643...

https://www.w3.org/TR/DOM-Lev...

https://javascript.ruanyifeng...

https://javascript.ruanyifeng...


xiaoping
337 声望12 粉丝

保持学习,记一下自己的学习经历


« 上一篇
React 错误边界
下一篇 »
手写 forEach