JavaScript事件总结

318 阅读20分钟

1. 事件流

事件冒泡

IE 事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触 发,然后向上传播至没有那么具体的元素(文档)。

事件捕获

事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。

DOM事件流

DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。

2. 事件处理程序

2.1 HTML事件处理程序

基本示例:

<input type="button" value="Click Me" onclick="console.log('Clicked')"/>

或者:

<script>
     function showMessage() {
     console.log("Hello world!");
     }
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
  • 属性的值是 JavaScript 代码
  • 事件处理程序执行的代码可以访问全局作用域中的一切。

this指向元素本身

<!-- 输出"Click Me" -->
<input type="button" value="Click Me" onclick="console.log(this.value)"> 

缺陷:

  • 第一个问题是时机问题。有可能 HTML 元素已经显示在页面上,用户都与其交互了,而事件处理程序的代码还无法执行。
  • 另一个问题是对事件处理程序作用域链的扩展在不同浏览器中可能导致不同的结果。
  • 最后一个问题是 HTML 与 JavaScript 强耦合。

2.2 DOM0事件处理程序

基本示例:

let btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log("Clicked");
}; 

注意,前面的代码在运行之后才会给事件处理程序赋值。因此如果在页面中上面的代码出现在按钮之后,则有可能出现用户点击按钮没有反应的情况。

像这样使用 DOM0 方式为事件处理程序赋值时,所赋函数被视为元素的方法。因此,事件处理程序会在元素的作用域中运行,即 this 等于元素。下面的例子演示了使用 this 引用元素本身:

let btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log(this.id); // "myBtn"
};

将事件绑定赋值为null,即可移除事件绑定

btn.onclick = null; // 移除事件处理程序

如果事件处理程序是在 HTML 中指定的,则 onclick 属性的值是一个包装相应HTML 事件处理程序属性值的函数。这些事件处理程序也可以通过在 JavaScript 中将相应属性设置为 null 来移除。

2.3 DOM2 事件处理程序

DOM2 Events 为事件处理程序的赋值和移除定义了两个方法:addEventListener()和removeEventListener()。这两个方法暴露在所有 DOM 节点上,它们接收 3 个参数:事件名、事件处理函数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。

  • 基本示例与this指向
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id); // myBtn,this指向元素
}, false);
  • 添加多个事件处理程序:
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id);
}, false);
btn.addEventListener("click", () => {
 console.log("Hello world!");
}, false);

多个事件处理程序以添加顺序来触发,因此前面的代码会先打印元素 ID,然后显示消息“Hello world!”。

  • 移除事件处理程序

通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除,如下面的例子所示:

匿名函数无法移除:

let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
 console.log(this.id);
 }, false);
// 其他代码
btn.removeEventListener("click", function() { // 没有效果!
 console.log(this.id);
}, false);

相同函数名可以移除:

let btn = document.getElementById("myBtn");
let handler = function() {
 console.log(this.id);
};
btn.addEventListener("click", handler, false);
// 其他代码
btn.removeEventListener("click", handler, false); // 有效果!

捕获与冒泡的例子:

#outer {
  width: 200px;
  height: 200px;
  background-color: aqua;
}
#inner {
  width: 100px;
  height: 100px;
  background-color: blueviolet;
}
<div id='outer'>
    <div id='inner'></div>
</div>
/* 先弹outer,再弹inner */
// 在冒泡阶段触发
document.getElementById('inner').addEventListener('click', showId);
// 在捕获阶段就触发
document.getElementById('outer').addEventListener('click', showId, true);

function showId() {
  alert(this.id);
}

2.3 IE事件处理程序

IE 实现了与 DOM 类似的方法,即 attachEvent()和 detachEvent()。这两个方法接收两个同样 的参数:事件处理程序的名字和事件处理函数。因为 IE8 及更早版本只支持事件冒泡,所以使用 attachEvent()添加的事件处理程序会添加到冒泡阶段。

  • 基本示例与this指向
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
 console.log("Clicked");
});

使用 DOM0方式时,事件处理程序中的 this 值等于目标元素。而使用 attachEvent()时,事件处理程序是在全局作用域中运行的,因此 this 等于 window。来看下面使用 attachEvent()的例子:

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
 console.log(this === window); // true
}); 
  • 添加多个事件,事件触发顺序与添加顺序相反
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
 console.log("Clicked");
});
btn.attachEvent("onclick", function() {
 console.log("Hello world!");
}); 
  • 移除事件,同样传递相同的引用,匿名函数无法移除
var btn = document.getElementById("myBtn");
var handler = function() {
 console.log("Clicked");
};
btn.attachEvent("onclick", handler);
// 其他代码
btn.detachEvent("onclick", handler);

2.4 跨浏览器事件处理程序

封装通用的事件处理程序

var EventUtil = {
 addHandler: function(element, type, handler) {
 if (element.addEventListener) {
 element.addEventListener(type, handler, false);
 } else if (element.attachEvent) {
 element.attachEvent("on" + type, handler);
 } else {
 element["on" + type] = handler;
 }
 },
 removeHandler: function(element, type, handler) {
 if (element.removeEventListener) {
 element.removeEventListener(type, handler, false);
 } else if (element.detachEvent) {
 element.detachEvent("on" + type, handler);
 } else {
 element["on" + type] = null;
 }
 cun'za
};

使用:

let btn = document.getElementById("myBtn")
let handler = function() {
 console.log("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
// 其他代码
EventUtil.removeHandler(btn, "click", handler);

依然存在执行顺序等问题,还是用框架或者其他库吧。

3. 事件对象

在 DOM 中发生事件时,所有相关信息都会被收集并存储在一个名为 event 的对象中。这个对象包 含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据。例如,鼠标操作导致的事件会生成鼠标位置信息,而键盘操作导致的事件会生成与被按下的键有关的信息。所有浏览器都支持这个 event 对象,尽管支持方式不同。

基本示例:

let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
 console.log(event.type); // "click"
};
btn.addEventListener("click", (event) => {
 console.log(event.type); // "click"
}, false);
  • event属性和方法

image.png

image.png

  • currentTarget, target

事件处理函数内部,this始终等于event.currentTarget,会等于绑定事件处理程序的元素。event.target会等于实际触发事件的元素。

document.body.onclick = function(event) {
 console.log(event.currentTarget === document.body); // true
 console.log(this === document.body); // true
 console.log(event.target === document.getElementById("myBtn")); // true
};
  • preventDefault() preventDefault()方法用于阻止特定事件的默认动作。比如,链接的默认行为就是在被单击时导 航到 href 属性指定的 URL。如果想阻止这个导航行为,可以在 onclick 事件处理程序中取消,如下面的例子所示:
let link = document.getElementById("myLink");
link.onclick = function(event) {
     event.preventDefault();
};

任何可以通过 preventDefault()取消默认行为的事件,其事件对象的 cancelable 属性都会设置为 true。

  • stopPropagation() stopPropagation()方法用于立即阻止事件流在 DOM 结构中传播,取消后续的事件捕获或冒泡。 例如,直接添加到按钮的事件处理程序中调用 stopPropagation(),可以阻止 document.body 上注 册的事件处理程序执行。比如:
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
     console.log("Clicked");
     event.stopPropagation();
};
document.body.onclick = function(event) {
     console.log("Body clicked");
};

阻止冒泡之后,body绑定的点击事件就不会被触发

IE事件对象略

4. 事件类型

DOM3 Events 定义了如下事件类型:

  • 用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件。
  • 焦点事件(FocusEvent):在元素获得和失去焦点时触发。
  • 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
  • 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
  • 输入事件(InputEvent):向文档中输入文本时触发。
  • 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
  • 合成事件(CompositionEvent):在使用某种 IME(Input Method Editor,输入法编辑器)输入字符时触发。

4.1 用户界面事件

  • load:在 window 上当页面加载完成后触发,在窗套(<frameset>)上当所有窗格(<frame>)都加载完成后触发,在元素上当图片加载完成后触发,在元素上当相应对象加载完成后触发。
    • 在 window 对象上,load 事件会在整个页面(包括所有外部资源如图片、JavaScript 文件和 CSS 文件)加载完成后触发。可以通过两种方式指定 load 事件处理程序。第一种是JavaScript 方式,如下所示:
    window.addEventListener("load", (event) => {
         console.log("Loaded!");
    });
    
    第二种指定 load 事件处理程序的方式是向元素添加 onload 属性,window上添加的事件,event.target都等于document:
    <body onload="console.log('Loaded!')">
    
    • 图片上也会触发load事件
    <img src="smile.gif" onload="console.log('Image loaded.')">
    
    let image = document.getElementById("myImage");
    image.addEventListener("load", (event) => {
     console.log(event.target.src);
    }); 
    
  • unload:在 window 上当页面完全卸载后触发,在窗套上当所有窗格都卸载完成后触发,在<object>元素上当相应对象卸载完成后触发。
  • abort:在<object>元素上当相应对象加载完成前被用户提前终止下载时触发。
  • error:在 window 上当 JavaScript 报错时触发,在<img>元素上当无法加载指定图片时触发,在<object>元素上当无法加载相应对象时触发,在窗套上当一个或多个窗格无法完成加载时触发。
  • select:在文本框(或 textarea)上当用户选择了一个或多个字符时触发。
  • resize:在 window 或窗格上当窗口或窗格被缩放时触发。
  • scroll:当用户滚动包含滚动条的元素时在元素上触发。<body>元素包含已加载页面的滚动条。大多数 HTML 事件与 window 对象和表单控件有关。 这些事件在 DOM2 Events 中都被归为 HTML Events。
  • <div class='wrapper'>
        <div class="content"></div>
    </div>
    
    .wrapper {
      width: 320px;
      height: 400px;
      margin: 100px auto;
      padding: 0;
      overflow-y: scroll;
    }
    
    .content {
      width: 300px;
      height: 1000px;
      background-color: aqua;
    }
    
    let wrapper = document.querySelector('.wrapper');
    // scrollTop必须是一个设置了overflow: scroll的元素才有意义
    wrapper.onscroll = function() {
      console.log(this.scrollTop);
      // 可以通过scrollTop获取滚动高度,也可以通过设置scrollTop使其滚动
      if(this.scrollTop > 200) {
        this.scrollTop = 0;
      }
    }
    

    4.2 焦点事件

    • blur:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。
    • focus:当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持。
    • focusin:当元素获得焦点时触发。这个事件是 focus 的冒泡版。
    • focusout:当元素失去焦点时触发。这个事件是 blur 的通用版。 焦点事件中的两个主要事件是 focus 和 blur,这两个事件在 JavaScript 早期就得到了浏览器支持。它们最大的问题是不冒泡。这导致 IE后来又增加了 focusin 和 focusout,Opera又增加了 DOMFocusIn和 DOMFocusOut。IE 新增的这两个事件已经被 DOM3 Events 标准化。

    当焦点从页面中的一个元素移到另一个元素上时,会依次发生如下事件。

    (1) focuscout 在失去焦点的元素上触发。
    (2) focusin 在获得焦点的元素上触发。
    (3) blur 在失去焦点的元素上触发。
    (4) focus 在获得焦点的元素上触发。
    

    其中,blur、和 focusout 的事件目标是失去焦点的元素,而 focus、和 focusin 的事件目标是获得焦点的元素。

    4.3 鼠标和滚轮事件

    DOM3 Events定义了 9 种鼠标事件。

    • click:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。这主要是基于无障碍的考虑,让键盘和鼠标都可以触发 onclick 事件处理程序。
    • dblclick:在用户双击鼠标主键(通常是左键)时触发。这个事件不是在 DOM2 Events 中定义的,但得到了很好的支持,DOM3 Events 将其进行了标准化。
    • mousedown:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发。
    • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseenter 事件不是在 DOM2 Events 中定义的,而是 DOM3 Events中新增的事件。
    • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseleave 事件不是在 DOM2 Events 中定义的,而是 DOM3 Events中新增的事件。
    • mousemove:在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。
    • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。这个事件不能通过键盘触发。
    • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。
    • mouseup:在用户释放鼠标键时触发。这个事件不能通过键盘触发。

    页面中的所有元素都支持鼠标事件。除了 mouseenter 和 mouseleave,所有鼠标事件都会冒泡, 都可以被取消,而这会影响浏览器的默认行为。

    由于事件之间存在关系,因此取消鼠标事件的默认行为也会影响其他事件。比如,click 事件触发的前提是 mousedown 事件触发后,紧接着又在同一个元素上触发了 mouseup事件。如果 mousedown 和 mouseup 中的任意一个事件被取消,那么 click 事件就不会触发。类似地,两次连续的 click 事件会导致 dblclick 事件触发。只要有任何逻辑阻止了这两个 click 事件发生(比如取消其中一个 click 事件或者取消 mousedown 或 mouseup 事件中的任一个),dblclick 事件就不会发生。这 4 个事件永远会按照如下顺序触发:

    (1) mousedown
    (2) mouseup
    (3) click
    (4) mousedown
    (5) mouseup
    (6) click
    (7) dblclick
    

    鼠标事件在 DOM3 Events 中对应的类型是"MouseEvent",而不是"MouseEvents"。
    鼠标事件还有一个名为滚轮事件的子类别。滚轮事件只有一个事件 mousewheel,反映的是鼠标滚 轮或带滚轮的类似设备上滚轮的交互。

    4.3.1 页面坐标

    4.3.2 修饰键

    虽然鼠标事件主要是通过鼠标触发的,但有时候要确定用户想实现的操作,还要考虑键盘按键的状 态。键盘上的修饰键 Shift、Ctrl、Alt 和 Meta 经常用于修改鼠标事件的行为。DOM 规定了 4 个属性来表示这几个修饰键的状态:shiftKey、ctrlKey、altKey 和 metaKey。这几属性会在各自对应的修饰键被按下时包含布尔值 true,没有被按下时包含 false。在鼠标事件发生的,可以通过这几个属性来检测修饰键是否被按下。

    let div = document.getElementById("myDiv");
    div.addEventListener("click", (event) => {
         let keys = new Array();
         if (event.shiftKey) {
             keys.push("shift");
         }
         if (event.ctrlKey) {
             keys.push("ctrl");
         }
         if (event.altKey) {
             keys.push("alt");
         }
         if (event.metaKey) {
             keys.push("meta");
         }
         console.log("Keys: " + keys.join(","));
    });
    

    4.3.3 鼠标按键

    对 mousedown 和 mouseup 事件来说,event 对象上会有一个 button 属性,表示按下或释放的是哪个按键。DOM 为这个 button 属性定义了 3 个值:0 表示鼠标主键、1 表示鼠标中键(通常也是滚轮键)、2 表示鼠标副键。

    let div = document.getElementById('myDiv')
    div.onmousedown = event => console.log(event.button)
    

    4.3.4 额外事件信息

    DOM2 Events 规范在 event 对象上提供了 detail 属性,以给出关于事件的更多信息。

    4.3.5 mousewheel 事件

    mousewheel 事件的 event 对象包含鼠标事件的所有标准信息,此外还有一个名为 wheelDelta 的新属性。当鼠标滚轮向前(上)滚动时,wheelDelta 每次都是+120;而当鼠标滚轮向后(下)滚动时,wheelDelta 每次都是–120

    document.addEventListener("mousewheel", (event) => {
     console.log(event.wheelDelta);
    });
    

    4.4 键盘与输入事件

    键盘事件包含 3 个事件:

    • keydown,用户按下键盘上某个键时触发,而且持续按住会重复触发。
    • keypress,用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc 键也会触发这个事件。DOM3 Events 废弃了 keypress 事件,而推荐 textInput 事件。
    • keyup,用户释放键盘上某个键时触发。 虽然所有元素都支持这些事件,但当用户在文本框中输入内容时最容易看到。

    输入事件只有一个,即 textInput。

         这个事件是对 keypress 事件的扩展,用于在文本显示给用户之前更方便地截获文本输入。textInput 会在文本被插入到文本框之前触发。
         当用户按下键盘上的某个字符键时,首先会触发 keydown 事件,然后触发 keypress 事件,最后触发 keyup 事件。注意,这里 keydown 和 keypress 事件会在文本框出现变化之前触发,而 keyup事件会在文本框出现变化之后触发。如果一个字符键被按住不放,keydown 和 keypress 就会重复触发,直到这个键被释放。
         对于非字符键,在键盘上按一下这个键,会先触发 keydown 事件,然后触发 keyup 事件。如果按住某个非字符键不放,则会重复触发 keydown 事件,直到这个键被释放,此时会触发 keyup 事件。

    键盘事件支持与鼠标事件相同的修饰键。event中shiftKey、ctrlKey、altKey 和metaKey属性在键盘事件中都是可用的。

    4.4.1 键码

    对于 keydown 和 keyup 事件,event 对象的 keyCode 属性中会保存一个键码,对应键盘上特定 的一个键。对于字母和数字键,keyCode 的值与小写字母和数字的 ASCII 编码一致。比如数字 7 键的keyCode 为 55,而字母 A 键的 keyCode 为 65。

    let textbox = document.getElementById("myText");
    textbox.addEventListener("keyup", (event) => {
     console.log(event.keyCode);
    }); 
    

    非字符键键码:

    image.png

    4.4.2 DOM3变化

    DOM3 Events 规范定义了 key 和 char 两个新属性。
    其中,key 属性用于替代 keyCode,且包含字符串。在按下字符键时,key 的值等于文本字符(如 “k”或“M”);在按下非字符键时,key 的值是键名(如“Shift”或“ArrowDown”)。char 属性在按 下字符键时与 key 类似,在按下非字符键时为 null。

    let input = document.getElementById('input')
    input.onkeydown = event => console.log(event.key)
    

    4.4.3 textInput事件

    作 为对 keypress 的替代,textInput 事件的行为有些不一样。

    • 一个区别是 keypress 会在任何可以获得焦点的元素上触发,而 textInput 只在可编辑区域上触发。
    • 另一个区别是 textInput 只在有新字符被插入时才会触发,而 keypress 对任何可能影响文本的键都会触发(包括退格键)。 因为 textInput 事件主要关注字符,所以在 event 对象上提供了一个 data 属性,包含要插入的 字符(不是字符编码)。data 的值始终是要被插入的字符,因此如果在按 S 键时没有按 Shift 键,data的值就是"s",但在按 S 键时同时按 Shift 键,data 的值则是"S"。
    let textbox = document.getElementById("myText");
    textbox.addEventListener("textInput", (event) => {
         console.log(event.data);
    });
    

    4.4.4 change事件

    <input><textarea>元素的 value 发生变化且失去焦点时触发,或者在 <select>元素中选中项发生变化时触发。

    4.5 HTML5 事件

    4.5.1 contextmenu 事件

    contextmenu 事件冒泡,因此只要给 document 指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。这个事件在所有浏览器中都可以取消,在 DOM 合规的浏览器中使用 event.preventDefault()。
    简单的示例:

    <div id="myDiv">Right click or Ctrl+click me to get a custom context menu.
        Click anywhere else to get the default context menu.</div>
    <ul id="myMenu" style="position:absolute;visibility:hidden;background-color:
    silver">
        <li><a href="http://www.somewhere.com"> somewhere</a></li>
        <li><a href="http://www.wrox.com">Wrox site</a></li>
        <li><a href="http://www.somewhere-else.com">somewhere-else</a></li>
    </ul>
    
    window.addEventListener("load", (event) => {
      let div = document.getElementById("myDiv");
      div.addEventListener("contextmenu", (event) => {
        event.preventDefault();
        let menu = document.getElementById("myMenu");
        menu.style.left = event.clientX + "px";
        menu.style.top = event.clientY + "px";
        menu.style.visibility = "visible";
      });
      document.addEventListener("click", (event) => {
        document.getElementById("myMenu").style.visibility = "hidden";
      });
    });
    

    4.5.2 beforeunload 事件

    beforeunload 事件会在 window 上触发,用意是给开发者提供阻止页面被卸载的机会。这个事件 会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。这个事件不能取消,否则就意味着可以把用户永久阻拦在一个页面上。相反,这个事件会向用户显示一个确认框,其中的消息表明浏览器即将卸载页面,并请用户确认是希望关闭页面,还是继续留在页面上。

    4.5.3 DOMContentLoaded 事件

    window 的 load 事件会在页面完全加载后触发,因为要等待很多外部资源加载完成,所以会花费 较长时间。而 DOMContentLoaded 事件会在 DOM 树构建完成后立即触发,而不用等待图片、JavaScript文件、CSS 文件或其他资源加载完成。相对于 load 事件,DOMContentLoaded 可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能够更快地与页面交互。

    4.5.4 hashchange

    HTML5 增加了 hashchange 事件,用于在 URL 散列值(URL 最后#后面的部分)发生变化时通知 开发者。这是因为开发者经常在 Ajax 应用程序中使用 URL 散列值存储状态信息或路由导航信息。 onhashchange 事件处理程序必须添加给 window,每次 URL 散列值发生变化时会调用它。event 对象有两个新属性:oldURL 和 newURL。这两个属性分别保存变化前后的 URL,而且是包含散列值的 完整 URL。下面的例子展示了如何获取变化前后的 URL:

    window.addEventListener("hashchange", (event) => {
     console.log(`Old URL: ${event.oldURL}, New URL: ${event.newURL}`);
    });
    

    如果想确定当前的散列值,最好使用 location 对象:

    window.addEventListener("hashchange", (event) => {
     console.log(`Current hash: ${location.hash}`);
    }); 
    

    5. 内存与性能

    JavaScript 中,页面中事件处理程序的数量与页面整体性能直接相关。原因有很多。

    • 首先,每个函数都是对象,都占用内存空间,对象越多,性能越差。
    • 其次,为指定事件处理程序所需访问 DOM 的次数会先期造成整个页面交互的延迟。 只要在使用事件处理程序时多注意一些方法,就可以改善页面性能。

    5.1 事件委托

    “过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。

    示例:

    <ul id="myLinks">
     <li id="goSomewhere">Go somewhere</li>
     <li id="doSomething">Do something</li>
     <li id="sayHi">Say hi</li>
    </ul> 
    
    let list = document.getElementById("myLinks");
    list.addEventListener("click", (event) => {
      let target = event.target;
      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":
          console.log("hi");
          break;
      }
    });
    

    5.2 删除事件处理程序

    把事件处理程序指定给元素后,在浏览器代码和负责页面交互的 JavaScript 代码之间就建立了联系。这种联系建立得越多,页面性能就越差。除了通过事件委托来限制这种连接之外,还应该及时删除不用的事件处理程序。很多 Web 应用性能不佳都是由于无用的事件处理程序长驻内存导致的。 导致这个问题的原因主要有两个。

    • 第一个是删除带有事件处理程序的元素。 比如通过真正的 DOM方法 removeChild()或 replaceChild()删除节点。最常见的还是使用 innerHTML 整体替换页面的某一部分。这时候,被 innerHTML 删除的元素上如果有事件处理程序,就不会被垃圾收集程序正常清理。
    • 另一个可能导致内存中残留引用的问题是页面卸载。 同样,IE8 及更早版本在这种情况下有很多问题,不过好像所有浏览器都会受这个问题影响。如果在页面卸载后事件处理程序没有被清理,则它们仍然会残留在内存中。之后,浏览器每次加载和卸载页面(比如通过前进、后退或刷新),内存中残留对象的数量都会增加,这是因为事件处理程序不会被回收。

    一般来说,最好在 onunload 事件处理程序中趁页面尚未卸载先删除所有事件处理程序。这时候也 能体现使用事件委托的优势,因为事件处理程序很少,所以很容易记住要删除哪些。关于卸载页面时的清理,可以记住一点:onload 事件处理程序中做了什么,最好在 onunload 事件处理程序中恢复。