注册事件/绑定事件
- 注册事件概述
给元素添加事件,称之为注册事件或者绑定事件 注册事件有两种方式:传统方式和方法监听注册方式
传统注册方式
- 利用on开头的事件,例如onclick
- < button onclikc=" alert(‘hello world!’)" ></ button >
- btn.οnclick=function(){}
- 特点:注册事件的唯一性
- 同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数
<body>
<button>传统注册事件</button>
<button>事件监听注册事件</button>
<script>
var btns = document.getElementsByTagName("button");
btns[0].onclick = function () {
console.log("你好");
}
btns[0].onclick = function () {
console.log("我会覆盖掉前面绑定的事件");
}
</script>
</body>
因为传统事件绑定的唯一性,如上代码中的为同一个事件绑定两个处理函数,那么后面的处理函数会把前面的处理函数覆盖 如上图,只打印了一次,这就是传统注册事件的特点,唯一性
方法监听注册方式
- w3c标准 推荐方式
- addEventListstener()它是一个方法
- IE9之前不支持此方法,可以使用attachEvent()代替
- 特点:同一个元素同一个事件可以注册多个监听器
- 按照注册顺序依次执行
语法
eventTarget.addEventListener(type, listener, [, useCapture]);
eventTarget.addEventListener()方法将指定的监听器注册到eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数. 该方法接收三个参数
- type:事件类型字符串,例如click mouseover,注意,此时事件类型不带on
- listener :事件处理函数,事件发生时会调用该监听函数
- useCapture : 可选参数,是一个布尔值,默认是false
<body>
<button>传统注册事件</button>
<button>事件监听注册事件</button>
<script>
var btns = document.getElementsByTagName("button");
btns[0].onclick = function () {
console.log("你好");
}
btns[0].onclick = function () {
console.log("我会覆盖掉前面绑定的事件");
}
btns[1].addEventListener("click", function () {
console.log("事件侦听注册事件");
})
btns[1].addEventListener("click", function () {
console.log("事件侦听注册事件不会覆盖");
})
</script>
</body>
但是ie9及以下不支持addEventListener()方法,需要用到特殊的attachEvent事件监听方式 语法
eventTarget.attachEvent(eventNameWithOn,callback)
eventTarget.attachEvent()方法将指定的监听器注册到eventTarget(目标对象上),当该对象触发指定的事件时,指定的回调函数就会被执行 该方法接收两个参数
- eventNameWithOn: 事件类型字符串,比如onclick,onmouseover,这里要带on
- callback : 事件处理函数,当目标触发事件时回调函数被调用
btns[2].attachEvent("onclick",function(){
console.log("hello world");
})
注册事件兼容性解决方案
function addEventListener(element, eventName, fn) {
if (element.addEventListener) {
element.addEventListener(eventName, fn);
} else if (element.attachEvent) {
element.attachEvent("on" + eventName, fn);
} else {
element["on" + eventName] = fn;
}
}
删除事件/解绑事件
传统删除事件方式
eventTarget.οnclick=null 示例
<body>
<button>传统删除事件</button>
<button>事件监听删除事件</button>
<script>
var btns = document.getElementsByTagName("button");
btns[0].onclick = function () { console.log("传统注册事件") };
btns[0].onclick = null;
</script>
</body>
方法监听注册方式
语法:
eventTarget.removeEventListener(type,listener[, useCapture]);
示例如下
<body>
<button>传统删除事件</button>
<button>事件监听删除事件</button>
<script>
var btns = document.getElementsByTagName("button");
btns[0].onclick = function () { console.log("传统注册事件") };
btns[0].onclick = null;
btns[1].addEventListener("click", fn);
function fn() {
console.log("事件监听注册的事件");
btns[1].removeEventListener("click", fn);
};
</script>
</body>
ie浏览器同样不支持removeEventListener()方法,而是用其特有的datachEvent()方法 语法
eventTarget.datachEvent(eventNameWithOn,callback);
示例
<body>
<button>传统删除事件</button>
<button>事件监听删除事件</button>
<button>ie</button>
<script>
var btns = document.getElementsByTagName("button");
btns[0].onclick = function () { console.log("传统注册事件") };
btns[0].onclick = null;
btns[1].addEventListener("click", fn);
function fn() {
console.log("事件监听注册的事件");
btns[1].removeEventListener("click", fn);
};
btns[2].attachEvent("onclick", ieFn);
function ieFn() {
console.log("ie事件监听");
btns[2].detachEvent("onclick", ieFn);
}
</script>
</body>
删除事件兼容性解决方案
function removeEventListener(element, eventName, fn) {
if (element.removeEventListener) {
element.removeEventListener(eventName, fn);
} else if (element.detachEvent) {
element.detachEvent("on" + eventName, fn);
} else {
element["on" + eventName] = null
}
}
DOM事件流
当在页面上某个元素触发特定事件时, 比如点击, 除了被点击的目标元素, 所有祖先元素都会触发该事件, 一直到 window.
那这样就出现了一个问题, 是先在目标元素上触发事件, 还是先在祖先元素上触发呢? 这就是事件流的概念.
事件流是事件在目标元素和祖先元素间的触发顺序, 在早期,微软和网景实现了相反的事件流, 网景主张捕获方式, 微软主张冒泡方式:
- 捕获 - Capture - 事件由最顶层逐级向下传播, 直至到达目标元素.
- 冒泡 - Bubble - 顾名思义, 类似水中冒泡, 从下往上. 事件由第一个被触发的元素接收, 然后逐级向上传播.
后来 w3c 采用折中的方式, 规定先捕获再冒泡平息了战火. 如此一个事件就被分成了三个阶段(是的, 不光是捕获和冒泡):
- 捕获阶段 - The capture phase - 事件从最顶层元素 window 一直传递到目标元素的父元素.
- 目标阶段 - The target phase - 事件到达目标元素. 如果事件指定不冒泡. 那就会在这里中止.
- 冒泡阶段 - The bubble phase - 事件从目标元素父元素向上逐级传递直到最顶层元素 window. 及捕获阶段的反方向.
也就是如下图所示, 好了,对事件流有了一个大概的理解了,我们再来看看DOM级别。
DOM级别
DOM级别是啥玩意呢?其实就是DOM在不同时期出来的一些规范,就像 JavaScript 里的ES5、ES6、ES7等是一个意思,不断往里面添加一些新的东西。DOM级别有DOM0、DOM1、DOM2、DOM3。其中DOM1和事件没有关系,将不做介绍。
DOM0级事件
为啥要以0为起点呢? 因为这不是 W3C 规范。最开始的时候还没有规范,但各个浏览器就约定俗成的这么做了,所以就成了一个事实上的“规范”。
那么什么是DOM0级处理事件呢?DOM0级事件就是将一个函数赋值给一个事件处理属性,比如:
<button id="btn" type="button">点我</button>
<script>
var btn = document.getElementById('btn');
btn.onclick = function() {
alert('Hello World');
}
</script>
以上代码我们给button定义了一个id,通过JS获取到了这个id的按钮,并将一个函数赋值给了一个事件处理属性onclick,这样的方法便是DOM0级处理事件的体现。我们可以通过给事件处理属性赋值null来解绑事件。
DOM0级事件处理程序的缺点在于一个处理程序无法同时绑定多个处理函数,比如我还想在按钮点击事件上加上另外一个函数。
DOM2级事件
DOM2级事件在DOM0级事件的基础上弥补了无法同时绑定多个处理函数的缺点,允许给一个DOM元素添加多个处理函数(addEventListener)。运行如下代码后点击按钮,我们可以发现两个事件均会被触发:
<body>
<button id="btn" type="button">点我</button>
<script>
var btn = document.getElementById('btn');
function showFn1() {
alert('Hello1');
}
function showFn2() {
alert('Hello2');
}
btn.addEventListener('click', showFn1, false);
btn.addEventListener('click', showFn2, false);
</script>
</body>
DOM2级事件定义了 addEventListener 和 removeEventListener 两个方法,分别用来绑定和解绑事件,方法中包含3个参数,分别是绑定的事件处理属性名称(注意不包含on)、处理函数和是否在捕获阶段执行。
DOM3级事件
DOM3级事件在DOM2级事件的基础上添加了更多的事件类型,全部类型如下:
- UI事件,当用户与页面上的元素交互时触发,如:load、scroll
- 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
- 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
- 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
- 文本事件,当在文档中输入文本时触发,如:textInput
- 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
- 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
- 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
- 同时DOM3级事件也允许使用者自定义一些事件。
详解事件流
事件冒泡
所谓事件冒泡就是事件像泡泡一样从最开始生成的地方一层一层往上冒,比如上图中a标签为事件目标,点击a标签后同时也会触发p、li上的点击事件,一层一层向上直至最外层的 html 或 document。下面是代码示例:
<body>
<p id="parent" style="background-color: red;padding: 20px">
<a id="child" style="background-color: blue;">事件冒泡</a>
</p>
<script>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
child.addEventListener('click', function () {
alert('我是目标 a');
}, false);
parent.addEventListener('click', function () {
alert('事件冒泡至 p');
}, false);
</script>
</body>
上面的代码运行后我们点击 a 标签,首先会弹出【我是目标 a 】的提示,然后又会弹出【事件冒泡至 p】的提示,这便说明了事件自内而外向上冒泡了。
事件捕获
和事件冒泡相反,事件捕获是自上而下执行,我们只需要将 addEventListener 的第三个参数改为true就行。 同样是上面的例子,我们在 p 标签上添加一个捕获阶段的绑定,代码如下:
<body>
<p id="parent" style="background-color: red;padding: 20px">
<a id="child" style="background-color: blue;">事件冒泡</a>
</p>
<script>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
parent.addEventListener('click', function () {
alert('事件捕获至 p');
}, true);
child.addEventListener('click', function () {
alert('我是目标 a');
}, false);
parent.addEventListener('click', function () {
alert('事件冒泡至 p');
}, false);
</script>
</body>
上面的代码运行后我们点击 a 标签,提示顺序为:【事件捕获至 p 】->【我是目标 a 】->【事件冒泡至 p】。可以看到,事件确实是如上图所示,先进行捕获,后冒泡。
终止事件传播
不管在捕获阶段还是冒泡阶段,我们都不希望事件再进行传递了,那么我们怎么进行阻止呢?这时我们可以使用Event 对象的 stopPropagation 方法。而 Event 怎么获取呢?其实每个事件响应函数都有一个参数,就是这个事件对象。
同样是上面的例子,当点击 a 标签,若我们在 p 标签的捕获阶段就阻止事件的传递,这样一来后面的事件均不会执行,代码示例如下:
<body>
<p id="parent" style="background-color: red;padding: 20px">
<a id="child" style="background-color: blue;">事件冒泡</a>
</p>
<script>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
parent.addEventListener('click', function (e) {
alert('事件捕获至 p');
e.stopPropagation();
}, true);
child.addEventListener('click', function () {
alert('我是目标 a');
}, false);
parent.addEventListener('click', function () {
alert('事件冒泡至 p');
}, false);
</script>
</body>
运行点击 a 标签,我们可以看到如注释那样的效果。只要看明白了事件流的那张图,了解了捕获/目标/冒泡这三个阶段,其实就很容易理解了 stopPropagation 阻止事件流所引起的这些现象。这里需要注意的是 stopPropagation 这个函数是阻止事件传播(从单词中可以明白),而不单单只是阻止冒泡
注意
onclick是在哪些阶段执行?
这里我们针对的是非目标阶段,修改上述例子,我们 id=parent 节点,即 p 标签添加一个onclick事件。
<body>
<p id="parent" style="background-color: red;padding: 20px">
<a id="child" style="background-color: blue;">事件冒泡</a>
</p>
<script>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
parent.addEventListener('click', function (e) {
alert('事件捕获至 p');
}, true);
child.addEventListener('click', function () {
alert('我是目标 a');
}, false);
parent.addEventListener('click', function () {
alert('事件冒泡至 p');
}, false);
parent.onclick = function (e) {
alert('p 标签 onclick');
}
</script>
</body>
们可以看到会有这样的一个顺序:
【事件捕获至 p 】->【我是目标 a 】->【事件冒泡至 p】-> 【p 标签 onclick】
从上面的结果我们可以得出结论:onclick是在冒泡阶段执行的!!!
那么另外一个问题来了,既然我们在 p 标签的冒泡阶段绑定了两个 click 函数,一个是通过addEventListener(DOM2级别)添加,一个是直接给onclick属性赋值(DOM0级别),那么这两个函数哪个会先执行呢?直接给出结论,和绑定的顺序有关,也就是说谁写在前面就先执行谁。同样是上面的例子,我们 p 标签绑定的两个函数对调一下先后位置:
<body>
<p id="parent" style="background-color: red;padding: 20px">
<a id="child" style="background-color: blue;">事件冒泡</a>
</p>
<script>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
parent.addEventListener('click', function (e) {
alert('事件捕获至 p');
}, true);
child.addEventListener('click', function () {
alert('我是目标 a');
}, false);
parent.onclick = function (e) {
alert('p 标签 onclick');
}
parent.addEventListener('click', function () {
alert('事件冒泡至 p');
}, false);
</script>
</body>
我们可以看到会变成这样的一个顺序:
调整后:【事件捕获至 p 】->【我是目标 a 】-> 【p 标签 onclick】->【事件冒泡至 p】
调整前:【事件捕获至 p 】->【我是目标 a 】->【事件冒泡至 p】-> 【p 标签 onclick】
可见,正是印证了我们之前的结论。
那么在目标阶段呢?即 a 标签我们既绑定了 onclick 事件,又通过 addEventListener 绑定了click事件,那么这个时候会按什么顺序执行呢?因为这个时候是在目标阶段,那么执行顺序还是和上述一样,先绑定先执行。
总结成一句话:先绑定先执行,onclick在冒泡阶段执行。
事件对象
事件对象用于描述所产生的事件。调用事件处理程序时,JS会把事件对象作为参数传给事件处理程序。事件对象提供了有关事件的详细信息,因而可以在事件处理程序中通过事件对象获取有关事件的相关信息,例如获取事件源的名称、键盘按键的状态、鼠标光标的位置、鼠标按钮的状态等信息。
var div = document.querySelector("div");
div.onclick = function (e) {
console.log(e);
}
- event就是一个事件对象,写到我们侦听函数的小括号里面,当形参来看
- 事件对象只有有了事件才会存在,它是系统给我们自动创建的,不需要我们传递参数
- 事件对象是我们事件的一系列相关数据的集合,跟事件相关的,比如鼠标点击里面就包含了鼠标的相关信息,如鼠标的坐标,如果是键盘事件就包含了键盘事件的信息,如按下了哪个键
- 事件对象可以自己命名,比如event evt e
事件对象兼容性问题
ie678通过window.event来获取事件对象 兼容性写法
e=e||window.event
事件对象常见属性和方法
事件对象属性方法 | 说明 |
---|
e.target | 返回触发事件的对象* | e.srcElement | 返回触发事件的对象 非标准ie6-8使用 | e.type | 返回事件类型,比如click mouseover 不带on | e.cancelBubble | 该属性阻止冒泡,非标准ie678使用 | e.returnValue | 该属性阻止默认事件(默认行为)非标准,ie678使用,比如不让链接跳转 | e.preventDefault() | 该方法阻止默认事件(默认小为)比如不让链接跳转 | e.stopPropagation() | 阻止冒泡 |
示例如下
<body>
<div style="width: 500px; height: 500px; background-color: red;"></div>
<script>
var div = document.querySelector("div");
div.onclick = function (e) {
console.log(e.target);
console.log(e.type);
}
</script>
</body>
e.target和this区别
e.target返回的是触发事件的对象(元素)this返回的是绑定事件的对象(元素) //区别e.target点击了那个元素,就返回那个元素 //this返回绑定了这个事件的元素 示例:
<body>
<ul>
<li>hi</li>
<li>hello</li>
<li>hello world</li>
</ul>
<ul>
<li>你好</li>
<li>我好</li>
<li>大家好</li>
<li>才是真的好</li>
</ul>
<script>
var ul = document.getElementsByTagName("ul")
ul[0].children[0].addEventListener("click", function (e) {
console.log(e.target.innerText);
console.log(this.innerText);
})
ul[1].addEventListener("click", function (e) {
console.log(e.target.innerText);
console.log(this.innerText);
})
</script>
</body>
阻止冒泡事件
冒泡事件:开始由最具体的元素接收,然后逐级向上传播到DOM顶层节点 冒泡本身的特性,会带来的坏处,也会带来好处,我们需要灵活掌握
- 标准写法: 利用事件对象里面的stopPropagation()方法
- cancelBubble=true ie678
阻止事件冒泡的兼容性写法
if (e && e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
事件委托(代理/委派)
事件冒泡的本身特性回来带好处也会带来坏处
如下代码,点击每个li都会弹出对话框,以前需要给每个li注册事件,这是一件非常辛苦的事情,而且访问DOM的次数过多,就会延长整个页面的交互就绪时间
<ul>
<li>你好</li>
<li>hello</li>
<li>摩西摩西</li>
<li>米西米西</li>
<li>死啦死啦的</li>
</ul>
这个时候就需要用到事件委托,事件委托也叫事件代理,在jQuery里面称为事件委派
事件委托的原理
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点
如上面的那个案例,给ul注册点击事件,然后利用事件对象的target来找到当前点击的li,因为点击li,事件会冒泡到ul上,ul有注册事件,就会触发事件监听器
事件委托的作用
我们只操作了一次DOM,提高了程序的性能
示例
<body>
<ul>
<li>你好</li>
<li>hello</li>
<li>摩西摩西</li>
<li>米西米西</li>
<li>死啦死啦的</li>
</ul>
<script>
var ul = document.getElementsByTagName("ul");
ul[0].addEventListener("click", function (e) {
for (var i = 0; i < this.children.length; i++) {
this.children[i].style.backgroundColor = "yellow";
}
e.target.style.backgroundColor = "red";
})
</script>
</body>
效果如下
|