js 事件

目录

前言

一、事件流

1、事件冒泡

2、事件捕获

3、DOM 事件流

二、事件处理程序

1、HTML 事件处理程序(了解)

2、DOM0 事件处理程序(了解)

3、DOM2 事件处理程序

 4、IE 事件处理程序

5、跨浏览器的事件处理程序

三、事件对象

1、DOM 中的事件对象

2、IE 中的事件对象

 3、跨浏览器的事件对象

四、事件类型

1、UI 事件

(1)、load 事件

(2)、unload 事件

(3)、resize 事件

(4)、scroll 事件

2、焦点、鼠标与滚轮事件

(1)、焦点事件

(2)、鼠标事件

(3)、滚轮事件

3、键盘与文本事件

(1)、键码

(2)、字符编码

(3)、DOM3 级键盘事件的变化

(4)、textInput 事件

4、复合事件

5、变动事件

(1)、删除节点

(2)、插入节点

6、HTML5 事件

(1)、contextmenu 事件

(2)、beforeunload 事件

(3)、DOMContentLoaded 事件

(4)、readystatechange 事件

(5)、pageshow 和 pagehide 事件

(6)、hashchange 事件

7、设备事件

(1)、orientationchange 事件

(2)、deviceorientationevent 事件

(3)、devicemotion 事件

8、触摸与手势事件

(1)、兼容 DOM 的触摸事件

(2)、跟踪触摸的事件

(3)、手势事件

五、内存和性能

1、事件委托

2、移除事件处理程序

六、事件循环(Event Loop)

1、进程与线程

2、宏队列与微队列

(1)、宏队列

(2)、微队列

3、JavaScript 执行代码的具体顺序

4、Event Loop 实例

七、事件模拟

1、DOM 中的事件模拟 和 IE 中的事件模拟事件


 

前言

本文会用到  EventUtil 事件处理程序,详情请戳:https://blog.csdn.net/mChales_Liu/article/details/106541683 

本文包含 EventUtil 跨浏览器事件处理程序的部分完善过程。

JavaScript 与 HTML 之间交互是通过事件实现的。事件就是用户或浏览器自身执行的某种动作,是文档或浏览器窗口中发生的一些特定的交互瞬间。

我们可以使用侦听器或处理程序来预定事件,以便事件发生时执行相应的代码。

 

一、事件流

当触发某个事件时,事件从子元素到父元素或者从父元素到子元素的过程叫做事件流。

IE 和 Netscape 开发团队分别提出了完全相反的事件流:IE 的事件流是冒泡流;Netscape 的事件流是捕获流。

DOM 2 级规定的事件流,包括三个阶段:

  • 事件捕获阶段
  • 目标阶段
  • 事件冒泡阶段

1、事件冒泡

IE 事件流叫做事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

IE8 及其更早版本只支持冒泡流。

例如:




    Document


    

如果你单击了页面中的

元素,在事件冒泡里,这个 click 事件就会按照如下图顺序传播:

js 事件_第1张图片

事件冒泡的演示:

/* style 中 */


/* html 中 */
/* JavaScript 中 */

我只点击紫红色的 son 元素一下,事件就开始冒泡了,请看效果:

js 事件_第2张图片

阻止事件冒泡

son.onclick = function(e){
    e = e || window.event;
    // 阻止事件冒泡(兼容 IE 8 及其以下)
    e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
    alert("son 被触发");
}

2、事件捕获

Netscape 的事件流叫做事件捕获:越不太具体的节点越先接收事件,越具体的节点越后接收事件。事件补货的用意在于在事件到达预定目标之前捕获它。

例如:




    Document


    

 如果你单击了页面中的

元素,在事件捕获里,这个 click 事件就会按照如下图顺序传播:

js 事件_第3张图片

事件捕获的演示:

/* style 中 */
.parent{
    width: 400px;
    height: 400px;
    background: #008000;
    margin-left:400px;
}
.son{
    display: block;
    width: 180px;
    height: 180px;
    background: #f0f;
    margin: 5px;
}

/* html 中 */
/* JavaScript 中 */

上述代码中, addEventListener() 方法的第三个参数决定了事件的方式——捕获或者冒泡,true为捕获,false为冒泡,默认为false。后面会细讲 addEventListener() 方法的。

我只点击紫红色的 son 元素一下,事件就开始捕获了,请看效果:

js 事件_第4张图片

阻止事件捕获

window.addEventListener("click", function(e){
    e = e || window.event;
    e.stopPropagation();// 阻止事件捕获
    alert("window 被触发");
}, true);

由上述代码可知:stopPropagation() 方法可以阻止事件的进一步传播(包括 冒泡 和 捕获)。

3、DOM 事件流

DOM2 事件流包括三个阶段:事件捕获阶段、处于目标阶段 和 事件冒泡阶段。

DOM2 明确规定:事件捕获仅仅为截获事件提供机会,不会接收到事件目标。多数支持 DOM 事件流的浏览器都实现了一种特定的行为,但仍有一些浏览器依然支持“事件捕获和事件冒泡都可以接收到事件。”

IE8 及其更早版本不支持 DOM 事件流。

例如:




    Document


    

  如果你单击了页面中的

元素,在 DOM 事件流中,这个 click 事件就会按照如下图顺序传播:

js 事件_第5张图片

 

二、事件处理程序

响应某个事件的函数叫做事件处理程序(或事件侦听器)。

1、HTML 事件处理程序(了解)

一个元素支持的每种事件,都可以用一个与相应事件处理程序同名的 HTML 特性来制定。这个特性的值应该是能够执行的 JavaScript 代码。比如:

上述代码,通过指定 onClick 特性,将要执行的具体动作作为它的值来定义。当然,也可以选择调用在页面其他地方自定义的脚本,比如:


HTML 事件处理程序存在三大问题:

  • 时差问题:因为用户可能会在 HTML 元素已出现在页面上就出发相应的时间,但当时的事件处理程序有可能尚不具备执行条件。为此,一般会把 HTML 处理程序封装在一个 try-catch 语句中,以便在错误显示在页面之前拦截捕获它,这样用户就不会看到错误了(开发人员可自行设置抛出此类错误)。
  • 不同的浏览器不同的结果:由于浏览器解析规则不一致,很可能会解析出不同的结果。
  • HTML 与 JavaScript 代码紧密耦合:如果要更换事件处理程序,就必须同时改动 HTML 和 JavaScript 代码,这正是很多开发人员抛弃 HTML 事件处理程序的原因。

2、DOM0 事件处理程序(了解)

DOM0 事件处理程序就是讲一个函数 赋值 给一个事件处理程序属性。

优点是:简单、能够跨浏览器执行。

缺点是:同一个事件处理程序添加多次,后添加的会覆盖之前的。

// html 中
// DOM0 事件处理程序 var myDiv = document.getElementById("myDiv"); // 添加 DOM0 事件处理程序 myDiv.onmouseenter = function(){ console.log("Hello world"); // "Hello world" }; myDiv.onmouseout = function(){ console.log("Bye-bye"); } myDiv.onmouseout = function(){ console.log("Get out"); // "Get out" } // 删除 DOM0 事件处理程序 myDiv.onmouseenter = null; myDiv.onmouseout = null;

3、DOM2 事件处理程序

DOM2 定义了两个操作事件处理程序的方法:

  • addEventListener():添加事件处理程序,同一个事件处理程序可以添加多次。接受三个参数,分别是 要处理的事件名、作为事件处理程序的函数 (可选)一个布尔值。这个布尔值默认为 false,如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。另外,在事件处理程序的内部 this 始终指向 currentTarget,即添加事件处理程序的调用者。
  • removeEventListener():删除事件处理程序。(参数特性与 addEventListener() 方法一样),使用 addEventListener() 方法添加的事件处理程序必须用 removeEventListener() 方法来删除,而且删除时传入的参数与添加事件处理程序时的参数必须完全一致。这意味着通过 addEventListener() 添加的匿名函数将无法被移除,所以我们必须规避掉这个大坑。

比如:

// html 中


// JavaScript 中
var btn = document.getElementById("btn");
btn.addEventListener("click", function(){
    alert("hello world");                                             // hello world
});
btn.removeEventListener("click", function(){
    alert("hello world");
});

// 删除失败,click 事件依然有效。改成下面这样写就能删除 click 事件了:
var handler = function(){
    alert("hello world");
};
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);

 4、IE 事件处理程序

IE 实现了与 DOM 类似的两个方法:

  • attachEvent():添加事件处理程序,同一个事件处理程序可以添加多次。接受两个参数,分别是 事件处理程序名称事件处理程序函数。使用该方法添加事件处理程序与 DOM 有 4 大区别:
    • 只会将事件处理程序添加到冒泡阶段,这是因为 IE8 及其更早版本只支持冒泡流。
    • 第一个参数必须写成 “on” + 事件类型 的形式
    • this 指向 window,而不是事件处理程序调用者。
    • 执行顺序是“先添加的后执行”,类似于栈存储方式(先进后出)。DOM 是先添加的先执行,类似于队列存储方式(先进先出)。
  • detachEvent():删除事件处理程序。(参数特性与 attachEvent() 方法一样。)使用 attachEvent() 方法添加的事件处理程序必须用 detachEvent() 方法来删除,而且删除时传入的参数与添加事件处理程序时的参数必须完全一致。这意味着通过 attachEvent() 添加的匿名函数将无法被移除,所以我们必须规避掉这个大坑。这里与 DOM 的 removeEventListener() 方法原理一致,不再举例。

5、跨浏览器的事件处理程序

想要跨浏览器实现事件处理程序,目前有两种途径:

  • 一个是使用能够隔离浏览器差异的 JavaScript 库。
  • 另一个是自己编码实现(要恰当的使用“能力检测”来检测浏览器的能力)。

要保证事件处理程序能在大多数浏览器下一致的运行,只需要关注冒泡阶段!!!

下面列举一个跨浏览器实现事件处理程序的案例:

// 封装一个跨浏览器的事件处理程序对象
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;
        }
    }
}

// 使用这个对象添加、删除跨浏览器事件处理程序
var handler = function(){
    alert(hello world);
};
EventUtil.addHandler(btn, "clcik", handler);
EventUtil.removeHandler(btn, "clcik", handler);

不过,上述实现方案仍然存在以下问题:

  • IE 添加事件处理程序的作用域问题;
  • DOM0 对每个事件只支持一个事件处理程序,否则会发生覆盖。

 

三、事件对象

触发某个事件时生成的对象叫做事件对象。事件对象包含着所有与事件相关的信息。

1、DOM 中的事件对象

DOM 中的事件对象是 event 对象。

在 DOM0 中 event 对象是 window 对象的一个属性,在 DOM2 中 event 对象就是 event 对象。

event 对象有以下属性:

属性 类型 读/写 描述
detail Integer 只读 与事件相关的细节信息。
target Element 只读 事件的目标。
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素。
type String 只读 被触发的事件类型。
cancelable Boolean 只读 表明是否可以取消事件的默认行为。
defaultPrevented Boolean 只读 为 true 表示已经调用了 preventDefault() 方法。(DOM3 新增)
eventPhase Integer 只读 调用事件处理程序的阶段:1 表示捕获阶段;2 表示目标阶段;3 表示冒泡阶段。
bubbles Boolean 只读 表明事件是否冒泡。
view AbstractView 只读 与时间关联的抽象视图。等同于发生事件的 window 对象。
trusted Boolean 只读 为 true 表示事件是浏览器生成的;为 false 表示事件是由开发人员通过 JavaScript 创建的。(DOM3 新增)

event 对象有以下方法:

  • preventDefault():取消事件的默认行为。必须设置 cancelable 为 true,才可以使用该方法。
  • stopPropagation():取消事件的进一步捕获或冒泡。必须设置 bubbles 为 true,才可以使用该方法。
  • stopImmediatePropagation():取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用(DOM3 新增方法)。

2、IE 中的事件对象

IE 的 event 对象有以下属性:

IE 浏览器中,根据添加事件处理程序的不同方式,访问 event 对象的方式分为三种:

  • 用 DOM0 添加事件处理程序时,event 对象作为 window 对象的一个属性存在。
  • 用 attachEvent() 方法添加事件处理程序时,event 对象作为参数被传入事件处理程序函数中。
  • 用 HTML 特性添加事件处理程序时,event 对象作为 event 变量存在。
// DOM0
var btn = document.getElementById("myBtn");
btn.onclick = function(){
    var event = window.event;
    alert(event);                                      // "click"
};

// attachEvent 方法
var btn = document.getElementById("myBtn");
var handle = function(event){
    alert(event);                                      // "click"
}
btn.addachEvent("onclick", handle);

// HTML
      // "click"

 3、跨浏览器的事件对象

// EventUtil.js 封装
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;
        }
    },
    // 获取 event 对象
    getEvent: function(){
        return event ?  event : window.event;
    },
    // 获取事件目标
    getTarget: function(){
        return event.target || event.srcElement;
    },
    // 取消默认事件行为
    preventDefault: function(){
        if(event.preventDefault){
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    // 阻止事件冒泡
    stopPropagation: function(){
        if(event.stopPropagation){
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
    } 
}

 

四、事件类型

事件类型大致可分为:

  • DOM 事件
    • UI 事件:当用户与页面上的元素交互时触发。
    • 焦点事件:元素获得或失去焦点。
    • 鼠标事件:通过鼠标在页面上执行操作。
    • 滚轮事件:使用鼠标滚轮或类似设备。
    • 文本事件:当用户在文档中输入文本。
    • 键盘事件:通过键盘在页面上执行操作。
    • 合成事件:当为 IME(输入法编辑器)输入字符时触发。
    • 变动事件:底层 DOM 结构发生变化。
  • HTML5 事件:DOM 规范没有涵盖所有浏览器支持的所有事件,HTML5 详尽列出了浏览器应该支持的所有事件。
  • 专有事件
    • 设备事件:针对手机、平板等各种设备的事件。
    • 触摸与手势事件

一般,在 window 上触发的任何事件都可以在 元素中通过相应的特性来指定。

 DOM3 级事件是在 DOM0 级和 DOM2 级事件的基础上重新定义了DOM 的事件。包括 IE 9 在内的所有主流浏览器都支持 DOM2 级和 DOM3 级的事件。

1、UI 事件

  • load 事件:页面完全加载完后(所有图像、js 文件、css 文件等)在 window 上触发;所有框架加载完毕后在框架集上触发;图像加载完毕在 img 元素上触发;当嵌入内容加载完毕在 元素上触发。
  • unload 事件:页面完全卸载在 window 上触发;所有框架都卸载后在框架集上触发;嵌入内容卸载完毕后在 元素上触发。
  • abort 事件:当用户停止下载过程,如果嵌入内容没有加载完,则在 元素上触发。
  • error 事件:当js错误时在 window 上触发;当一或多个框架无法加载在框架集上触发;当无法加载图像时在 img 元素上触发;当无法加载嵌入内容时在 元素上触发。
  • select 事件:当用户选择文本框()中的一个或多个字符时触发。
  • resize 事件:当窗口或框架的大小变化时在 window 或 框架 上触发。
  • scroll 事件:当用户滚动带滚动条的元素中的内容时,在该元素上触发。 元素中包含所加载页面的滚动条。
  • 如何检测浏览器是否支持 DOM2 级或 DOM3 级规定的 HTML 事件呢?

    var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
    // 或
    var isSupported = document.implementation.hasFeature("HTMLEvents", "3.0");

    只有根据 DOM2 级或 DOM3 级事件实现这些事件的浏览器才会返回 true,而以非标准方式支持这些事件的浏览器则会返回 false。

    (1)、load 事件

    当页面完全加载完毕后(所有图像、js文件、css文件等),就会触发window上面的load事件。

    这个事件在 window 上面触发,因此,可以通过 JavaScript 或者 元素中的 onresize 特性来指定事件处理程序:

    --> 第一种方式是使用 JavaScript 的跨浏览器事件处理程序:

    EventUtil.addHandler(window, "load", function(event){
        alert("Loaded");
    });

    --> 第二种方式是为 元素添加一个 onload 特性:

    
    
    
        load 事件案例
    
    
        
    
    

     其实,在 DOM2 级事件规范中,应该在 document 而非 window 对象上触发 load 事件,但是,所有的浏览器都在 window 上实现了该事件,以确保向后兼容。另外,还有一些非标准的方式支持 load 事件,比如:在 IE9+、Firefox、Opera、Chrome 和 Safari3+ 中,

    效果:

     

    ⑤、探索鼠标点击 button 按钮时对 mousedown 和 mouseup 事件的影响

    由于只要在同一个元素上相继触发 mousedown 和 mouseup 事件就会触发 click 事件,所以,当我们单击时,会先触发 mousedown 和 mouseup 事件。 对于 mousedown 和 mouseup 事件,DOM 给 event 对象提供了一个 button 属性(注意不是