[夯实前端基础]--js事件详解

tips:欢迎关注我在github的博客点击查看

遇到js事件的问题思考,回去补当初自己的总结文,受益匪浅,感谢当初的自己😂原文地址

javaScript 与 HTML 之间的交互是通过事件实现的。

事件流

概念:事件流描述的是从页面中接收事件的顺序。

事件冒泡

概念:即事件开始时由最具体的元素(文档中嵌套层次最深 的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

image

理论及运用

  • 任何可以冒泡的事件都不仅仅可以在事件目标上进行处理,目标的任何祖先节点上也能处理。

运用事件委托 更详细的介绍看这篇文章

事件委托原理:以将事件处理程序附加到更高层的地方负责多个目标的事件处理。

大多数 Web 应用在用户交互上大量用到事件处理程序。页面上的事件处理程序的数量和页面响应 用户交互的速度之间有个负相关。为了减轻这种惩罚,最好使用事件代理。

事件委托实例

<ul id="list">  
        <li>我是孩子1</li>  
        <li>我是孩子2</li>  
        <li>我是孩子3</li>  
        <li>我是孩子4</li>  
        <li>我是孩子5</li>  
</ul> 
<div id = "btn">添加一个新孩子</div>

场景1:需要在点击列表项的时候响应一个事件。如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

场景2:动态添加新的Li元素。在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

代码: 代码运行

//事件委托
var ul = document.getElementById('list'); 
ul.addEventListener('click', function (event) {
  // 兼容性处理
  var event = event||window.event;
  var target = event.target || event.srcElement;//target表示在事件冒泡中触发事件的源元素,在IE中是srcElement  
  // 判断是否匹配目标元素
  if (target.nodeName.toLowerCase() == 'li') {
       alert(target.innerHTML);
  }
});
//添加li元素
document.getElementById('btn').addEventListener('click', function (event) {
        var li = document.createElement('li');  
        li.innerHTML="我是新孩子";  
        ul.appendChild(li);  
})
  • 不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。

  • 有些业务场景需要阻止冒泡事件的冒泡,比如在一个HTML结构中,父级包含着子级,当事件在子级发生时(click,mouseenter,mouseleave等),由于事件冒泡就会触发父级的同名事件。示例:

<ul id="parent">
    <li id="child">son</li>
</ul>
   child.addEventListener("click",function(event){
        alert(1);
        // event stopPropagation(); //标准浏览器
        //或者是
        //event cancelBubble = true; //IE
    })
    parent.addEventListener("click",function(event){
        alert(1);
    })

当点击li时,会弹出两个1,可以通过阻止冒泡防止这样的行为,点击li就弹出一个1。
event.stopPropagation()或event.cancelBubble = true 可以阻止事件冒泡。

  • 阻止冒泡并不能阻止对象默认行为。比如submit按钮被点击后会提交表单数据,这种行为无须我们写程序定制。

应用

  • 事件代理,事件冒泡允许多个操作被集中处理(把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元素上),它还可以让你在对象层的不同级别捕获事件
  • 让不同的对象同时捕获同一事件,并调用自己的专属处理程序做自己的事情,就像老板一下命令,各自员工做自己岗位上的工作去了。

事件捕获

概念:事件捕获的思想 是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在 事件到达预定目标之前捕获它。

image

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段::事件捕获阶段、处于目标阶段和事件冒泡阶段。

首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶 段,可以在这个阶段对事件做出响应。

image

在 DOM 事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。这意味着在捕获阶段, 事件从 document 到<html>再到<body>后就停止了。下一个阶段是“处于目标”阶段,于是事件在<div> 上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。然后,冒泡阶段发生, 事件又传播回文档。

事件处理程序

概念:事件就是用户或浏览器自身执行的某种动作。诸如 click、load 和 mouseover,都是事件的名字。 而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,onclick等。

HTML事件处理程序

概念:某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的 HTML 特性来指定。

例如,要在按钮被单击时执行一些 JavaScript,可以像下面 这样编写代码:

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

缺点

首先,存在一个时差问题。因为用户可能会在 HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。

另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。

最后一个缺点是 HTML 与 JavaScript 代码紧密耦合。如果要更换事 件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。

DOM0 级事件处理程序

简单来说就是取得一 个要操作的对象的引用,然后为它指定了某事件处理程序(如onclick)。使用 DOM0 级方法指定的事件处理程序被认为是元素的方法,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

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

btn.onclick = null;//删除事件处理程序

将事件处理程序设置为 null 之后,再单击按钮将不会有任何动作发生。

DOM2 级事件处理程序

“DOM2 级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener() 和 removeEventListener()。

onclick 事件会被覆盖,addevenlistener 事件先后执行。

所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获 阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。没有指定的话,默认为 false。

target.addEventListener(type, listener, useCapture);
var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
    alert(this.id); 
}, false);

上面的代码为一个按钮添加了 onclick 事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数是 false,此时和DOM0级的onclick事件差不多)。

使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id); 
}, false); 
btn.addEventListener("click", function(){
    alert("Hello world!"); 
}, false);

这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的 ID,其次会显示"Hello world!"消息。

MDN 对第三个参数的解释是:Boolean,是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件。

沿着DOM树向上冒泡的事件不会触发被指定为为true的listener(因为此时为true是比冒泡更早的事件捕获阶段)。

例子: (引自csdn的一篇文章

image.png
<style>
.parent
{
    position: relative;
    background-color: coral;
    border: 1px solid;
    padding: 50px;
}
.child {
    position: relative;
    background-color: pink;
    width: 100px;
    height: 100px;
}
</style>
<body>
<p>该实例演示了在添加事件监听时冒泡与捕获阶段的不同。</p>
<div id="parent" class="parent">
    <div id="child" class="child">点击该方块, 我是冒泡</div>
</div><br>
<div id="parent2" class="parent">
    <div id="child2" class="child">点击该方块, 我是捕获</div>
</div>
<script>
//事件冒泡    child的DOM事件先发生(粉),parent的DOM事件再发生(橙)。
document.getElementById("child").addEventListener("click", function(){
    alert("你点击了 粉色!");
}, false);
document.getElementById("parent").addEventListener("click", function(){
    alert("你点击了 橙色!");
}, false);

//事件捕获  parent的DOM事件先发生(橙),parent的DOM事件再发生(粉)。
document.getElementById("child2").addEventListener("click", function(){
    alert("你点击了 粉色!");
}, true);
document.getElementById("parent2").addEventListener("click", function(){
    alert("你点击了 橙色!");
}, true);
</script>
</body>

点击查看实例效果

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需 要,我们不建议在事件捕获阶段注册事件处理程序。

事件对象

兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什 么方法(DOM0 级或 DOM2 级),都会传入 event 对象。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(event){
    alert(event.type); //"click" 
}; 
btn.addEventListener("click", function(event){
alert(event.type); //"click" 
}, false);

触发的事件类型不一样,可用的属性和方法也不一样。常见的有event.type,event.target 等

事件类型

Web 浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信息,而“DOM3 级事件”规定了以下几类事件。

  • UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发; 如resize,scroll
  • 焦点事件,当元素获得或失去焦点时触发; 如blur (在元素失去焦点时触发,这个事件不会冒泡)
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发;如click
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
  • 文本事件,当在文档中输入文本时触发;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发;
  • 合成事件,当为 IME(Input Method Editor,输入法编辑器)输入字符时触发;
  • 变动(mutation)事件,当底层 DOM 结构发生变化时触发。

内存和性能

事件委托

这个前面讲到了 ,这边不重复。

移除事件处理程序

每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就 会建立一个连接。。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立 的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那 些过时不用的“空事件处理程序”(dangling event handler),也是造成 Web 应用程序内存与性能问题的 主要原因。

在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。 这可能是通过纯粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法。但更多地是发 生在使用 innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被 innerHTML 删除 了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。

<div id="myDiv"> 
    <input type="button" value="Click Me" id="myBtn"> 
</div>
<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
    //先执行某些操作
   btn.onclick = null; //移除事件处理程序
    document.getElementById("myDiv").innerHTML = "处理中..."; //把按钮替换成文字
}; 
</script>

这里,有一个按钮被包含在<div>元素中。为避免双击,单击这个按钮时就将按钮移除并替换成一 条消息;这是网站设计中非常流行的一种做法。但问题在于,当按钮被从页面中移除时,它还带着一个 事件处理程序呢。在<div>元素上设置 innerHTML 可以把按钮移走,但事件处理程序仍然与按钮保持 着引用关系。有的浏览器(尤其是 IE)在这种情况下不会作出恰当地处理,它们很有可能会将对元素和 对事件处理程序的引用都保存在内存中。如果你知道某个元素即将被移除,那么最好手工移除事件处理 程序

第二种情况,就是卸载页面的时候。如果在页面被卸载之前没 有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页 面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序 占用的内存并没有被释放。一般来说,最好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。 在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。对这 种类似撤销的操作,我们可以把它想象成:只要是通过 onload 事件处理程序添加的东西,最后都要通 过 onunload 事件处理程序将它们移除。

模拟事件

概念:使用 JavaScript 在任意时刻来触发特定的事件,此时的事件就如同浏览器创建的事件一样。

模拟鼠标事件

思路: 1.创建事件对象 2.分发事件

          //创建事件对象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //分发事件 
          document.getElementById("myBtn").dispatchEvent(event);

ps:红包书介绍的创建事件对象的方法有点过时了,现被MDN不建议使用

    var event = document.createEvent("MouseEvents");  //创建事件对象 
    event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  //初始化事件对象 
image

实例运用 代码运行

这边有两个按钮,按钮1绑定一个鼠标点击事件1,alert出一句话;在按钮2绑定一个鼠标点击事件2,鼠标点击事件2将模拟用户去点击按钮1。所以点按钮2,触发一个模拟鼠标事件去点击按钮1。

<body>
    <button id="btn1">我是按钮1</button>
    <button id="btn2">我是按钮2,点我一下 按钮1也会被点击哦</button>
    <script type="text/javascript">
        var btn=document.getElementById("#btn1");
        // 按钮1的点击事件
        btn.addEventListener('click',function(e){
           console.log("我是按钮1,我被点击啦") // 点击按钮2,按钮1也会被点击(模拟事件);只点击按钮1(正常事件)
        })
        // 按钮2的点击事件
        document.querySelector("#btn2").addEventListener('click',function(e){
           //模拟鼠标事件步奏1  创建事件对象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //模拟鼠标事件步奏2  触发事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

模拟键盘事件

这边举一反三,不做解释。

模拟其他事件

自定义 DOM 事件

实例运用 代码运行

<body>
    <button id="btn1">我是按钮1,我没鼠标事件,点我不会输出</button>
    <button id="btn2">我是按钮2,点我一下会执行绑定在按钮1的自定义事件,会有输出</button>
    <script type="text/javascript">
       var btn= document.querySelector("#btn1");
        // 按钮1的点击事件
        btn.addEventListener('test',function(e){
           console.log("我是按钮1,我被点击啦") // 点击按钮2,有输出(自定义事件);只点击按钮1,没输出(自定义事件,非模拟鼠标事件)
        })
       // 按钮2的点击事件
        document.querySelector("#btn2").addEventListener('click',function(e){
          //自定义事件步奏1  创建事件对象
           var event = new Event('test', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //自定义鼠标事件步奏2  触发事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

红宝书的方法有点过时,新实现方法可看这篇文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,464评论 1 11
  • JavaScript 与 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬...
    LemonnYan阅读 669评论 0 4
  • 事件流 JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互...
    DHFE阅读 815评论 0 3
  • JavaScript 程序采用了异步事件驱动编程模型。在这种程序设计风格下,当文档、浏览器、元素或与之相关的对象发...
    劼哥stone阅读 1,248评论 3 11
  • JavaScript 与 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬...
    threetowns阅读 337评论 0 0