javaScript 浏览器事件总结

javaScript 浏览器事件

1.事件基本概念

事件是指文档或者浏览器中发生的一些特定交互瞬间,比如打开某一个网页,浏览器加载完成后会触发load事件,当鼠标浮于某一个元素上时会触发hover事件,当鼠标点击某一个元素时会触发click事件等等。

事件处理就是当事件被触发吼,浏览器响应这个事件的行为,而这个行为所对应的代码即为事件处理程序

2.事件操作:监听与移除监听

2.1监听事件

浏览器会根据一些事件作出相对应的事件处理,事件处理的前提是需要监听事件,监听事件的方法主要有以下三种:

2.1.1 HTML内联属性

即在HTML元素里直接填写与事件相关的属性,属性值为事件处理程序。示例如下:


onclick对应着click事件,所以当按钮被点击后,便会执行事件处理程序,即控制台输出“You clicked me!”。

不过我们需要指出的是,这种方式将HTML代码与JavaScript代码耦合在一起,不利于代码的维护,所以应该尽量避免使用这样的方式。

2.1.2 DOM属性绑定

通过直接设置某个DOM节点的属性来指定事件和事件处理程序,上代码

const btn = document.getElementById("btn");
btn.onclick = function(e) {
    console.log("You clicked me!")
}

上面示例中,首先获得btn这个对象,通过给这个对象添加onclick属性的方式来监听click事件,这个属性值对应的就是事件处理程序。这段程序也被成为DOM 0级事件处理程序。

2.1.3 事件监听函数

标准的事件监听函数如下:

const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
    console.log("You click me!");
}, false);

上面的示例表示先获得表示节点的btn对象,然后在这个对象上面添加了一个事件监听器,当监听到click事件发生时,则调用回调函数,即在控制台输出“You clicked me!”addEventListener函数包含了第三个参数false,第三个参数的含义在后面的事件触发三个阶段之后再讲解。这段程序也被称作DOM 2级事件处理程序。IE9+、FireFox、Safari、Chrome 和 Opera 都是支持 DOM 2 级事件处理程序的,对于 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;
    }
}
2.2移除事件监听

在为某个元素绑定了一个事件后,如果想解除绑定,则需要removeEventListener方法。

const handler = function() {
    // hanlder logic
}
const btn = document.getElementById("btn");

btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);

需要注意的是,绑定事件的回调函数不能是匿名函数,必须是一个已经被声明的函数,因为解除事件绑定时需要传递这个回调函数的引用。

同样,IE8 及以下版本也不支持上面的方法,而是用 detachEvent 代替。

const handler = function() {
    // hnalder logic
}
const btn = document.getElementById("btn");

btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);

同样,可以写一段具有兼容性的删除事件函数:

function removeEventHandler(obj, eventName, handler) {
    if (document.removeEventListener) {
        obj.removeEventListener(eventName, handler, false);
    } else if (document.detachEvent) {
        obj.detachEvent("on" + eventName, handler);
    } else {
        obj["on" + eventName] = null;
    }
}
DOM事件级别

DOM级别一共可以分为四个级别:DOM0级DOM1级DOM2级DOM3级。而DOM事件分为3个级别:DOM 0级事件处理,DOM2级事件处理和DOM3级事件处理。由于DOM1级中没有事件的相关内容,所以没有DOM1级事件。

DOM0级事件

el.onclick=function(){}

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

当希望为同一个元素、标签绑定多个同类型事件的时候(如给上面的这个btn元素绑定3个点击事件),是不背允许的。DOM0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。

DOM 2级事件

el.addEventListener(event-name,callback,useCapture)

  • event-name: 事件名称,可以是标准的DOM事件。
  • callback:回调函数,当事件触发时,函数会被注入一个参数为当前事件对象event。
  • useCpature:默认是false,代表事件句柄在冒泡执行阶段执行(或者说注册的是冒泡事件),true表示事件句柄在捕获阶段执行(或者说注册的是捕获事件)
var btn = document.getElementById('btn');
btn.addEventListener("click", test, false);
function test(e) {
    e = e || window.event;
    alert((e.target || e.srcElement).innerHTML);
    btn.removeEventListner("click", test);
}

IE9以下的IE浏览器不支持 addEventListener()和removeEventListener(),使用 attachEvent()与detachEvent() 代替,因为IE9以下是不支持事件捕获的,所以也没有第三个参数,第一个事件名称前要加on。可以对此做个兼容性处理。

DOM 3级事件

在DOM 2级事件的基础上添加了更多的事件类型。

  • UI事件,当用户预页面的元素交互时触发,如:load、scroll
  • 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
  • 鼠标事件,当用户通过谁啊哦在页面执行操作时触发如:dblclick、mouseup
  • 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
  • 文本事件,当在文档中输入文本时触发,如:textinput
  • 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
  • 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
  • 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified。
  • 同时DOM 3级事件也允许使用者自定义一些事件。

总结:

  • DOM2级的好处是可以添加多个事件处理程序;DOM0级对每个事件支持持一个事件处理程序;
  • 通过DOM2级添加的匿名函数无法移除,addEventListenerremoveEventListenerhandler必须同名
  • 作用域:DOM 0的handler会在所属元素的作用域内运行,IE的handler会在全局作用域运行,this === window
  • 触发顺序:添加多个事件时,DOM2会按照添加顺序执行,IE会以相反的顺序执行

3.事件触发过程

事件流描述了页面接收事件的顺序。现代浏览器事件流包含三个过程,分别是捕获阶段、目标阶段和冒泡阶段。

下面详细地讲解这三个过程。

3.1捕获阶段

当我们对 DOM元素进行操作时,比如鼠标点击、悬浮等,就会有一个事件传输到这个DOM元素,这个事件从Window开始,依次经过document、html、body,再不断经过过子节点直到到达目标元素,从 Window到达目标元素父节点的过程称为捕获阶段,注意此时还未到达目标节点。

3.2目标阶段

捕获阶段结束时,事件到达了目标节点的父节点,最终到达目标节点,并在目标节点上触发了这个时间,这就是目标阶段

需要注意的是,事件触发的目标节点为最底层的节点。比如下面这个例子:

你猜,目标在这里还是哪里

当我们点击“哪里“的时候,目标节点是,点击这里的时候,目标节点是

,而当我们点击

区域之外,
区域之内,目标节点就是

3.3 冒泡阶段

当事件到达目标节点之后,就会沿着原路返回,这个过程有点类似于水泡从水底浮出水面的过程,所以称这个过程为冒泡阶段

现在再看addEventListener(eventName, handler, useCapture)函数。第三个参数是useCapture,代表是否在捕获阶段进行事件处理,如果是false,则在冒泡阶段进行事件处理,如果是true则在捕获阶段进行处理,默认是false

冒泡事件的流程刚好是事件捕获的逆过程。我们来看个事件冒泡的例子:

// 输出为 inner outter body html dcoument window

4.事件委托

JavaScript中,事件的委托表示给元素的父级或者祖级,甚至页面,由他们来绑定事件,然后利用事件冒泡的基本原理,通过事件目标对象进行检测,然后执行相关操作。

事件委托有两个优点:

  • 减少内存消耗,提高性能

假设有一个列表,列表之中有大量的列表项,我们需要在点击每个列表项的时候响应一个事件。

  • item 1
  • item 2
  • item 3
  • ......
  • item n

如果给每个列表项都一一绑定函数,那么对内存的消耗是非常大的,需要消耗更多性能。借助事件委托,我们只需要给父容器ul绑定方法即可,这样不管点击哪一个后代元素,都会根据冒泡传播的传递机制,把容器的click行为处罚,然后把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而完成不同的事。

  • 动态绑定事件

在很多时候,我们需要通过用户操作动态的增删列表项元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要从新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件委托就会省去很多这样的麻烦。

接下来我们来实现上例中父元素#list下的li元素的事件委托到他的父层元素上:

// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function(e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 判断是否匹配目标元素
    if (target.nodeName.toLocaleLowerCase === 'li') {
        console.log('the content is:', target.innerHTML);
    }
});

5.事件对象

DOM0DOM2的事件处理程序都会自动传入event对象,即触发DOM上的某个事件时,会产生一个事件对象,里面包含着所有和事件有关的信息。IE中的event对象取决于指定的事件处理程序的方法。

IE的handler会在全局作用域运行,this === window所以在IE中会有window.eventevent两种情况。

另外在IE中,事件对象的属性也不一样,对应关系如下:

srcElement => target returnValue => preventDefault() cancelBubble => stopPropagation() IE不支持事件捕获,因而只能取消事件冒泡,但stopPropagtion可以同时取消事件捕获和冒泡。

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

1、event.preventDefault()

如果调用这个方法,默认事件行为将不在触发。什么是默认事件呢?例如表单 - 点击提交按钮跳转页面、a标签默认页面跳转或是锚点定位等。

很多时候我们使用a标签仅仅是想当做一个普通的按钮,点击实现一个功能,不想页面跳转,也不想锚点定位。

// 方法一
链接

也可以通过JS方法来阻止,给其click事件绑定方法,当我们点击A标签的时候,先触发click事件,其次才会执行自己的默认行为

// 方法二
链接


// 方法三
链接

接下来我们看个例子:输入框最多只能输入六个字符,如何实现?

// 例子5


2.event.stopPropagation() & event.stopImmediatePropagation()

event.stopPropagation()方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行(一般我们认为stopPropagation是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件)。上面提到事件冒泡阶段是指事件从目标节点紫霞而上的向window对象传播的阶段。我们在上面的例子中的inner元素click事件上,添加event.stopPropagation()这句话后,就阻止了父事件的执行,最后只打印了'inner'

inner.onclick = function(ev) {
    console.log('inner');
    ev.stopPropagation();
}

stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发。而 stopPropagation 只能实现前者的效果。我们来看个例子:


  


如上所示,使用stopImmediatePropagation后,点击按钮时,不仅body绑定事件不会触发,与此同时按钮的另一个点击事件也不触发。

event.target & event.currentTarget

event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget也就是说,event.currentTarget始终是监听事件这,而event.target是事件的真正发出者

6.捕获与冒泡的顺序问题

当有很多层交互嵌套时,事件捕获和时间冒泡的先后顺序看起来是不好确定的。下面分5种情况讨论给它们的顺序,以及如何规避意外情况的发生。

1、在外层div注册事件,点击内层div来触发事件时,捕获事件总是要比冒泡事件先触发(与代码顺序无关)

假设,有这样的html结构:

然后,我们在外层div上注册两个click事件,分别是捕获事件和冒泡事件,代码如下:

const btn = document.getElementById("test");

// 捕获事件
btn.addEventListener("click", function(e) {
    alert("capture is ok");
}, true);

// 冒泡事件
btn.addEventListener("click", function(e) {
    alert("bubble is ok");
}, false)

点击内层的div,先弹出capture is ok,后弹出bubble is ok。只有当真正触发事件的 DOM元素是内层时,外层DOM元素才有机会模拟捕获事件和冒泡事件。

2、当在触发事件的DOM元素上注册事件时,那个先注册就先执行那个

html 结构同上,js代码如下:

const btnInner = document.getElementById("testInner");

// 冒泡事件
btnInner.addEventListener("click", function(e) {
    alert("bubble is ok");
}, false);

// 捕获事件
btnInner.addEventListener("click", function(e) {
    alert("caapture is ok");
}, true);

在本例中,冒泡事件先注册,所以先执行。所以,点击内层div,先弹出bubble is ok,再弹出caputre is ok

3、当外层div和内层div同时注册了捕获事件时,点击内层的div时,外层div的事件一定会先触发

const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");

btnInner.addEventListener("click", function(e) {
    alert("inner capture is ok");
}, true);

btn.addEventListener("click", function(e) {
    alert("outer capture is ok");
}, true)

虽然外层 div 的事件注册在后面,但会先触发。所以,结果是先弹出 outer capture is ok,再弹出 inner capture is ok

4、同理,当外层div和内层div都同时注册了冒泡事件,点击内层div时,一定是内层div事件先触发。

const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");

btn.addEventLisntener("click", function(e) {
    alert("outer bubble is ok");
}, false);

btnInner.addEventListener("click", function(e) {
    alert("inner bubble is ok");
}, false)

先弹出inner bubble is ok,再弹出outer bubble is ok

5、阻止事件的派发

通常情况下,我们都希望点击某个div时,就触发自己的事件回调。比如,明明点击的是内层div,但是外层div的事件也触发了,这就不是我们想要的了。这时,就需要阻止事件的派发。

事件触发时,会默认传入一个event对象,这个event对象上有一个方法:stopPropagation。MDN上的解释是:阻止捕获和冒泡阶段中,当前事件的进一步传播。所以通过此方法,让外层div接收不到事件,自然也就不会触发了。

btnInner.addEventListener("click", function(e) {
    // 阻止冒泡
    e.stopPropagation();
    alert(”inner bubble is ok“);
}, false);

你可能感兴趣的:(javaScript 浏览器事件总结)