9

今天看书又看到事件,遂决定小总结一下~

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

本文主要讲事件流事件处理程序。后续会再写一篇文章,总结事件类型


事件流

大家都举同心圆的栗子,我在这里也说说咯~想象画在一张纸上的一组同心圆,如果把手指放在圆心上,那么手指碰触的不是一个圆,而是纸上所有的圆。这和点击HTML页面上的元素原理是一样的,如果页面上有个按钮,你点击按钮的同时,也点击了按钮的容器元素,点击了容器元素的容器元素……点击了整个页面。

事件流描述的是从页面中接收事件的顺序。现存两种事件流:

  • 事件冒泡流——IE
  • 事件捕获流——Netscape

事件冒泡

接收事件的顺序是:事件由最具体的元素接收,逐级向上传播到较为不具体的节点。

如果页面中有个div,点击了div之后,事件的传播顺序如下:
(1) div
(2) body
(3) html
(4) document

事件冒泡的过程如下图:
事件冒泡的过程

所有现代浏览器都支持事件冒泡,而且IE9,Firefox、Chrome、Safari都将事件一直冒泡到window对象。

事件捕获

接收事件的顺序是:事件由最不具体的元素接收,逐级向下传播到最具体的节点。

如果页面中有个div,点击了div之后,事件的传播顺序如下:
(1) document
(2) html
(3) body
(4) div

事件捕获的过程如下图:
事件捕获的过程

IE9,Firefox、Chrome、Safari都支持这种事件流模型,而且是从window对象开始捕获事件的。

DOM事件流

“DOM2级事件”规定事件流包含3个阶段:事件捕获阶段处于目标阶段事件冒泡阶段
如下图:
DOM2级执行顺序

在DOM事件流中,捕获阶段包括1,2,3。冒泡阶段包括4,5,6,7。其中4是处理目标阶段,在事件处理中被看成是冒泡阶段的一部分。

虽然“DOM级事件”规范明确规定捕获阶段不涉及事件目标,但是IE9,Firefox、Chrome、Safari和Opera9.5及以上版本都会在捕获阶段触发事件对象上的事件,这就意味着有两个机会在目标对象上面操作事件

IE9,Firefox、Chrome、Safari、Opera都支持DOM事件流,IE8及更早版本不支持DOM事件流。即IE9,Firefox、Chrome、Safari、Opera及支持冒泡又支持捕获,而IE8及以前版本仅支持冒泡。

需要说明的是,在实际中,大多数都使用冒泡事件,仅在有特殊需要时才使用捕获事件。

事件处理程序

在说事件处理程序之前,先简单介绍下事件对象。什么是事件对象呢?
在触发DOM上的某个事件时,会产生一个对象,这个对象中包含所有与事件有关的信息,如导致事件的元素、事件的类型以及其他与特定事件相关的信息,这个对象就是事件对象。所有的事件处理程序都可以访问事件对象,但是访问的方式不尽相同,一种是将事件对象作为事件处理程序的参数,另一种是事件对象作为window的属性,通过window.event来访问。这里就介绍到这里,先有个概念~

回归正题,clickloadmouseover都是事件的名字,而响应某个事件的函数就叫做事件处理程序,事件处理程序的名字以“on”开头。

那么该怎样为事件指定处理程序呢?主要有两类方法:HTML事件处理程序JavaScript指定事件处理程序

HTML特性指定事件处理程序

某个HTML元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值是能够执行的JavaScript代码

具体指定方式

<input type="button" value="Click" onclick="console.log('hello')" />  <!--直接是一句代码-->



<script> function show () { console.log('hello'); } </script> <input type="button" value="Click" onclick="show()" /> <!--调用show()函数-->

这种方式中,事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。

本质

这样的事件处理程序在执行的时候,会创建一个封装着元素属性值的函数,然后执行这个函数。就是说,

 <input type="button" value="Click" onclick="console.log('hello')" />  

当单击按钮的时候,先创建一个函数function () { console.log('hello'); },然后立即执行该函数。

这个函数有以下3个特点
1) 函数中会有一个局部变量event,即事件对象。
注意:event是局部变量,可以在函数中直接访问,既不用自己定义,也不是从参数列表中读取。这点与后面其他指定事件处理程序的方式有所不同,记得区分。
2) 函数内部,this值等于事件的目标元素。
3)这个动态创建的函数使用with扩展作用域:

function () {
    with(document) {
        with(this) {
          // 元素属性值,如 console.log('hello');
        }
    }
}

从而该函数可以访问document及该元素本身的任何成员。这也是为什么“采用这种方式指定的事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。”,因为作用域中有document

取消事件默认行为的方法

实质上就是让动态创建的函数return false,所以要这样写:

<a href="http://www.baidu.com" onclick="console.log('hello');  return false;">baidu</a>



<script> function show () { console.log('hello'); } </script> <a href="http://www.baidu.com" onclick="show(); return false;">baidu</a>

第二种方式一定要注意,return false是加在onclick属性值里面的,如果放到show()里是不起作用的哦,因为是要“让动态创建的函数return false

3个缺点

  • 时差问题
    用户可能会在HTML元素一出现就在页面上触发相应的事件,但当时的事件处理程序有可能尚不具备执行的条件,从而引发错误。比如上面的show()函数,如果用户单击按钮的时候,function show(){}还没有执行,就会导致错误。
  • 这样扩展事件处理程序的作用域链在不同的浏览器中会导致不同的结果。
  • HTML与JavaScript代码紧密耦合,所以实际中这种方法使用的比较少。

JavaScript指定事件处理程序

JavaScript指定事件处理程序主要有两种方式:DOM0 级事件处理程序DOM2 级事件处理程序。使用JavaScript指定事件处理程序,首先必须取得对一个操作对象的引用。

DOM0 级事件处理程序

每个元素(包括windowdocument)都有自己的事件处理程序属性,这些属性通常全部小写,如onclick,将这种属性赋值为一个函数,就是DOM0 级事件处理程序的指定方式。如下:

var btn = document.getElementById('myBtn'); // 取得对操作对象的引用
btn.onclick = function () { // 为onclick赋值一个函数,指定事件处理程序
    console.log('hello');
}
移除事件处理程序:
btn.onclick = null; // 只需将属性置空
特点:

1)添加事件处理程序的代码只有在运行完之后才会为元素绑定事件处理程序,因此如果该段代码未执行单击按钮是没有反应的。
2)事件处理程序中的this引用当前元素,即绑定了该事件的元素。
3)以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
4)所有的浏览器都支持DOM0 级事件处理程序。

DOM2 级事件处理程序

“DOM2 级事件处理程序”定义了两个方法,用来指定和删除事件处理程序:addEventListener()removeEventListener()。这两个方法都接受3个参数:要处理的事件名作为事件处理程序的函数一个布尔值,这个布尔值参数如果为true表示在捕获阶段调用事件处理程序,为false表示在冒泡阶段调用事件处理程序,一般设为false

添加事件处理程序:
var btn = document.getElementById('myBtn'); // 取得对操作对象的引用
btn.addEventListener('click', function(){ // 注意是 click 哦~
    console.log('hello');
}, false);

function show() {
    console.log('hello');
}

var btn = document.getElementById('myBtn'); // 取得对操作对象的引用
btn.addEventListener('click', show, false);
移除事件处理程序:

移除事件处理程序有一个要求就是:移除时传入的参数必须与添加事件处理程序时使用相同的参数。从而通过addEventListener()添加的匿名函数将无法移除。

移除上面第二种方式绑定的事件处理程序代码:

var btn = document.getElementById('myBtn'); // 取得对操作对象的引用
btn.removeEventListener('click', show, false);

要说明的是:通过addEventListener添加的事件处理程序只能通过removeEventListener()来移除。

特点

1)只有运行完addEventListener()才为元素绑定了事件处理程序。
2)事件处理程序中的this引用当前元素,即绑定了该事件的元素。
3)通过设置第三个参数为truefalse可以设定在捕获阶段还是冒泡阶段调用事件处理程序。
4)可以为同一个元素绑定多个事件处理程序,绑定的事件处理程序会按照它们添加的先后顺序依次触发。这是DOM0 级事件处理程序不支持的哦~ DOM0 级只能添加一个事件处理程序,后添加的会覆盖先添加的。
5)IE9、Firefox、Safari、Chrome和Opera都支持DOM2 级事件处理程序。

IE事件处理程序

上面特地加粗了IE9,那么IE8及以前版本怎么办呢?IE实现了与DOM中类似的两个方法:attachEvent()detachEvent()。这两个方法仅接受两个参数:事件处理程序名称事件处理程序函数。IE8及之前版本仅支持事件冒泡,所以通过attachEvent()添加的事件处理程序会在冒泡阶段调用。

注:IE10及之前版本支持attachEvent()detachEvent()方法,IE11不再支持,一般IE9及以上都使用DOM方法,这两个方法仅对IE8及以下版本使用。

添加事件处理程序:
var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function () { // 注意这里是 onclick 哦~
    console.log('hello');
});

function show() {
    console.log('hello');
}

var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', show);
移除事件处理程序:

使用detachEvent移除事件处理程序的条件与DOM方法相同——必须提供相同的参数,从而添加的匿名函数也无法被移除。

var btn = document.getElementById('myBtn');
btn.detachEvent('onclick', show);
特点:

1)只有运行完attachEvent()才为元素绑定了事件处理程序。
2)事件处理程序会在全局作用域中运行,从而事件处理程序中的this指向全局对象window。这与之前的都不同哦~所以不能认为this始终等于事件目标,当使用attachEvent()的时候,thiswindow,不过其他时候,this是等于事件目标的~
3)可以为同一个元素绑定多个事件处理程序,绑定的事件处理程序会按照它们添加顺序的相反次序被触发,即先添加后执行,后添加的先执行。

是不是觉得IE太奇葩呢,如果没有IE,前端世界会和谐很多吧~还好IE9以上都是按照DOM标准来了,不再我行我素。

事件对象event

这里顺便简单说下事件对象,对比来看,会更清晰~
前面介绍过事件对象,这里不再赘述。

事件处理程序中访问事件对象的方式

  • HTML特性指定事件处理程序
    事件处理程序中会有个局部变量event,这个event就是事件对象。
  • JavaScript指定事件处理程序
    无论指定事件处理程序采用的是DOM0 级还是DOM2 级方法,事件对象都会作为事件处理程序的参数传入到事件处理程序中。
  • 奇葩的IE
    在使用DOM0 级方法添加事件处理程序时,事件对象作为window对象的属性存在,通过window.event访问。
    如果使用attachEvent()添加事件处理程序,事件对象会作为参数传入到事件处理程序中。

取消默认行为和阻止事件冒泡

  • DOM中的事件对象(两个方法)
    preventDefault()方法用来取消事件默认行为,stopPropagation()方法用来阻止事件进一步捕获或冒泡

    // 假设事件对象传入给参数event
    event.preventDefault(); // 取消事件默认行为
    event.stopPropagation(); // 阻止事件进一步捕获或冒泡
    
  • IE8及以下中的事件对象(两个属性)
    returnValue属性用来设置事件的默认行为。默认值为true,当设置为false时,就会取消事件的默认行为。
    cancelBubble属性用来取消事件冒泡。默认值为false,当设置为true时,就会阻止事件冒泡。

event.returnValue = false; // 取消事件的默认行为
event.cancelBubble = true; // 阻止事件冒泡

注:这两个属性仅IE8及以下版本支持,IE9+使用这两个属性会报错。


终于写完了,这篇写得时间好长……这样整理完知识脉络会清晰一点吧~

本文主要参考《js高级程序设计》事件一章。如有不妥之处,还请指正。谢谢!


梦禅
3.2k 声望609 粉丝

2019,重新出发