JS事件基础(非常详细)

JavaScript 与 HTML 之间交互就是通过事件实现的,事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。事件在浏览器中是以对象的形式存在的,即 event,触发一个事件,就会产生一个事件对象 event,该对象包含着所有与事件有关的信息,包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。

事件模型

在浏览器发展历史中,出现 4 种事件处理模型。

  • 基本事件模型:也称为 DOM0 事件模型,是浏览器初期出现的一种比较简单的事件模型,主要通过 HTML 事件属性,为指定标签绑定事件处理函数。由于这种模型应用比较广泛,获得了所有浏览器的支持,目前依然比较流行。但是这种模型对于 HTML 文档标签依赖严重,不利于 JavaScript 独立开发。
  • DOM 事件模型:由 W3C 制订,是目前标准的事件处理模型。所有符合标准的浏览器都支持该模型,IE 怪异模式不支持。DOM 事件模型包括 DOM2 事件模块和 DOM3 事件模块,DOM3 事件模块为 DOM 2 事件模块的升级版,略有完善,主要是增加了一些事情类型,以适应移动设备的开大需要,但大部分规范和用法保持一致。
  • IE 事件模型:IE 4.0 及其以上版本浏览器支持,与 DOM 事件模型相似,但用法不同。
  • Netscape 事件模型:由 NetSCAPE 4 浏览器实现,在 Netscape 6 中停止支持。

事件流

事件流就是多个节点对象对同一种事件进行响应的先后顺序,主要包括以下 3 种类型。

冒泡型

事件从特定的目标向最不特定的目标(document 对象)触发,也就是事件从下向上进行响应,这个传递过程被形象的称为“冒泡”。

【示例1】在下面示例中,文档包含 5 层嵌套的 div 元素,为它们定义相同的 click 事件,同时为每层 <div> 标签定义不同的类名。设计当单击 <div> 标签时当前对象边框显示为红色虚线效果,同时抓取当前标签的类名,以此标识每个标签的响应顺序。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
div {/* 定义div元素样式 */
    margin: 20px;   /* 边界距离 */
    border: solid 1px blue;  /* 蓝色边框线 */
    font-size: 18px;   /* 字体大小 */
}
</style>
<script>
function bubble(){
    var div = document.getElementsByTagName('div');
   var show = document.getElementById("show");
  for (var i = 0; i < div.length; ++i){   //遍历div元素
      div[i].onclick = (function(i){   //为每个div元素注册鼠标单击事件处理函数
    return function(){   //返回闭包函数
        div[i].style.border = '1px dashed red'; //定义当前元素的边框线为红色虚线
        show.innerHTML += div[i].className + " > "; //标识每个div元素的响应顺序
    }
    })(i);
    }
}
window.onload = bubble;
</script>
</head>
<body>
<div class="div-1">div-1
    <div class="div-2">div-2
        <div class="div-3">div-3
        <div class="div-4">div-4
          <div class="div-5">div-5</div>
      </div>
        </div>
    </div>
</div>
<p id="show"></p>
</body>
</html>

在浏览器中预览,如果单击最内层的 <div> 标签,则 click 事件按照从里到外的顺序逐层响应,从结构上看就是从下向上触发,在 <p> 标签中显示事件响应的事件。演示效果如下图所示。

JS事件基础(非常详细)
 
JS事件基础(非常详细)

捕获型

事件从最不特定的目标(document 对象)开始触发,然后到最特定的目标,也就是事件从上向下进行响应。

【示例2】针对示例 1,修改 JavaScript 脚本,使用 addEventListener() 方法为 5 个 div 元素注册 click 事件,在注册事件时定义响应类型为捕获型事件,即设置第 3 个参数值为 true。

function bubble(){
    var div = document.getElementsByTagName('div');
    var show = document.getElementById("show");
    for (var i = 0; i < div.length; ++i){   //遍历div元素
      div[i].onclick = (function(i){   //为每个div元素注册鼠标单击事件处理函数
        return function(){   //返回闭包函数
          div[i].style.border = '1px dashed red'; //定义当前元素的边框线为红色虚线
          show.innerHTML += div[i].className + " > "; //标识每个div元素的响应顺序
       }
        })(i);
    }
}
window.onload = bubble;

在浏览器中预览,如果单击最里层的 <div> 标签,则 click 事件将按照从外到里的顺序逐层响应,在 <p> 标签中显示 5 个 <div> 标签的响应顺序。演示效果如下图所示。

JS事件基础(非常详细)

混合型

W3C 的 DOM 事件模型支持捕获型和冒泡型两种事件流,其中捕获型事件流先发生,然后才发生冒泡型事件流。两种事件流会触及 DOM 中的所有层级对象,从 document 对象开始,最后返回 document 对象结束。因此,可以把事件传播的整个过程分为 3 个阶段。

  • 捕获阶段:事件从 document 对象沿着文档树向下传播到目标节点,如果目标节点任何一个上级节点注册了相同的事件,那么事件在传播的过程中就会首先在最接近顶部上级节点执行,依次向下传播。
  • 目标阶段:注册在目标节点上的事件被执行。
  • 冒泡阶段:事件从目标节点向上触发,如果上级节点注册了相同的事件,将会逐级响应,依次向上传播。

绑定事件

在基本事件模型中,JavaScript 支持以下两种绑定方式。

静态绑定

把 JavaScript 脚本作为属性值,直接赋予给事件属性。

【示例1】在下面示例中,把 JavaScript 脚本以字符串的形式传递给 onclick 属性,为 <button> 标签绑定 click 事件。当单击按钮时,就会触发 click 事件,执行这行 JavaScript 脚本。

<button onclick="alert('你单击了一次!');">按钮</button>

动态绑定

使用 DOM 对象的事件属性进行赋值。

【示例2】在下面示例中,使用 document.getElementById() 方法获取 button 元素,然后把一个匿名函数作为值传递给 button 元素的 onclick 属性,实现事件绑定操作。

<button id="btn">按钮</button>
<script>
    var button = document.getElementById("btn");
    button.onclick = function () {
        alert("你点击了一次!");
    }
</script>

可以在脚本中直接为页面元素附加事件,不破坏 HTML 结构,比上一种方式灵活。

事件处理函数

事件处理函数是一类特殊的函数,与函数直接量结构相同,主要任务是实现事件处理,为异步调用,由事件触发进行响应。

事件处理函数一般没有明确的返回值。不过在特定事件中,用户可以利用事件处理函数的返回值影响程序的执行,如果单击超链接时,禁止默认的跳转行为。

【示例1】下面示例为 form 元素的 onsubmit 事件属性定义字符串脚本,设计当文本框中输入值为空时,定义事件处理函数的返回值为 false。这样将强制表单禁止提交数据。

<form id="form1" name="form1" method="post" action="http://www.mysite.cn/" onsubmit="if (this.elements[0].value.length==0) return false;">
    姓名:<input id="user" name="user" type="text" />
    <input type="submit" name="btn" id="btn" value="提交" />
<form>

 在上面代码中,this 表示当前 form 元素,elements[0] 表示姓名文本框,如果该文本框的 value.length 属性值长度为 0,表示当前文本框为空,则返回 false,禁止提交表单。

事件处理函数不需要参数。在 DOM 事件模型中,事件处理函数默认包含 event 参数对象,event 对象包含事件信息,在函数内进行传播。

【示例2】下面示例为 button 对象绑定一个单击事件。在这个事件处理函数中,参数 e 为形参,响应事件之后,浏览器会把 event 对象作为一个实参进行传递,读取 event 对象包含的事件信息,在事件处理函数中输出当前源对象节点名称。

<button id="btn">按钮</button>
<script>
    var button = document.getElementById("btn");
    button.onclick = function () {
        var e = e || window.event;  //获取事件对象
        document.write(e.srcElement ? e.srcElement : e.target);  //获取当前单击对象的标签名
    }
</script>

演示效果如下:

JS事件基础(非常详细)
 
JS事件基础(非常详细)

IE 事件模型和 DOM 事件模型对 event 对象的处理方式不同:IE 把 event 对象定义为 window 对象的一个属性,而 DOM 事件模型把 event 定义为事件处理函数的默认参数。所以,在处理 event 参数时,应该判断 event 在当前解析环境中的状态,如果当前浏览器支持,则使用 event(DOM 事件模型);如果不支持,正则说明当前环境是 IE 浏览器,通过 window.event 获取 event 对象。

event.srcElement 表示当前事件的源,即响应对象的当前对象,这是 IE 模型用法。但是 DOM 事件模型不支持该属性,需要使用 event 对象的 target 属性,它是一个符合标准的源属性。为了能够兼容不同的浏览器,这里使用了一个条件运算符,先判断 event.srcElement 属性是否存在,如果不存在则使用 event.target 属性来获取当前事件对象的源。

在事件处理函数中,this 表示当前事件对象,与 event 对象的 srcElement 属性(IE 模型)或者 target(DOM 事件模型)属性所代表的意思相同。

【示例3】在下面示例中,定义当单击按钮时改变当前按钮的背景色为红色,其中 this 关键字就表示 button 对象。

<button id="btn" onclick="this.style.background='red';">按 钮</button>

也可以使用下面代码来表示:

<button id="btn" onclick="(event.srcElement?event.srcElement:event.target).style.background='red';">按 钮</button>

在一些特殊环境中,this 并非都表示当前事件对象。

【示例4】下面示例分别使用 this 和事件源来指定当前对象,但是会发现 this 并没有指向当前的事件对象按钮,而是指向 window 对象,所以这个时候继续使用 this 引用当前对象就错了。

<script>
    function btn1 () {  //事件处理函数,函数中的this表示调用该函数的当前对象
        this.style.background = "red";
    }
    function btn2 (event) {  //事件处理函数
        event = event || window.event;  //获取事件对象event
        var src = event.srcElement ? event.srcElement : event.target;  //获取当前事件源
        src.style.background = "red";  //改变当前事件源的背景色
    }
</script>
<button id="btn1" onclick="btn1()">按 钮1</button>
<button id="btn2" onclick="btn2(event)">按 钮2</button>

 为了能够准确获取当前事件对象,在第二个按钮的 click 事件处理函数中直接把 event 传递给 btn2()。如果不传递该参数,支持 DOM 事件模型的浏览器就会找不到 event 对象。

使用 event 对象

event 对象由事件自动创建,记录了当前事件的状态,如事件发生的源节点、键盘按键的响应状态、鼠标指针的移动位置、鼠标按键的响应状态等信息。event 对象的属性提供了有关事件的细节,其方法可以控制事件的传播。

2 级 DOM Events 规范定义了一个标准的事件模型,它被除了 IE 怪异模式以外的所有现代浏览器所实现,而 IE 定义了专用的、不兼容的模型。简单比较两种事件模型如下:

  • 在 DOM 事件模型中,event 对象被传递给事件处理函数,但是在 IE 事件事件模型中,它被存储在 window 对象的 event 属性中。
  • 在 DOM 事件模型中,Event 类型的各种子接口定义了额外的属性,它们提供了与特定事件类型相关的细节;在 IE 事件模型中,只有一种类型的 event 对象,它用于所有类型的事件。

下面列出了 2 级 DOM 事件标准定义的 event 对象属性,如下图所示。注意,这些属性都是只读属性。

DOM 事件模型中 event 对象属性
属性 说明
bubbles 返回布尔值,指示事件是否是冒泡事件类型。如果事件时冒泡类型,则返回 true;否则返回 false
cancelable 返回布尔值,指示事件是否可以取消的默认动作。如果使用 preventDefault() 方法可以取消与事件关联的默认动作,则返回值为 true;否则为 false
currentTarget 返回触发事件的当前节点,即当前处理该事件的元素、文档或接口。在捕获和冒泡阶段,该属性时非常有用的,因为在这两个阶段,它不同于 target 属性
eventPhase 返回事件传播的当前阶段,包括捕获阶段(1)、目标事件阶段(2)和冒泡阶段(3)
target 返回事件的目标节点(触发该事件的节点),如生成事件的元素、文档或窗口
timeStamp 返回事件生成的日期和时间
type 返回当前 event 对象表示的事件的名称。如“submit”、“load”或“click”

下面列出了 2 级 DOM 事件标准定义的 event 对象方法,如下表所示。IE 事件模型不支持这些方法。

DOM 事件模型中 event 对象方法
方法 声明
initEvent() 初始化新创建的 event 对象的属性
preventDefault() 通知浏览器不要执行与事件关联的默认动作
stopPropagation() 终止事件在传播过程的捕获、目标处理或冒泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理函数将被调用,但事件不再被分派到其他节点

上表是 Event 类型提供的基本属性,各个事件子模块也都定义了专用属性和方法。例如,UIEvent 提供了 view(发生事件的 window 对象)和 detail(事件的详细信息)属性;而 MouseEvent 除了拥有 Event 和 UIEvent 属性和方法外,也定义了更多实用属性。

IE 7 及其早期版本,,以及 IE 怪异模式不支持标准的 DOM 事件模型,并且 IE 的 event 对象定义了一组完全不同的属性,如下表所示:

IE 事件模型中 event 对象属性
属性 描述
cancelBubble 如果想在事件处理函数中阻止事件传播到上级包含对象,必须把该属性设为 true
fromElement 对于 mouseover 和 mouseout 事件,fromElement 引用移出鼠标的元素
keyCode 对于 keypress 事件,该属性声明了被敲击的键生成的 Unicode 字符码。对于 keydown 和 keyup 事件,它指定了被敲击的键的虚拟键盘码。虚拟键盘码可能和使用的键盘的布局相关
offsetX、offsetY 发生事件的地点在事件源元素的坐标系统中的 x 坐标和 y 坐标
returnValue 如果设置了该属性,它的值比事件处理函数的返回值优先级高。把这个属性设置为 false,可以取消事件发生的源元素的默认动作
srcElement 对于生成事件的 window 对象、document 对象或 element 对象的引用
toElement 对于 mouseover 和 mouseout 事件,该属性引用移入鼠标的元素
x、y 事件发生的位置的 x 坐标和 y 坐标,它们相对于用 CSS 定位的最内层包含元素

IE 事件模型并没有为不同的事件定义继承类型,因此所有和任何事件的的类型相关的属性都在上面列表中。

为了兼容 IE 和 DOM 两种事件模型,可以使用下面表达式进行兼容。

var event = event || window.event;  //兼容不同模型的event对象

上面代码右侧是一个选择运算表达式,如果事件处理函数存在 event 实参,则使用 event 形参来传递事件信息;如果不存在 event 参数,则调用 window 对象的 event 属性来获取事件信息。把上面表达式放在事件处理函数中即可进行兼容。

在以事件驱动为核心的设计模型中,一次只能处理一个事件,由于从来不会并发两个事件,因此使用全局变量来存储事件信息是一种比较安全的方法。

【示例】下面示例演示了如何禁止超链接默认的跳转行为。

<a href="https://www.baidu.com/: id="a1">禁止超链接跳转</a>
<script>
    document.getElementById("a1").onclick = function(e) {
        e = e || window.event;  //兼容事件对象
        var target = e.target || e.srcElement;  //兼容事件目标元素
        if (target.nodeName !== 'A') {  //仅针对超链接起作用
            return;
        }
        if (typeof e.preventDefault === 'function') {  //兼容DOM模型
            e.preventDefault();  //禁止默认行为
            e.cancelBubble();  //禁止事件传播
        } else {  //兼容IE类型
            e.returnValue = false;  //禁止默认行为
            e.cancelBubble = true;  //禁止冒泡
        }
    };
</script>

事件委托

事件委托(delegate)也称为事件托管或事件代理,就是把目标节点的事件绑定到祖先节点上。这种简单而优雅的事件注册方式是基于事件传播过程中,逐层冒泡总能被祖先节点捕获。

这样做的好处:优化代码,提升运行性能,真正把 HTML 和 JavaScript 分离,也能防止出现在动态添加或删除节点过程中注册的事件丢失的现象。

【示例1】下面示例使用一般方法为列表结构中每个列表项目绑定 click 事件,单击列表项目,将弹出提示对话框,提示当前节点包含的文本信息。但是,当我们为列表框动态添加列表项目之后,新添加的列表项目没有绑定 click 事件,这与我们的愿望相反。

<button id="btn">添加列表项目</button>
<ul id="list">
    <li>列表项目1</li>
    <li>列表项目2</li>
    <li>列表项目3</li>
</ul>
<script>
    var ul = document.getElementById("list");
    var lis = ul.getElementsByTagName("li");
    for (var i = 0; i < lis.length; i ++) {
        lis[i].addEventListener('cluick', function (e) {
            var e = e || window.event;
            var target = e.target || e.srcElement;
            alert(e.target.innerHTML);
        }, false);
    }
    var i = 4;
    var btn = document.getElementById("btn");
    btn.addEventListener("click", function() {
        var li = document.createElement("li");
        li.innerHTML = "项目列表" + i++;
        ul.appendChild(li);
    });
</script>

【示例2】下面示例借助事件委托技巧,利用事件传播机制,在列表框 ul 元素上绑定 click 事件,当事件传播到父节点 ul 上时,捕获 click 事件,然后在事件处理函数中检测当前事件响应节点类型,如果是 li 元素,则进一步执行下面代码,否则跳出事件处理函数,结束响应。

<button id="btn">添加项目列表</button>
<ul id="list">
    <li>列表项目1</li>
    <li>列表项目2</li>
    <li>列表项目3</li>
</ul>
<script>
    var ul = document.getElementById("list");
    ul.addEventListener('click', function(e) {
        var e = e || window.event;
        var target = e.target || e.srcElement;
        if (e.target && e.target.nodeName.toUpperCase()=="LI") {
            alert(e.target.innerHTML);
        }
    }, false);
    var i = 4;
    var btn = document.getElementById("btn");
    btn.addEventListener("click", function () {
        var li = document.createElement("li");
        li.innerHTML = "项目列表" + i++;
        ul.appendChild(li);
    });
</script>

当页面存在大量元素并且每个元素注册了一个或多个事件时,可能会影响性能。访问和修改更过的 DOM 节点时,程序就会更慢;特别是事件连接过程都发生在 load(或 DOMContentReady)事件中时,对任何一个富交互网页来说,这都是一个繁忙的时间段。另外,浏览器需要保存每个事件句柄的记录,也会占用更多内存。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/23284.html

(0)
上一篇 2021年7月20日
下一篇 2021年7月20日

相关推荐

发表回复

登录后才能评论