事件三要素:
事件流描述了页面接收事件的顺序
DOCTYPE html>
<html lang="en">
<head>
<title>Documenttitle>
head>
<body>
<div id="myDiv">Click Mediv>
body>
html>
IE 事件流称为事件冒泡,因为事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)
点击上面的civ后,click事件发生的顺序:
最不具体的节点最先收到事件,而最具体的节点最后收到事件。事件捕获实际上是为了在事件到达最终目的前拦截事件。
点击上面的civ后,click事件发生的顺序:
DOM2 Event 规定事件流分为3个阶段:事件捕获、到达目标、事件冒泡
事件处理程序的名字以on开头
特定元素支持的每个事件都可以使用事件处理程序得到名字以HTML属性的形式来指定。
注意: 因为属性的值是JS代码,所以不能在未转义的情况下使用HTML语句,如符号和好(&)、双引号、大小于号。为避免使用HTML实体,可以使用单引号代替双引号
<script>
function showMessage() {
console.log("Hello World!");
}
script>
<input type="button" value="Click me" onclick="showMessage()">
以调用函数指定的事件处理函数首先会创建一个函数来封装属性的值。该函数有个特殊的局部变量event,其中保存的是event对象。动态创建的包装函数其作用域链被扩展了。document和元素自身的成员都可以被当成局部变量来访问。通过with实现,意味着事件处理程序可以更方便的访问自己的属性
function() {
with(document) {
with(this) {
// 属性值
}
}
}
时机问题
有可能HTML元素已经显示在页面上,用户已经与其交互,而事件处理程序得代码还无法执行
对事件处理程序作用域链的扩展在不同浏览器中可能导致不同的结果
HTML 和 JS 强耦合
JS中指定事件处理程序得传统方法是:把一个函数赋值给(DOM元素)一个事件处理程序属性。 因为该方法简单,现在所有浏览器仍然支持此方法。
要使用JS指定事件处理程序,必须先获得操作对象得引用。然后将事件处理程序属性赋值为一个函数
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log(this.id);
}
事件处理程序会在元素的作用域里运行,即this等于元素。在事件处理程序里通过this可以访问元素的任何属性和方法。以这种方法添加事件处理程序是注册在事件流的冒泡阶段
通过将事件处理程序属性设置为null,可以移除通过DOM0方式添加的事件处理程序。
DOM2 为事件处理程序的赋值和移除添加了两个方法:addEventListener()和 removeEventListener()。接收3个参数:事件名、事件处理函数和一个布尔值。true表示在捕获阶段调用事件处理程序。false表示在冒泡阶段调用事件处理程序。
let btn = document.getElementById("myBtn");
btn.addEventListener("click",() => {
console.log(this.id);
},false);
也可以把方法抽象出去
let btn = document.getElementById("myBtn");
let handler = function() {
console.log(this.id);
}
btn.addEventListener("click",handler,false);
注意:
ie9 以前支持
注意:
function addEventListener(element,eventName,fn) {
if(element.addEventListener) {
element.addEventListener(eventName,fn); //第三个参数默认false
} else if(element.attachEvent) {
element.attachEvent('on'+eventName,fn);
} else {
//相当于element.onclick = fn;
element['on'+ eventName] = fn;
}
}
IE 实现了和DOM类似的方法,即attachEvent() 和 detachEvent()。这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数,使用attachEvent()添加的事件会添加到冒泡阶段
let btn = document.getElementById("myBtn");
btn.attachEvent("onclick",() => {
console.log("clicked"));
});
注意:
为确保事件处理程序具有最大的兼容性,需要让代码在冒泡阶段运行
为实现跨浏览器事件处理程序,需要创建 addHandler()。该方法根据需要分别使用DOM0、DOM2或IE方法添加事件处理程序。该方法会在**EventUtil()**对象上添加一个方法,以实现跨浏览器事件处理。添加的addHandler()接收3个参数:目标元素、事件名和事件处理程序
let btn = document.getElementById("myBtn");
let handler = function() {
console.log("clicked");
}
EventUtil.addHandler(btn,"click",handler);
addHandler() 和 removeHandler() 并没有解决所有跨浏览器一致性问题,比如IE的作用域问题、多个事件处理程序执行顺序问题
在DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中,这个对象包括一些基本信息:如导致时间的元素,发生的事件类型以及可能与特定事件相关的任何其他数据
在DOM合规的浏览器中,event对象是传给事件处理程序的唯一参数。不管以哪种方法(DOM0 和 DOM2)指定事件处理程序,都会传入这个event对象
事件包含的公共属性和方法:
在事件处理程序内部,this 对象始终等于 currentTarget 的值,而target只包含事件的实际目标。如果事件处理程序直接添加在了意图的目标,则this、currentTarget 和 target的值是一样的。
若事件处理程序是添加到按钮的父节点上,那他们的值就不一样
let btn = document.getElementById("myBtn");
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
}
区别:
应用场景:处理程序处理多个事件
用于阻止特定事件的默认动作。比如链接的默认行为
用于立即阻止事件流在DOM结构中传播,取消后续事件的捕获或冒泡。
用于确认事件流当前所处的阶段。
与DOM 事件不同,IE事件可以基于事件处理程序被指定的方式以不同的方法来访问。如果事件处理程序是使用DOM0方式指定的,则event对象只是window对象的一个属性。若事件处理程序是使用attachEvent()指定的,则event对象会作为唯一的参数传给处理函数,event对象仍然是window对象的属性。
IE事件对象包含的公共属性和方法:
事件处理程序的作用域取决于指定它的方式,因此this值并不总等于事件目标。因此,更好的是用事件对象的srcElement属性代替this。
等价于DOM的preventDefault(),用于取消给定事件默认的行为。returnValue = false;即设置阻止默认动作
与DOM的stopPropogation()一样,都可以阻止事件冒泡,stopPropagation()即可取消捕获也取消冒泡
DOM3 Event定义如下事件类型:
事件有:
)上当所有窗格(
)都加载完成后触发,在
元素上当图片加载完成后触发,在
元素上当相应对象加载完成后触发。
元素上当相应对象无法加载时触发。
元素上当相应对象加载完成前被用户提前终止下载时触发最常用的事件。load事件会在整个页面(包括所有外部资源)加载完后触发。有两种方式指定load事件:
window.addEventListener("load",(event)=>{
...
})
元素添加onload属性DOCTYPE html>
<html lang="en">
<head>
<title>Documenttitle>
head>
<body onload="console.log('hhh')">
body>
html>
给元素指定一个在加载完成后执行的事件处理程序:
window.addEventListener("load",() => {
let image = document.createElement("img");
image.addEventListener("load", (event) => {
console.log(event.target.src);
});
document.body.appendChild(image);
image.src = "smile.gif";
})
注意:下载图片并不一定要把元素添加到文档,只要给他设置src属性就会立即开始下载
元素在 JS 文件加载完成后触发load事件,从而动态检测。window.addEventListener("load",() => {
let image = document.createElement("script");
image.addEventListener("load", (event) => {
console.log("loaded!");
});
script.src = "example.js";
document.body.appendChild(script);
})
注意:与图片不同,要下载JS文件必须同时指定src属性并把元素添加到文档中
触发load事件
unload 与 load 事件相对,unload事件会在文档写在完成后触发。unload事件一般从一个页面导航到另一个页面触发,最常用于清理引用,以免内存泄漏。
在元素获得或失去焦点时触发。这些事件可以与document.hasFocus() 和 document.activeElement 一起为开发者提供用户在页面中导航的信息。 有6种事件:
focus:元素获得焦点时触发。不冒泡,所有浏览器支持
blur:元素失去焦点时触发。不冒泡,所有浏览器支持
DOMFocusIn:元素获得焦点时触发。冒泡。DOM3废弃
DOMFocusOut:元素失去焦点时触发。冒泡,DOM3废弃
focusin:元素获得焦点时触发。冒泡
focusout:元素失去焦点时触发。冒泡
当一个元素从页面中的一个元素移动到另一个元素上时,会依次发生如下事件:
9种鼠标事件:
mousedown,mouseup,click和dblclick的触发顺序:
滚轮事件:
只有mousewheel,反映鼠标滚轮或带滚轮的类似设备上滚轮的交互
鼠标事件都是在浏览器视口中的某个位置发生的,这些信息被保存在event对象的clientX和clientY属性,表示事件发生时鼠标光标在视口中的坐标
事件发生时鼠标光标在页面上的坐标,pageX,pageY,在页面没有滚动时,pageX,pageY 和 clientX、clientY相同
screenX 和 screenY
修饰键:Shift、Ctrl、Alt、Meta 也经常用于修改鼠标事件的行为。DOM中修饰键对应的状态为:shiftKey、ctrlKey、altKey、metaKey,修饰键在被按下时其对应的状态为true
对 mouseout 和 mouseover 而言,还存在与事件相关的其他元素。对mouseover来说,其目标是获得管的光标的元素,相关元素是失去光标的元素。对于mouseout来说,主要目标是失去光标的元素,相关元素是获得光标的元素。
对 mousedown 和 mouseup 事件,event 对象上会有一个button属性,表示按下或释放的是哪个按键。DOM 为button属性定义了3个值:0表示鼠标主键、1表示鼠标中键、2表示鼠标右键
DOM2 在event 对象上提供了detail属性,detail包含一个数值,表示在给定位置上发生了多少次单击。从1开始,若鼠标在mouseout 和 mouseup之间移动了,detail被重置为0
触摸屏设备开发时需要注意:
document.addEventListener("selectstart", (e) => {
e.preventDefault();
})
对于keydown 和 keyup,event对象的keyCode属性会保存一个键码,对应键盘上待定的一个键。
keypress事件时才会被设置值,为按键字符对应的ASCII编码
DOM3 Events并未规定charCode属性,而是定义了key和char两个新属性。其中key属性用于代替keyCode。
DOM3 Events也支持location属性,是一个数值,表示在哪儿按的键。
key、char、keyidentifier和location都缺乏浏览器支持,不建议使用
DOM3新增的,textInput在字符被输入到可编辑区域时触发。与keypress的区别:
textInput关注字符,所以event对象上提供了data属性,包含要插入的字符
event对象上名为inputMethod的属性,表示向控件输入文本的手段:
DOM3 新增,用于处理通常使用IME(输入法编辑器)输入时的复杂输入序列。IME可以让用户输入物理键盘上没有的字符。IME通常需要同时按下多个键才能输入一个字符。合成事件用于检测和控制这种输入,有属性data:
用于表示何时该显示上下文菜单(点击右键显示的菜单),从而允许开发者取消默认的上下文菜单并提供自定义菜单。
contentmenu 事件冒泡, 因此只要给document指定一个事件处理程序就可以处理页面上的所有同类事件。通常自定义的上下文菜单都是通过contentmenu事件处理程序触发显示,并通过onclick事件处理程序触发隐藏的。
实现上下文菜单的基础代码:
DOCTYPE html>
<html lang="en">
<head>
<title>Documenttitle>
head>
<body>
<div id="myDiv">hhhdiv>
<ul id="myMenu" style="position: absolute;visibility:hidden;background-color:silver">
<li><a href="#">1a>li>
<li><a href="#">2a>li>
<li><a href="#">3a>li>
ul>
body>
html>
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";
})
})
beforeunload事件会在window上触发,用于给开发者提供阻止页面被卸载的机会。该事件在页面即将从浏览器中卸载时触发。若页面要继续使用,则可以不被卸载。该事件回向用户提示一个确认框,其中的信息表明浏览器即将卸载页面,请用户确认是否希望关闭页面。
为显示确认框,需要将event.returnValue设置为要在确认框中显示的字符串,并将其作为函数值返回
window.addEventListener("beforeunload",(event) => {
let message = "hhhhhhhhhhh";
event.returnValue = message;
return message;
})
DOMContentLoaded事件会在DOM树构建完成后立即触发,而不用等图片、JS、CSS等其他资源文件加载完成。相对于load事件,DOMContentLoaded 可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能更快的与页面交互,DOMContentLoaded事件始终在load之前触发
window 添加事件处理程序(实际的事件目标时document,但会冒泡到window)
用于提供文档或元素加载状态的信息。支持readystatechange事件的每个对象都有一个readyState属性, 该属性具有一个可以列出的可能的字符串值:
Firefox 和 Opera 开发了往返缓存,旨在使用浏览器的前进和后退按钮时加快页面之间的切换。这个缓存不仅存储页面,也存储DOM 和 JS状态,实际把整个页面都保存在内存中。若页面在缓存中,那导航到该页面时不会触发load事件,但提供了一些事件暴露往返缓存的行为。
在页面显示时触发,不论是否来自往返缓存。在新加载的页面上,pageshow会在load事件之后触发;在来自往返缓存的页面上,pageshow会在页面状态完全恢复后触发。
注意:虽然该事件的目标树document,但事件处理程序必须添加到window上
在页面从浏览器中卸载后,在unload事件之前触发,事件处理程序也必须添加到window上
pageshow 和 pagehide 都有属性 persisted,页面存储了往返缓存就是true,否则为false。对pageshow 来说,persisted 为true 表示页面时长往返缓存中加载的;对pagehide 来说,persisted 为 true 表示页面在卸载之后会被保存到往返缓存中。因此,第一次触发 pageshow 时 persisted 始终为false,第一次触发 pagehide 时 persisted 始终为 true。
用于在URL散列值(URL最后#后面的部分)发生变化时通知开发者。因为开发者经常在Ajax应用中使用URL散列值存储状态信息或路由导航信息
haschange 事件处理程序必须添加给window,event对象有两个属性:oldURL 和 newURL。用于分别保存变化前后的URL,且包含散列值的完整URL。
用于确认用户使用设备的方式
苹果公式在Safari浏览器上创造了orientationchange 事件,以便开发者判断用户的设备是处于垂直模式还是水平模式。在window上暴露window.orientation属性,有3种值:
若可以获取设备的加速计信息,且数据发生了变化,这个事件就会在window上触发。注意:deviceorientation事件只反应设备在空间中的朝向,而不涉及移动相关的信息
当deviceorientation触发时,event对象会包含各个轴相对于设备静置时坐标值的变化,主要是:
用于提示设备实际上在移动,而不仅仅是改变了朝向。
当devicemotion 事件触发时,event对象中包含如下额外的属性:
若无法提供acceleration、accelerationIncludingGravity 和 rotationRate信息,则属性值为null。为此,在使用这些属性前必须先要检测他们的值是否为null
当手指放到屏幕上、在屏幕上滑动或从屏幕上拿开时,触摸事件触发,有以下几种:
这些事件都会冒泡,也都可以被取消。每个触摸事件的event对象都提供了鼠标事件的公共属,除此之外还有:
每个Touch对象的属性:
当手指点触屏幕上的元素时,依次会发生如下事件(包括鼠标事件):
当两个手指触碰屏幕且相对距离或旋转角度变化时触发,有3种:
只有在两个手指同时接触事件接收者时,这些事件才会触发。因为这些事件会冒泡,所以也可以把事件处理程序放到文档级别,从而处理所有手势事件
每个手势事件的event对象都包含所有的鼠标事件属性。新增的两个event对象属性是:rotation 和 scale。rotation属性表示手指变化旋转的度数,负值表示逆时针旋转,正值表示顺时针旋转(从0开始)。scale属性表示两指之间距离变化的程度。开始为1,岁距离增大或缩小相应的增大或缩小
“过多事件处理程序”的解决方案:事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一类的事件。 如:click事件冒泡到document。以为着可以为整个页面指定一个onclick事件处理程序,而不用为每个可点击元素分别指定事件处理程序。
<ul id="myLinks">
<li id="go"><a href="#">1a>li>
<li id="hi"><a href="#">2a>li>
<li id="some"><a href="#">3a>li>
ul>
let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
let target = event.target;
switch(target.id) {
case "go":
document.title = 'hhhhhhh';
break;
case "hi":
document.title = 'xixixii';
break;
case "some":
location.href = "http://www.baidu.com";
break;
}
})
事件委托的优点:
很多Web应用性能不佳都是由于无用的事件处理程序常驻内存导致的。导致该问题的原因有:
删除带有事件处理程序的元素。 如:通过真正的DOM方法 removeChild()或replaceChild()删除节点。最常见的是innerHTML整体替换页面的某一部分。这时候被innerHTML删除的元素上如果有事件处理程序,就不会被垃圾收集程序正常清理
<div id="myDiv">
<input type="button" value="click me" id="myBtn">
div>
<script>
let btn = document.getElementById("myBtn");
btn.onclick = function() {
// 操作
btn.onclick = null; //删除事件处理程序,一定要添加
document.getElementById("myBtn").innerHTML = "hhhhhh...";
}
script>
单击按钮,会将自己删除并替换为一条信息,以阻止双击发生
页面卸载, 最好在onunloaded事件处理程序种趁页面尚未卸载时先删除所有的事件处理程序。
可以通过JS在任何时候触发任意事件,在测试时特别有用
任何时候,都可以用 document.createEvent()创建一个event对象。该方法接收一个参数:一个表示要创建事件类型的字符串:
事件模拟的最后一步是触发事件,使用 dispatchEvent(),接收一个参数,即表示要触发事件的event对象。 调用dispatchEvent()之后,事件就“转正”了,接着便冒泡并触发事件处理程序执行
调用createEvent()并传入“MouseEvents” 参数,返回一个event对象,该对象有**initMouseEvent()**方法,用于为新对象指定鼠标的特定信息。initMouseEvent()有15个参数:
使用默认值模拟单击事件的例子:
let btn = document.getElementById("myBtn");
let event = document.createEvent("MouseEvents");
//初始化
event.initMouseEvent("click",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null);
//触发事件
btn.dispatchEvent(event);
调用createEvent()并传入“KeyboardEvents” 参数,返回一个event对象,该对象有**initKeyboardEvent()**方法。initKeyboardEvent()参数:
注意:DOM3 废除了keypress事件,因此只能用上述方法模拟keydown和keyup事件
Firefox 允许给createEvent()传入"KeyEvents"来创建,返回的对象包含initKeyEvent(),参数:
模拟HTML事件需要调用createEvent()并传入“HTMLEvents” 参数,返回一个event对象,该对象有initEvent() 方法
自定义事件不会触发原生的DOM事件。创建自定义事件,需要调用 createEvent(“CustomEvent”)。返回对象包括initCustomEvent()方法,接收4个参数:
使用 document 的 createEventObject()来创建对象,与DOM不同,该方法不接受参数,返回一个通用的 event 对象,然后,手工给返回的对象指定希望该对象具备的所有属性。(没有初始化方法)。 最后在事件目标上调用 fireEvent() 方法,该方法接收两个参数:事件处理程序的名字和event对象。调用fireEvent()时, srcElement 和type属性会自动指派到event对象(其他属性必须手工指定)
模拟click事件:
var btn = document.getElementById("myBtn");
var event = document.createEventObject();
event.screenX =100;
event.screenY =0;
event.clientX =0;
event.clientY =0;
event.ctrlkey =false;
event.altkey =false;
event.shiftkey =false;
event.button =0;
btn.fireEvent("onclick",event);