(1) 事件是指在文档或者浏览器中发生的一些特定交互瞬间;是元素天生具备的行为方式(和写不写JS代码没有关系)。当我们去操作元素的时候会触发元素的很多事件。事件是javaScript和DOM之间交互的桥梁。
比如打开某一个网页,浏览器加载完成后会触发 load 事件(onload不是事件),当鼠标悬浮于某一个元素上时会触发 hover 事件(onhover不是事件),当鼠标点击某一个元素时会触发 click 事件(onclick不是事件)等等。浏览器会把一些常用事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定,
(2) 事件的组成部分:事件源 . 事件类型 = 预处理函数
事件源:真正触发事件的那个元素;
事件类型 : 例:onmousemove 、onmouseover等;
预处理函数:function ( ){ };
(3) 事件绑定(DOM 0级事件):给当前元素的某个事件绑定方法,目的是让当前元素某个事件被触发时,做出一些反应
事件处理就是当事件被触发后,浏览器响应这个事件的行为,而这个行为所对应的代码即为事件处理程序(也叫事件处理函数、事件句柄)。
事件处理程序的名字以"on"开头,因此click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。
总之,事件就是一个动作瞬间,如鼠标点击;事件处理程序是一个过程,处理事件发生时的函数的函数。
监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动,即当被监听对象发生上述事件后,监听器某个方法立即被执行。
事件监听器(DOM 2级事件),就是让计算机监视一个事件是否发生,从而执行一些写好的程序,来让HTML元素对事件作出反应;
一般情况下,监听事件是对应于dom元素而言的,同时也需要声明所监听事件种类。 比如可以为一个按钮设置监听事件,点击之后自动调用相应函数处理。 同时可以在用函数处理时使用事件对象(event object)来获得关于触发这个事件的对象的信息。
事件流又称为事件传播,指的是事件的流向,事件的执行顺序。
当事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程就叫 DOM 事件流 。
由于在 HTML 中的标签都是相互嵌套的,可以将元素想象成一个大盒子里装一个小盒子, document 是最外面的大盒子,给所有盒子都绑定点击事件,点击小盒子大盒子的事件也会被触发,这个就叫事件的传播。那么是先执行大盒子的单击事件,还是先执行小盒子的单击事件 ?
现代浏览器(指 IE6-IE8 除外的浏览器,包括 IE9+、FireFox、Safari、Chrome 和 Opera 等)事件流包含两种事件流模型和三个过程。两种方式是事件捕获和事件冒泡;三个过程,分别是捕获阶段、目标阶段和冒泡阶段,下图形象地说明这个过程:
Event.prototype:
- 0 NONE:默认值,不代表任何意思
- 1
CAPTURING_PHASE 捕获阶段
- 2
AT_TARGET 目标阶段(当前事件源)
- 3
BUBBLING _PHASE :冒泡阶段
事件分为DOM 0级事件和Dom 2级事件,DOM2级事件也叫做事件监听。
DOM 0级事件绑定的缺点是如果事件相同,后者的事件会覆盖前者的事件。
DOM2级事件绑定可以为同一个对象的同一个事件绑定多个事件处理程序,且前者事件不会被覆盖,会依次执行。
DOM0级事件和DOM2级事件可以共存,不互相覆盖,但是dom0之间依然会覆盖。
DOM0事件绑定的原理
给当前元素的某一私有属性(onXXX)赋值的过程;(之前属性默认值是null,如果我们赋值了一个函数,就相当于绑定了一个方法)
当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的的函数建立关联,以及建立DOM元素的行为监听,当某一行为被用户触发,浏览器会把赋值的函数执行;
DOM0事件绑定的特点:
只有DOM元素天生拥有这个私有属性(onxxx事件私有属性),我们赋值的方法才叫事件绑定,否则属于设置自定义属性
移除事件绑定的时候,我们只需要赋值为null(eventTarget.事件类型 = null;)
在DOM0事件绑定中,只能给当前元素的某一个事件行为绑定一个方法,绑定多个方法,最后一次的绑定的会替换前面绑定的
即在 HTML 元素里直接填写与事件相关的属性,属性值为事件处理程序。示例如下:
<button onclick="console.log('You clicked me!');"></button>
onclick 对应着 click 事件,所以当按钮被点击后,便会执行事件处理程序,即控制台输出 You clicked me!。
不过我们需要指出的是,这种方式将 HTML 代码与 JavaScript 代码耦合在一起,不利于代码的维护,所以应该尽量避免使用这样的方式。
绑定事件监听函数
通过直接设置某个 DOM 节点的属性来指定事件和事件处理程序,上代码:
const btn = document.getElementById("btn");
btn.onclick = function(e) {
console.log("You clicked me!");
};
上面示例中,首先获得 btn 这个对象,通过给这个对象添加 onclick 属性的方式来监听 click 事件,这个属性值对应的就是事件处理程序。这段程序也被称作 DOM 0 级事件处理程序。
eventTarget.事件类型 = null;
(1)将解绑事件写在函数里的任何位置,其注册事件只会执行一次;
(2)将解绑事件写在函数内的最后,注册事件执行完在销毁将不执行。
(3)将解绑事件写在函数外的注册事件前,解绑事件对其无效;
(4)将解绑事件写在函数外的注册事件后,注册事件将不执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>传统注册解除绑定</title>
</head>
<body>
<button class='btn1'>传统注册</button>
<script>
var btn1 = document.querySelector('.btn1');
var btn2 = document.querySelector('.btn2');
// 传统注册
// btn1.onclick = null;//对应(3)
btn1.onclick = function(e) {
btn1.onclick = null;//对应(1)
console.log('我是传统注册,明天周日了')
// btn1.onclick = null;//对应(2)
}
// btn1.onclick = null;//对应(4)
</script>
</body>
</html>
常见的鼠标事件监听
事件名 | 事件描述 |
---|---|
onclick | 当鼠标单击某个对象 |
ondblclick | 当鼠标双击某个对象 |
onmousedown | 当某个鼠标按键在某个对象上被按下 |
onmouseup | 当某个鼠标按键在某个对象上被松开 |
onmousemove | 当某个鼠标按键在某个对象上被移动 |
mouseover | 鼠标滑过 |
onmouseenter | 当鼠标进入某个对象(相似事件onmouseover) |
onmouseleave | 当鼠标离开某个对象(相似事件onmouseout) |
常见的键盘事件监听
事件名 | 事件描述 |
---|---|
onkeypress | 当某个键盘的键被按下(系统按钮,如箭头键和功能键无法得到识别) |
onkeydown | 当某个键盘的键被按下(系统按钮可以识别,并且会先于onkeypress发生) |
onkeyup | 当某个键盘的键被松开 |
常见的表单事件监听
事件名 | 事件描述 |
---|---|
onchange | 当用户改变某个表单域的内容时,会触发 |
onfocus | 当某元素获得焦点(比如tab键或鼠标点击) |
onblur | 当某元素失去焦点 |
onsubmit | 当表单被提交 |
onreset | 当表单被重置 |
oninput | 当用于正在修改表单域的内容 |
常见的页面事件监听
事件名 | 事件描述 |
---|---|
onload | 当页面或图像被完成加载 |
onunload | 当用户退出页面 |
关于事件监听起初Netscape制定了JavaScript的一套事件驱动机制(即事件捕获)。随即IE也推出了自己的一套事件驱动机制(即事件冒泡)。最后W3C规范了两种事件机制,分为捕获阶段、目标阶段、冒泡阶段,详细介绍请看下文。IE8以前IE一直坚持自己的事件机制(前端人员一直头痛的兼容性问题),IE9以后IE也支持了W3C规范。
DOM2级事件绑定的原理
DOM2级事件绑定使用的addEventListener/attachEvent方法都是在eventTarget这个内置类的原型上定义的,我们调用的时候,首先要通过原型链找到这个方法,然后执行完成事件绑定的效果.
浏览器会给当前元素的某个事件行为开辟一个事件池(事件队列),浏览器有一个统一的事件池,每个元素绑定的行为都放在这里,通过相关标志区分,当我们通过addEventListener/attachEvent进行事件绑定的时候,会把绑定的方法放在事件池中;
当元素的某一行为被触发,浏览器回到对应事件池中,把当前放在事件池的所有方法按序依次执行
DOM2级事件绑定的特点
浏览器会把一些常用事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定,所有DOM0支持的行为,DOM2都可以用,DOM2还支持DOM0没有的事件行为,例如:onDOMContentLoaded(所有的DOM0和IE6-8的DOM2都不支持)//当前浏览器中的DOM结构加载完成,就会触发这个事件。
DOM2中可以给当前元素的某一事件行为绑定多个不同方法(因为绑定的所有方法都放在事件池中);
事件的移除:事件类型、绑定的方法、传播阶段三个完全一致,才可以完成移除(因此在绑定方法时,尽量不要用匿名函数,否则不好移除)
element.addEventListener(event, listener, useCapture)
//第一个参数是事件的类型(例如“ click”或“ mousemove”,与IE不同的是前面不用加on)。
//第二个参数是事件发生时我们要调用的监听函数。
//第三个参数是一个布尔值,指定是否使用捕获事件。此参数是可选的(默认是false true代表捕获,由外向里,很少用; false代表冒泡,由里向外)
//标准的事件监听函数如下:
```javascript
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
console.log("1:You clicked me!");
}, false);
btn.addEventListener("click", () => {
console.log("2:You clicked me!");
}, false);
btn.addEventListener("click", () => {
console.log("3:You clicked me!");
}, false);
//执行的顺序:1 - 2 - 3
上面示例表示先获得表示节点的 btn 对象,然后在这个对象上面添加了一个事件监听器,当监听到 click 事件发生时,则调用回调函数,即在控制台输出 You clicked me!。这段程序也被称作 DOM 2 级事件处理程序。
注意:
- jq点击事件$(“.btn”).click(function(){alert(“1”)})和原⽣的addEventListener的点击事件,都是dom2级事件,不会覆盖,会依次执⾏
removeEventListener()
(1)将解绑事件写在函数里的任何位置,其注册事件只会执行一次;
(2)将解绑事件写在函数外,那么注册的事件将无效。
(3)对于监听注册方式的解绑,需要注意事件类型和监听函数必须是相同的,否则解绑无效。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button class='btn2'>监听注册</button>
<script>
// 监听注册
/*不能解绑事件
btn2.addEventListener('click', function() {
console.log('我是监听注册,明天也是周日')
})
// 不能解绑事件,必须放在函数内部
btn2.removeEventListener('click', function() {
console.log('监听注册解绑');
})
*/
btn2.addEventListener('click', fn);
function fn() {
console.log('监听注册解绑,我们真没关系了');
btn2.removeEventListener('click', fn)
}
</script>
</body>
</html>
补充:在事件中,this总是指向触发事件的对象,但是特殊的是attachEvent()方法里面this总是指向windows
//对象.attachEvent(‘onevent’,‘function’)
//参数1:事件类型,需要在事件类型前加on
//参数2:回调函数,即事件触发时执行的函数
const btn = document.getElementById("btn");
btn.attachEvent("click", () => {
console.log("1:You clicked me!");
});
btn.attachEvent("click", () => {
console.log("2:You clicked me!");
});
btn.attachEvent("click", () => {
console.log("3:You clicked me!");
});
//三个方法执行的顺序是3 - 2 -1;
IE9+、FireFox、Safari、Chrome 和 Opera 都是支持addEventListener()方法的,对于 IE8 及以下版本,则用 attacEvent() 函数绑定事件。 所以我们可以写一段具有兼容性的代码:
function addEventHandler(obj, eventName, handler) {
if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
}
else if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
}
else {
obj["on" + eventName] = handler;
}
}
单击div01 输出为’01’
单击div02 输出为 2、01
单击div03 输出3、2、01
单击div1 输出为1、4
单击div2 输出为 1、2、4
单击div3 输出1、3、2、4
单击div11 输出为11、1、14
单击div12 输出为 11、12、2、1、14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style text="txt/css">
div {border: 1px solid red;padding: 0 20px 20px 20px;margin: 15px;}
</style>
</head>
<body>
<div id="div01" onclick="alert(1)">
div01 onclick 绑定输出1
<div id="div02" onclick="alert(2)">
div02 onclick 绑定输出2 ...
<div id="div03" onclick="alert(3)">
div03 onclick 绑定输出3... (DOM0级事件展示块1)
</div>
</div>
</div>
<hr>
<div id="div1">
div1 click 绑定输出1 4...
<div id="div2">
div2 click 绑定输出2...
<div id="div3">
div3 click 绑定输出3... (DOM2级事件展示块2)
</div>
</div>
</div>
<hr>
<div id="div11" onclick="alert(1)">
div11 click 绑定输出11 14 onclick 绑定输出1
<div id="div12" onclick="alert(2)">
div12 click 绑定输出 11 12... onclick 绑定输出2...(DOM0级事件&DOM2级事件共存展示块3)
</div>
</div>
<script>
var div01 = document.getElementById("div01");
div01.onclick = function(e) {
alert('02');
};
div01.onclick = function(e) {
alert('01');
};
var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
var div3 = document.getElementById("div3");
div1.addEventListener("click", function() {
alert("1");
}, true);
div1.addEventListener("click", function() {
alert("4");
});
div2.addEventListener("click", function() {
alert("2");
});
div3.addEventListener("click", function() {
alert("3");
}, true);
var div11 = document.getElementById("div11");
var div12 = document.getElementById("div12");
div11.addEventListener("click", function() {
alert("11");
}, true);
div11.addEventListener("click", function() {
alert("14");
});
div12.addEventListener("click", function() {
alert("12");
}, true);
</script>
</body>
</html>
事件委托是事件冒泡的一个应用,它是基于事件的冒泡传播机制,将子元素的事件委托给父元素去监听(给父元素添加事件),当子元素触发事件时,事件冒泡到父级;
如果希望指定的子元素才能触发某事件(根据是谁做不同的事情),可以通过事件对象(event)获得事件源(target),然后通过条件判断是不是期望的子元素,如果是的话,执行事件,否则不执行。
获取事件源的方法: var target = e.target || e.srcElement
若一个容器中多个子元素都要在触发同一事件类型时执行同一个行为(事件处理函数)的时候。
例:有100个li,我们去给这100个li绑定click事件。
原始方案:给每一个li元素都单独进行事件绑定。需要操作dom100次,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间。
基于事件委托:将所有的li绑定的事件,绑定到父元素ul上,则与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能。
这样不论是触发后代中哪一个元素的相关事件行为,由于冒泡传播机制,当前容器绑定的方法也都要被触发执行。
// HTML
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
// JavaScript
var list = document.getElementById("list");
list.addEventListener("click", function(e) {
console.log(e.target);
});
若一个容器中的数据是动态绑定渲染的,且要给容器中每一条数据绑定事件行为时。
在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素。
那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。而使用事件委托,
选用事件委托,就可以实现动态点击的处理了,不用在逐一获取绑定了。
原始方案:每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。
基于事件委托:事件是绑定在父元素的,执行到目标元素时,可通过事件冒泡去匹配该事件。
这样不论是触发后代中哪一个元素的相关事件行为,由于冒泡传播机制,当前父元素容器绑定的方法也都要被触发执行,且不影响新增/删除数据的事件绑定。
可以看到,使用事件委托,在动态绑定事件的情况下是可以减少很多重复工作的
// HTML
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
// JavaScript
var list = document.getElementById("list");
var li=document.getElementsByTagName('li')
list.addEventListener("click", function(e) {
console.log(e.target,e.target.nodeName,li.length);
var ele = document.createElement("li");
ele.innerHTML=('Item '+(li.length+1));
list.appendChild(ele);
});
当事件代理同时绑定了li和span,当点击span的时候,li和span都会冒泡,代码如下:
//HTML
<ul>
<li><span>我是嵌套在li中的span里的内容</span></li>
</ul>
// JavaScript
<script>
var li = document.getElementByTagName("li");
var span = document.getElementByTagName("span");
li.addEventListener("click", function(e) {
console.log('li',e.target);
});
span.addEventListener("click", function(e) {
console.log('span',e.target);
});
</script>
阻止事件冒泡的核心代码:
//cancelBubble 适用于IE,而stopPropagation适用于其他浏览器。现代浏览器(IE9 及 以上、Chrome、FF等)均同时支持这两种写法,保险起见的兼容写法:
e.cancelBubble = true;
if (e.stopPropagation){
e.stopPropagation();
}
方法一: span的事件处理程序中阻止冒泡
var li=document.getElementByTagName('li');
var span =document.getElementByTagName('span');
li.addEventListener('click',function(e){
// 兼容性处理方法1
//if (!e) {
// const e = window.event;
var event = e || window.event;// 兼容性处理方法2
console.log('li',event.target);
//阻止冒泡 start
event.cancelBubble = true;//IE
if (event.stopPropagation) {
event.stopPropagation();//其他浏览器
}
//阻止冒泡end
//}
})
方法二: li的事件处理程序中检测target元素
var li = document.getElementByTagName('li');
var span =document.getElementByTagname('span');
li.addEventlistener('click',function(e){
// 兼容性处理
var event = e || window.event;
if(event.target.nodeName.toLocaleLowerCase()=='span){
//阻止冒泡 start
event.cancelBubble = true;//IE
if (event.stopPropagation) {
event.stopPropagation();//其他浏览器
}
//阻止冒泡end
return;
}
console.log('点击li,且li的序号是',[].indexOf.call(lis,event.target));
})
1、事件代理的好处:
2、适合用事件委托的事件:click、mousedown、mouseup、keydown、keyup、keypress。
3、mouseover、mousemove和mouseout虽然也有事件冒泡,但是处理它们的时候要不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。
4、focus、blur这些事件没有事件冒泡机制,所以无法进行委托绑定事件。
默认行为(也称默认事件),就是不用我们注册,它自己就存在的事情。浏览器常见默认行为:
有的时候,我们不希望浏览器执行默认事件:
我们有两个方法来阻止默认事件:
1. e.preventDefault() 在事件处理函数中,非 IE 使用
2. e.returnValue = false 在低版本的ie中可用
3. return false //
只能阻止on+事件类型的事件中起作用,addEventListener(type,function)中不可用
• 我们阻止默认事件的时候也要写一个兼容的写法
addEventListener绑定的事件:
var oA = document.querySelector('a')
oA.addEventListener('click', function (e) {
e = e || window.event
console.log(this.href)
//兼容写法
e.preventDefault ? e.preventDefault() : e.returnValue = false
})
on+事件类型的事件:
window.onload = function(){
var a1 = document.getElementById("a1");
a1.onclick = function(){
return false;
return confirm("你确定要离开当前页面吗?");
}
}