JavaScript 事件

DOM 级别

此文中涉及到DOM0DOM2级事件,故先行解释

文档对象模型(DOM)是一个平台,一个中立于语言的应用程序编程接口(API),允许程序访问并更改文档的内容、结构和样式。

DOM被分为不同的部分(核心、XMLHTML)和级别(DOM Level 1/2/3
DOM分类

  • DOM核心:针对任何结构化文档的标准模型
  • DOM XML:只针对XML文档的标准模型
  • DOM HTML:只针对HTML文档的标准模型

DOM级别是描述DOM对象,方法和行为的规范集合。基于先前级别构建的DOM规范的更高级别。变化发生在两个方面:

  1. 增加一个全新的规格类别(例如,3级添加了“验证”和“加载和保存”规范,这在2级中不存在)
  2. 修改现有规范类别(例如更新“核心”规范)

DOM级别

  • DOM0级别:不是W3C规范。而仅仅是对在Netscape Navigator 3.0Microsoft Internet Explorer 3.0中的等价功能性的一种定义。DOM1级别建立于此功能性之上
  • DOM1级别:专注于HTMLXML文档模型。它含有文档导航和处理功能。于1998年10月1日称为W3C推荐标准
  • DOM2级别:对DOM1级别添加了样式表对象模型,并定义了操作附于文档至上的样式信息的功能性。同时还定义了一个事件模型,并提供了对XML命名空间的支持。作为一项W3C推荐标准,于2000年11月13日发布
  • DOM3级别:规定了内容模型(DTDSchemas)和文档验证。同时规定了文档加载和保存、文档查看、文档格式化和关键事件

注:DOM1级标准中未定义事件相关的内容,故没有所谓的DOM1级别事件模型


事件

JavaScriptHTML的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。

网页中的每个元素都可以产生某些可以触发JavaScript函数的事件。比如说,我们可以在用户点击某按钮时产生一个事件来触发某个函数。事件在HTML中定义

事件举例:

  • 鼠标点击
  • 页面或图像载入
  • 鼠标悬浮于页面的某个热点至上
  • 在表单中选取输入框
  • 确认表单
  • 键盘按键

事件流

事件流描述的是从页面中接受事件的顺序

如图2.1,目标节点是<td>,当点击<td>事件的传播如下所示:
[图片上传失败...(image-5bef-1534760955972)]

事件冒泡

事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
所由的现在浏览器都支持事件冒泡,但在具体是线上还是有一些差别。IE5.5及更在版本中的事件冒泡会跳过<html>元素(从<body>直接跳到document)。IE9FirefoxChromeSafari则将事件一直冒泡到window对象。
即上图中的3阶段 ,事件传播顺序:<td> - <tr> - <tbody> - <table> - <body> - <html> - <document> - <window>

事件捕获

不太具体的节点更早接收事件,而最具体的元素最后接收事件,与事件冒泡相反
即上图中的1阶段 ,事件传播顺序:<window> - <document> - <html> - <body> - <table> - <tbody> - <tr> - <td>

DOM 事件流

DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
IE9OperaFirefoxChromeSafari都支持DOM事件流;IE8及更早版本不支持DOM事件流。
即上图中的1,2,3个阶段


事件处理程序

事件就是用户或浏览器自身执行的某种动作。诸如clickloadmouseover,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以on开头,因此click事件的事件处理程序就是onclickload事件的事件处理程序就是onload。为事件指定处理程序的方式有好几种。

HTML事件处理程序

元素支持的每个元素都可以使用一个相应事件处理程序同名的HTML属性指定。这个属性的值应该是可以执行的JavaScript代码,我们可以为一个button添加click事件处理程序

<input type="button" value="Click Here" onclick="alert('Clicked');">

HTML事件处理程序中可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,刚才的例子可以写成如下形式:

<input type="button" value="Click Here" onclick="showMessage();">

HTML中指定事件处理程序书写很方便,但有两个缺点:

  1. 存在时差问题。因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
    以上面的例子说明,假设showMessage()函数是在按钮下方、页面的最底部定义的。如果用户在页面解析showMessage()函数之前就单击了按钮,就会引发错误。为此,很多HTML事件处理程序都会被封装在一个try-catch块中,以便错误不会浮出水面,如下面的例子所示:
<input type="button" value="Click Here" onclick="try{showMessage();}catch(ex){}">

这样,如果在showMessage()函数有定义之前单机了按钮,用户将不会看到JavaScript错误,因为在浏览器有机会处理错误之前,错误就被捕获了。

  1. HTML代码和JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动这两个地方:HTML代码和JavaScript代码。

DOM0 级事件处理程序

每个元素(包括windowdocument)都有自己的事件处理程序属性,这些属性全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序。
使用DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中进行;换句话说,程序中的this引用当前元素。例如:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert(this.id); // "myBtn"
}

也可以删除通过DOM0 级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设为null即可:

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

将事件处理程序设置为null后,再单击按钮将不会有任何动作发生。
这种做法的缺点:一次只能定义一个事件处理程序,不能同时定义多个事件处理程序,后定义的事件处理程序会覆盖前面定义的事件处理程序。

DOM2 级事件处理程序

DOM2 级事件定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()removeEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示再捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序;不写的情况下默认为false
在按钮上为click事件添加事件处理程序,如下:

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
  alert(this.id);
}, false);

DOM0 级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。如下所示:

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
  alert(this.id);
}, false);
btn.addEventListener("click", function(){
  alert("Hello World");
}, false);

这里为按钮添加了两个事件处理程序。这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的ID,其次会显示“Hello World”消息。
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除。因为匿名函数体虽然方法体一样,但是句柄却不同是,是完全不同的函数,所以应写成:

var btn = document.getElementById("myBtn");
var handler = function(){
  alert(this.id);
};
btn.addEventListener("click", handler, false);
btn.addEventListener("click", handler, false);

IE 事件处理程序

IE 实现了与DOM中类似的两个方法:attachEvent()detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
使用attachEvent()添加的事件可以通过detachEvent()来移除,条件是必须提供相同的参数。与DOM方法一样,这也意味着添加的匿名将不能被移除。不过,只要能够将相同函数的引用传给detachEvent(),就可以移除相应的事件处理程序。
使用attachEvent()为按钮添加一个事件处理程序,如下所示:

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
  alert("Clicked");
});

attachEvent()addEventListener()的区别:

  1. 参数个数不同。attachEvent()的有两个参数,添加的事件只能发生在冒泡阶段;而addEventListener()有三个参数,都最后一个参数决定添加的事件处理程序是在冒泡阶段还是捕获阶段(一般为了兼容浏览器都设置为冒泡阶段)

  2. 第一个参数意义不同。attachEvent()的第一个参数是事件处理程序名称,例如onclick;而addEventListener()方法中第一个参数是事件处理类型,例如click

3.事件处理程序的作用域不同。在使用DOM2级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this等于window。如下:

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
  alert(this === window); // true
});
  1. 为一个事件添加多个事件处理程序时,执行顺序不同。与addEventListener()类似,attachEvent()也可以为一个元素添加多个事件处理程序,不过,这些事件处理程序不是以添加它们的顺序执行的,而是以相反的顺序被触发。当添加的事件处理程序较多时则顺序无规律。

跨浏览器的事件处理程序

就不同浏览器的差异,对事件处理程序封装,此方法接收三个参数:要操作的元素、事件名称、事件处理程序函数。jQuery创始人John Resig做法如下:

function addEvent(node, type, handler){
  if(!node){
    return false;
  }
  if(node.addEventListener){
    node.addEventListener(type, handler, false);
    return true;
  }else if(node.attachEvent){
    node['e' + type + handler] = handler;
    node[type + handler] = function(){
      node['e' + type + handler](window.event);
    };
    node.attachEvent('on' + type, node[type + handler]);
    return true;
  }
  return false;
}

在取消事件处理程序的时候,如下:

function removeEvent(node, type, handler){
  if(!node){
    return false;
  }
  if(node.removeEventListener){
    node.removeEventListener(type, handler, false);
    return true;
  }else if(node.detachEvent){
    node.detachEvent('on' + type, node[type + handler]);
    node[type + handler] = null;
    return true;
  }
  return false;
}

事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有的浏览器都支持event对象,但支持方式不同。

DOM中的事件对象

兼容DOM的浏览器会将一个event对象传入到事件处理程序中。
event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过所有事件都会有下表列出的成员。


在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将使事件处理程序指定给了目标元素,则thiscurrentTargettarget包含相同的值。

阻止事件的默认行为
要阻止特定事件的默认行为,可用使用preventDefault()方法。例如,链接的默认行为就是在被单击时会导航到其href特性指定的URL,若想阻止链接导航这一默认行为,那么提供链接的onclick事件处理程序可以取消它,如下:

var link = document.getElementById("myLink");
link.onclick = function(event){
  event.preventDefault();
}

只有cancelable属性设置为true的事件,才可以使用preventDefault()取消其默认行为

阻止事件传播
stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。例如,直接添加到一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body上面的事件处理程序,如下所示:

var btn = document.getElementById("myBtn");
btn.onclick = function(e){
  alert('Clicked');
  e.stopPropagation()
};
document.body.onclick = function(e){
  alert('Body clicked');
};

调用stopPropagation()后事件不会传到document.body,因此不会触发注册在document.body上的onclick事件处理程序。

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

IE 中的事件对象

IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。直接为DOM元素添加事件处理程序时,event对象作为window对象的一个属性存在,如下所示:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  var e = window.event;
  alert(e.type); // "click"
};

可是,如果事件处理程序是使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中,如下所示:

var btn = document.getElementById("myBtn");
btn.attachEvent('onclick', function(event){
  alert(event.type); // "click"
});

像这样使用attachEvent()的情况下,也可以提供window对象来访问event对象,就像使用DOM0级方法一样。不过为方便起见,同一个对象也会作为参数传递。
如果是通过HTML特性指定的事件处理程序,那么还可以通过一个名叫event的变量来访问event对象(与DOM中的 事件模型相同),如下所示:

<input type="button" value="Click Me" onclick="alert(event.type)">

IEevent对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有对应的或者相关的DOM属性和方法。与DOMevent对象一样,这些属性和方法也会因为事件类型的不同而不同,但所有事件对象都会包含下表所列的属性和方法。

注:使用returnValue达到了阻止默认行为的目的,cancelBubble设为true可阻止事件冒泡。

跨浏览器的事件对象

虽然DOMIEevent对象不同,但基于它们的相似性,我们还是可以写出跨浏览器的事件对象方案

function getEvent(e){
  return e || window.event;
}

function getTarget(e){
  return e.target || e.srcElement;
}

function preventDefault(e){
  if(e.preventDefault){
    e.preventDefault();
  }else {
    e.returnValue = false;
  }
}

function stopPropagation(e){
  if(e.stopPropagation){
    e.stopPropagation();
  }else {
    e.cancelBubble = true;
  }
}

事件冒泡的应用

事件代理,又称为事件委托。
例如:要求当点击每一个元素li时控制台展示该元素的文本内容。不考虑兼容,如下所示

<ul class="ct">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
    var ct = document.querySelector('.ct');
    ct.addEventListener('click', function(e){
        var target = e.target;
        if(target.tagName.toLowerCase() === 'li'){
            console.log(target.innerText);
        }
    })
</script>

事件类型

Web浏览器中可能发生的事件有很多类型,DOM3级事件规定了以下几类事件:

  • UIUser Interface,用户界面)事件,当用户与页面上的元素交互时触发
  • 焦点事件,当元素获得或失去焦点时触发
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发
  • 文本事件,当在文档中输入文本时触发
  • 键盘事件,当用户通过键盘在页面上执行操作时触发
  • 合成事件,当为IMEInput Method Editor,输入法编辑器)输入字符时触发
  • 变动(mutation)事件,当底层DOM结构发生变化时触发
    此处不想述,具体的可以查看JavaScript程序设计

参考:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,464评论 1 11
  • JavaScript 与 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬...
    threetowns阅读 337评论 0 0
  • 事件处理程序在应用中是必不可少的,虽然现在很多框架都有自己实现事件处理方法,但是熟知原生才能让我们应对各种各样的需...
    俗三疯阅读 279评论 0 1
  • 事件流 事件流描述的是从页面中接受事件的顺序。但是IE和Netscape开发团队提出了差不多相反的事件流的概念。I...
    losspm阅读 259评论 0 0
  • 第13章 事件 1. 事件流 事件流描述的是从页面中接收事件的顺序。 (1) 事件冒泡 IE 的事件流叫做事件冒泡...
    yinxmm阅读 930评论 0 17