【重温基础】20.事件

本文是 重温基础 系列文章的第二十篇。html

这是第三个基础系列的第一篇,欢迎持续关注呀!
重温基础 系列的【初级】和【中级】的文章,已经统一整理到个人【Cute-JavaScript】的JavaScript基础系列中。前端

今日感觉:电影有时候看的是缘分。git

系列目录:github

本章节复习的是JS中的事件,事件冒泡捕获代理模拟等等。浏览器

前置知识:
JavaScript与HTML的交互式经过事件来实现的,是文档或浏览器窗口中发生的一些特定的交互瞬间。微信

1.事件流

事件流描述的是从页面中接收事件的顺序,一般有这样两种彻底相反的事件流概念:事件冒泡流(IE团队提出)和事件捕获流(网景团队提出)。闭包

1.1 事件冒泡

冒泡事件(Event Bubbling):事件开始时由最具体的元素接收(文档中嵌套层次最深的那个节点),而后逐层向上传播到较为不具体的节点(文档),看下示例代码:dom

<!DOCTYPE html>
<html>
<head>
    <title>leo 事件冒泡</title>
</head>
<body>
    <div id="leo">点击</div>
</body>
</html>
复制代码

点击页面中<div>元素,这个click事件就会按照下面顺序传播:函数

  1. <div>
  2. <body>
  3. <html>
  4. document

因而可知,元素绑定的事件会经过DOM树向上传播,每层节点都会发生,直到document对象,如图展现了冒泡过程:
post

事件冒泡流

1.2 事件捕获

事件捕获(Event Capturing):让不太具体的节点更早接收事件,而最具体的节点最后接收事件,即在事件到达预约目标以前捕获到,看下示例代码(HTML代码和前面同样),事件捕获的过程是这样的:

  1. document
  2. <html>
  3. <body>
  4. <div>

看得出,document对象最新接收事件,而后沿DOM树依次向下,直到最后的实际目标<div>元素,如图展现了捕获过程:

事件捕获流

注意:因为老版本的浏览器不支持,所以不多人使用事件捕获,不过若是特殊需求仍是可使用事件捕获,建议仍是使用事件冒泡。

1.3 DOM事件流

“DOM2级事件”规定的事件流包含三个阶段:事件捕获阶段处于目标阶段事件冒泡阶段
事件捕获为截获事件提供机会,而后实际的目标接收到事件,最后事件冒泡,对事件做出响应。按照前面的HTML代码,整个流程是这样的:

DOM事件流

在DOM事件流中,实际目标(<div>元素)在捕获阶段不接收事件,即在捕获阶段,事件从document对象<html>再到<body>后就中止,进入“处于目标”阶段,事件在<div>元素上发生,而后才进入冒泡阶段,将事件传回给文档。

注意:目前主流浏览器都支持DOM事件流,只有IE8和以前版本不支持。

2.事件处理

事件处理,即响应某个事件。咱们把事件处理的函数,称为“事件处理程序”。
事件处理程序的名称通常都以on开头,如click事件的事件处理程序就是onclickload事件的事件处理程序就是onload
咱们将事件处理程序,分为这么几类:

  • HTML事件处理程序
  • DOM0级事件处理程序
  • DOM2级事件处理程序
  • IE事件处理程序
  • 跨浏览器事件处理程序

2.1 HTML事件处理程序

某个元素支持的事件,均可以用一个与相应事件处理程序同名的HTML特性来指定,这个特性的值应该是可以执行的JavaScript代码。好比:

<input type="button" value="点击" onclick="alert('hello leo');">
复制代码

也能够把须要执行的具体事件单独定义出来,能够放置与单独.js文件,也能够在文档内用<script>标签引入:

function fun(){
    alert('hello leo');
}
复制代码
<input type="button" value="点击" onclick="fun()">
复制代码

咱们经过这样指定事件处理程序,能够有一个局部变量event来获取事件对象自己,在这个函数内部,this值等于这个变量event

<input type="button" value="点击" onclick="fun(event)">
复制代码

另外,HTML中指定事件处理程序,会有2个缺点:

  1. 存在时间差
    可能出现这样的状况:HTML元素触发事件,可是事件处理程序还未定义(函数的定义在HTML最底下定义),就会出现报错,这与HTML代码加载顺序有关。
  2. 做用域链的异常 因为不一样浏览器JavaScript引擎遵循的标识符解析规则存在差别,致使访问非限定对象成员时出错,表现为事件处理程序的做用域链在不一样浏览器结果不一样。
  3. HTML和JavaScript代码紧密耦合 这经常就是不少开发人员放弃HTML事件处理程序的缘由。

2.2 DOM0级事件处理程序

经过赋值形式,将一个函数赋值给一个事件处理程序属性。每一个元素(包含windowdocument)都有本身的事件处理属性,这些属性一般所有小写,如onclick,将这种属性的值设置成一个函数,就能够指定事件处理程序:

var leo = document.getElementById('leo');
leo.onclick = function(){
    alert('hello leo!');
}
复制代码

使用DOM0级方法指定事件处理程序,被认为是元素的方法。此时的事件处理程序是在元素的做用域执行,那么,this就引用当前元素,能够访问元素的任何属性和方法:

var leo = document.getElementById('leo');
leo.onclick = function(){
    alert(this.id);  // "leo"
}
复制代码

咱们也能够经过设置事件处理程序属性来删除DOM0级的事件处理程序。

leo.onclick = null;
复制代码

2.3 DOM2级事件处理程序

有2个方法:

  • 添加事件处理程序:addEventListener()
  • 删除事件处理程序:removeEventListener()

全部的DOM节点都包含这两个方法,而且它们都接收三个参数:

  • 处理的事件名称
  • 事件处理程序的函数
  • 布尔值(true:事件捕获阶段调用,false:事件冒泡阶段调用)
var leo = document.getElementById('leo');
leo.addEventListener('click',function(){
    alert(this.id);        // "leo"
},false);
复制代码

与DOM0级方法同样,这里的事件处理程序也会是在元素的做用域中执行,所以this也是指向元素,能够访问元素的任何属性和方法。

使用DOM2级方法,能够添加多个事件处理程序,并按照添加顺序触发

var leo = document.getElementById('leo');
leo.addEventListener('click',function(){
    alert(this.id);       // "leo"
},false);
leo.addEventListener('click',function(){
    alert('hello leo!');  // "hello leo!"
},false);
复制代码

注意:经过addEventListener()添加的事件只能经过removeEventListener()移除,而且二者传入的参数一致,这就意味着经过addEventListener()添加的匿名函数不能被移除,看下面代码:

var leo = document.getElementById('leo');
leo.addEventListener('click',function(){
    alert(this.id);       // "leo"
},false);

// 没有效果
leo.removeEventListener('click',function(){
    // do some thing
},false);
复制代码

没有效果是由于这两个方法传入的函数,是彻底不一样的,为了达到删除事件处理程序的效果,咱们能够将处理函数单独定义出来:

var leo = document.getElementById('leo');
var fun = function(){
    alert(this.id);
}
leo.addEventListener('click',fun,false);
// 有效果
leo.removeEventListener('click',fun,false);
复制代码

2.4 IE事件处理程序

IE实现两种方法: attachEvent()detachEvent()。这两个方法接收相同的两个参数:事件处理程序名称事件处理程序函数
因为IE8和更早版本只支持事件冒泡,因此经过attachEvent()添加的事件处理程序会被添加到冒泡阶段。

var leo = document.getElementById('leo');
leo.attachEvent('onclick',function(){
    alert('hello leo');       // "leo"
},false);
// attachEvent也支持添加多个事件处理程序

leo.attachEvent('onclick',function(){
    alert('hello world');       // "leo"
},false);
复制代码

注意:这里的第一个参数是onclick而不是DOM的addEventListener()click

IE的attachEvent()和DOM0级方法区别
二者事件处理程序的做用域不一样。

  • DOM0级方法,做用域在所属元素的做用域。
  • attachEvent(),做用域在全局做用域,即this指向window

和DOM0级方法同样,detachEvent()只能移除使用attachEvent()添加的方法,为了不没法移除,也是须要将处理的函数单独定义出来:

var leo = document.getElementById('leo');
var fun = function(){
    alert(this.id);
}
leo.attachEvent('onclick',fun,false);
// 有效果
leo.detachEvent('onclick',fun,false);
复制代码

2.6 跨浏览器事件处理程序

在作跨浏览器事件处理程序,咱们能够有两种方式:

  1. 使用可以隔离浏览器差别的JavaScript库
  2. 单独手写一个处理方法

咱们单独受写一个处理方法也不难,只需关注好事件冒泡阶段,咱们能够建立一个方法,区分使用DOM0级方法DOM2级方法IE方法便可,默认采用DOM0级方法

var my_event = {
    addMyEvent: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;
        }
    };
    removeMyEvent: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;
        }
    }
}

复制代码

3.事件对象

当触发一个DOM上的事件时,都会产生一个事件对象event,并做为参数传入事件处理程序,这个对象包含全部与事件相关的信息。包括致使事件的元素、事件类型等其余信息。

3.1 DOM中的事件对象

不管指定事件处理程序时使用什么方法(DOM0级方法或DOM2级方法),都会传入event对象:

var leo = document.getElementById('leo');
leo.onclick = function(event){
    alert(event.type);  // 'click'
}
leo.addEventListener('click',function(event){
    alert(event.type);  // 'click'
},false);
复制代码

event对象包含与建立它的特定事件相关的属性和方法,经常有以下成员:

event成员1

event成员2

咱们可使用event中的对象和方法:

  • 阻止事件的默认行为
var leo = document.getElementById('leo');
leo.onclick = function(event){
    // 只有当 event 中的 cancelable 属性为true的事件
    event.preventDefault();
}
复制代码
  • 当即中止事件在DOM的传播

经过调用event.stopPropagation()方法避免弹框出现两次。

var leo = document.getElementById('leo');
leo.onclick = function(event){
    alert('leo');
    event.stopPropagation();
}
document.body.onclick = function(event){
    alert('hello leo');
}
复制代码

3.2 IE中的事件对象

访问IE中的事件对象event,方法有多种,取决于事件处理程序的方法:

  • DOM0级方法,使用window.event
var leo = document.getElementById('leo');
leo.onclick = function(){
    var event = window.event;
    alert(event.type);   // 'click'
}
复制代码
  • IE的attachEvent方法,event做为参数传入(也可使用window.event
var leo = document.getElementById('leo');
leo.attachEvent('onclick',function(event){
    alert(event.type);   // 'click'
},false);
复制代码

3.3 跨浏览器的事件对象

虽然DOM和IE中event对象不一样,但咱们也能够和前面的 跨浏览器事件处理程序 处理同样,经过他们之间的区别,实现映射:

var my_event = {
    myAddFun : function(element, type, handler){
        // do some thing
    },
    // 返回对event的引用
    getMyEvent : function(event){
        return event?event:window.event;
    },
    // 返回事件的目标
    getMyTarget : function(event){
        return event.target || event.srcElement;
    },
    // 取消事件的默认行为
    preventDefault : function(event){
        if(event.preventDefault){
            event.preventDefault();
        }else {
            event.returnValue = false;
        }
    },

    myRemoveFun : function(element, type, handler){
        // do some thing
    },

    // 阻止事件流
    stopPropagetion : function(event){
        if(event.stopPropagetion){
            event.stopPropagetion();
        }else {
            event.cancelBubble = true;
        }
    }
}

var leo = document.getElementById('leo');
leo.onclick = function(event){
    alert('leo');
    event = my_event(event);
    my_event.stopPropagation(event);
}

leo.onclick = function(event){
    alert('hello world');
}
复制代码

4.事件类型

Web浏览器中的事件类型有不少,DOM3级事件规定有如下几类事件类型:

  • UI事件:当用户与页面上元素交互时触发;
  • 焦点事件:当元素失去或获取焦点时触发;
  • 鼠标事件:当用户经过鼠标在页面操做时触发;
  • 滚轮事件:当使用鼠标滚轮(或相似设备)时触发;
  • 文本事件:当在文档中输入文本时触发;
  • 键盘事件:当用户经过键盘操做时触发;
  • 合成事件:当为IME输入字符时触发;
  • 变更事件:当底层DOM结构变更时触发;

具体每一个方法的详细介绍,能够查看**W3school HTML DOM Event 对象**
或者查看《JavaScript高级程序设计(第三版)》的第362页开始。
我后面会单独整理一篇,介绍这些事件的文章。

5.事件委托

简单理解就是讲一个响应事件委托到另外一个元素。
事件委托利用事件冒泡,指定一个事件处理程序来管理某一类型的全部事件,好比咱们经过一个函数来代替其余三个函数:

<ul id="btn">
    <li id="btn1">按钮1</li>
    <li id="btn2">按钮2</li>
    <li id="btn3">按钮3</li>
</ul>
复制代码
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
// my_event 在前面定义了
my_event.myAddFun(btn1, 'click', function(event){
    alert('btn1点击');
});
my_event.myAddFun(btn2, 'click', function(event){
    alert('btn2点击');
});
my_event.myAddFun(btn3, 'click', function(event){
    alert('btn3点击');
});
复制代码

下面咱们在DOM树层级更高的元素上添加一个事件处理函数,来作事件委托,咱们这么重写这段代码:

var btn = document.getElementById('btn');
my_event.myAddFun(btn, 'click', function(event){
    event = my_event.getMyEvent(event);
    var target = my_event.getMyTarget(event);
    switch(target.id){
        case "btn1":
            alert('btn1点击');
            break;
        case "btn2":
            alert('btn2点击');
            break;
        case "btn3":
            alert('btn3点击');
            break;
    }
});
复制代码

最适合采用事件委托技术的事件包括:click/mousedown/mouseup/keyup/keydown/keypress,虽然mouseovermouseout事件也有冒泡,但由于很差处理它们而且常常须要从新计算元素位置。

能够看出,事件委托有如下优势:

  • 减小内存消耗
  • 动态绑定事件

6.事件模拟

JavaScript的事件模拟主要用来在任意时刻触发特定事件。

6.1 DOM中的事件模拟

document对象上使用createEvent()方法建立一个event对象。
createEvent()接收一个参数,即要建立的事件类型的字符串。
DOM2级中,全部这些字符串都使用英文复数形式,DOM3级中都变成单数,也能够是下面中的字符串:

  • UIEvents : 通常化的UI事件(鼠标和键盘事件都继承自UI事件)(DOM3级中UIEvent
  • MouseEvents : 通常化的鼠标事件(DOM3级中MouseEvent
  • MutationEvents : 通常化的DOM滚动事件(DOM3级中MutationEvent
  • HTMLEvents : 通常化的HTML事件(DOM3级中HTMLEvent

建立event以后,咱们须要使用dispatchEvent()方法去触发这个事件,须要传入一个参数,参数是须要触发事件的event对象。

全部支持事件的DOM节点都支持这个方法。事件触发以后,事件就能照样冒泡并引起响应事件处理程序的执行。

6.1.1 模拟鼠标事件

使用createEvent()方法传入MouseEvents建立一个鼠标事件,返回的对象有一个initMouseEvent()方法,用于指定与该鼠标事件相关的信息,有15个参数:

  • type : 字符串,表示触发的事件类型,如click
  • bubble : 布尔值,表示是否冒泡,为了精确模拟鼠标事件,一般设置为true
  • cancelable :布尔值,表示是否能够取消,为了精确模拟鼠标事件,一般设置为true
  • view : 与事件关联的视图,基本都设置为document.defaultView
  • detail : 整数,与事件有关的详细信息,基本设置为0
  • screenX : 整数,事件相对屏幕的X坐标
  • screenY : 整数,事件相对屏幕的Y坐标
  • clientX : 整数,事件相对视口的X坐标
  • clientY : 整数,事件相对视口的Y坐标
  • ctrlKey : 布尔值,表示是否按下Ctrl键,默认false
  • altKey : 布尔值,表示是否按下Alt键,默认false
  • shiftKey : 布尔值,表示是否按下Shift键,默认false
  • metaKey : 布尔值,表示是否按下Meta键,默认false
  • button : 整数,表示按下哪一个鼠标键,默认0
  • relatedTarget : 对象,表示与事件相关的对象,只在mouseovermouseout时使用

案例:

var btn = document.getElementById('btn');
var myEvent = document.createEvent('MouseEvents');
myEvent.initMouseEvent(
    'click', true, true, document.defaultView, 
    0, 0, 0, 0, 0,
    false, false, false, false, 0, null
)
btn.dispatchEvent(myEvent);
复制代码

6.1.2 模拟键盘事件

DOM3级规定,使用createEvent()方法传入KeyboardEvent建立一个键盘事件,返回的对象有一个initKeyEvent()方法,有8个参数:

  • type : 字符串,表示触发的事件类型,如keydown
  • bubble : 布尔值,表示是否冒泡,为了精确模拟键盘事件,一般设置为true
  • cancelable :布尔值,表示是否能够取消,为了精确模拟键盘事件,一般设置为true
  • view : 与事件关联的视图,基本都设置为document.defaultView
  • key : 整数,表示按下的键的键码
  • localtion : 整数,表示按下哪里的键,默认0表示主键盘,1表示左,2表示右,3表示数字键盘,4表示移动设备(即虚拟键盘),5表示手柄
  • modifiers : 字符串,空格分隔的修改件列表,如"shift"
  • repeat : 整数,在一行中按了多少次这个键
    因为DOM3级不提倡使用keypress事件,所以只能用这个方式来模拟keyup/keydown事件。

模拟按住Shift和A键的案例:

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

// 以DOM3级方式建立
if(document.implementation.hasFeature('KeyboardEvents', '3.0')){
    myEvent = document.createEvent('KeyboardEvent');
    myEvent.initKeyboardEvent(
        'keydown', true, true, document.defaultView,
        'a', 0, 'Shift', 0
    );
}
btn.dispatchEvent(myEvent);
复制代码

6.1.3 模拟其余事件

如模拟变更事件HTML事件

  • 模拟变更事件

经过createEvent()传入MutationEvents参数建立,返回一个initMutationEvent()方法,这个方法接收参数包括:type/bubbles/cancelable/relatedNode/preValue/newValue/attrName/attrChange,下面模拟一个案例:

var btn = document.getElementById('btn');
var myEvent = document.createEvent('MutationEvents');
myEvent.initMutationEvent(
    'DOMNodeInserted', true, false, someNode, '', '', '', 0
);
btn.dispatchEvent(myEvent);
复制代码
  • 模拟HTML事件

经过createEvent()传入HTMLEvents参数建立,返回一个initEvent()方法,下面模拟一个案例:

var btn = document.getElementById('btn');
var myEvent = document.createEvent('HTMLEvents');
myEvent.initEvent('focus', true, false);
btn.dispatchEvent(myEvent);
复制代码

6.1.4 自定义DOM事件

经过createEvent()传入CustomEvent参数建立,返回一个initCustomEvent()方法,有4个参数:

  • type : 字符串,表示触发的事件类型,如keydown
  • bubble : 布尔值,表示是否冒泡,为了精确模拟键盘事件,一般设置为true
  • cancelable :布尔值,表示是否能够取消,为了精确模拟键盘事件,一般设置为true
  • detail : 对象,任意值,保存在event对象的detail属性中

案例:

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

// my_event在前面定义 2.6 跨浏览器事件处理程序
my_event.addMyEvent(btn, 'myevent', function(event){
    alert('btn detail ', event.detail);
});

my_event.addMyEvent(document, 'myevent', function(event){
    alert('document detail ', event.detail);
});


// 以DOM3级方式穿件
if(document.implementation.hasFeature('CustomEvents', '3.0')){
    myEvent = document.createEvent('CustomEvent');
    myEvent.initCustomEvent(
        'myevent', true, false, 'hello leo',
    );
    btn.dispatchEvent(myEvent);
}
复制代码

6.2 IE中的事件模拟

IE8及以前的版本模拟事件和DOM中模拟思路类似:想建立event对象再指定信息,最后触发。
区别在于,IE中使用document.createEventObject()方法建立event对象,而且不接收参数,返回一个通用event对象,咱们要作的就是给这个event对象添加信息,最后在目标上调用fireEvent()方法,并接受两个参数(事件处理程序的名称和event对象)。
在调用fireEvent()方法会自动添加srcElementtype属性,咱们须要手动添加其余属性,下面模拟一个click事件:

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

myEvent.screenX = 100;
myEvent.screenY = 0;
myEvent.clientX = 100;
myEvent.clientY = 0;
myEvent.ctrlKey = false;
myEvent.altKey = false;
myEvent.shiftKey = false;
myEvent.button = 0;

btn.fireEvent('onclick', event);
复制代码

参考文章

  1. 《JavaScript高级程序设计》第13章 事件

本部份内容到这结束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推荐 github.com/pingan8787/…
JS小册 js.pingan8787.com

欢迎关注个人微信公众号【前端自习课】

相关文章
相关标签/搜索