面试题 事件

事件

  • 题目
  • 知识点
  • 解答

题目

  • 编写一个通用的事件监听函数
  • 描述事件冒泡的流程
  • 无限下拉的图片列表,如何监听每个图片的点击
编写一个通用的事件监听函数

 var Event = {
    
    
   //页面加载完成后
   readyEvent:function(fn){
    
    
    if(fn == null){
    
    
     fn = document;
    }
    var oldonload = window.onload;
    if(typeof window.onload != 'function'){
    
    
     window.onload = fn;
    }else{
    
    
     window.onload = function(){
    
    
      oldonload();
      fn();
     }
    }
   },
   //添加事件
   addEvent:function(element,type,hander){
    
    
    if(element.addEventListener){
    
     //非IE
     //事件类型,需要执行的函数,是否捕获
     element.addEventListener(type,hander,false);
    }else if(element.attachEvent){
    
     //IE
     element.attachEvent("on"+type,function(){
    
    
      hander.call(element);
     })
    }else{
    
    
     element["on"+type] = hander;
    }
   },
   //移除事件
   removeEvent:function(element,type,hander){
    
    
    if(element.removeEventListener){
    
    
     element.removeEventListener(type,hander,false)
    }else if(element.attachEvent){
    
    
     element.attachEvent("on"+type,hander);
    }else{
    
    
     element["on"+type] = null;
    }
   },
   //阻止事件
   stopPropagation:function(event){
    
    
    if(event.stopPropagation){
    
    
     event.stopPropagation();//w3c标准
    }else{
    
    
     event.cancelBubble = true; //ie
     
    }
   },
   //取消默认事件
   preventDefault:function(event){
    
    
    if(event.preventDefault){
    
    
     event.preventDefault();
    }else{
    
    
     event.returnValue = false; //ie
    }
   },
   //获取事件目标
   getTarget:function(event){
    
    
    return event.target || event.srcElement;
   },
   //获取event对象的引用,取到事件的所有信息,确保随时能使用event;
   getEvent:function(e){
    
    
    var even = e || window.event;
    if(!even){
    
    
     var c = this.getEvent.caller;
     while(!c){
    
    
      even = c.arguments[0];
      if(even && Event == even.constructor){
    
    
     break;  
      }
      c = c.caller;
     }
    }
    return even;
   }
   
  }
  
描述事件冒泡的流程
什么是冒泡?

DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段

**事件捕获(event capturing):**通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。

事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。

dom标准事件流的触发的先后顺序为:先捕获再冒泡,即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。不同的浏览器对此有着不同的实现,IE10及以下不支持捕获型事件,所以就少了一个事件捕获阶段,IE11、Chrome 、Firefox、Safari等浏览器则同时存在。

addEventListener()方法
这个方法设定一个事件监听器,当某一事件发生通过设定的参数执行操作。语法是:

addEventListener(event, function, useCapture)

参数 event是必须的,表示监听的事件,例如 click, touchstart 等,就是之前不加前缀 on 的事件。
参数function 也是必须的,表示事件触发后调用的函数,可以是外部定义函数,也可以是匿名函数。
参数 useCapture 是选填的,填true或者false,用于描述事件是冒泡还是捕获,true表示捕获,默认的false表示冒泡。

移除事件监听

如果要移除 addEventListener() 添加的事件监听,就要使用removeEventListener(),语法是:

removeEventListener(event, function)

参数与addEventListener()一致。

兼容性

IE 8及更早的版本,和Opera 7.0及更早的版本,不支持 addEventListener() 和 removeEventListener() 方法,他们使用的是一下方法代替:

添加事件:
attachEvent(event, function)

移除事件:
detachEvent(event, function)

可以用以下方法解决兼容性问题:


if (div1.addEventListener) {
    
    
     div1.addEventListener('click', function () {
    
    
          console.log("支持")
     });
} else if (div1.attachEvent) {
    
    
       div1.attachEvent('onclick', function () {
    
    
           console.log("不支持")
       });
}

冒泡和捕获的具体区别
HTML

<div id="div1">
     <div id="div2"></div>
</div>

JS

<script>
     var div1 = document.getElementById("div1");
     var div2 = document.getElementById("div2");
      div1.addEventListener('click',function(){
    
    
           console.log("div1--捕获阶段")
      },true);
      div2.addEventListener('click',function(){
    
    
          console.log("div2--捕获阶段")
      },true);
      div1.addEventListener('click',function(){
    
    
          console.log("div1--冒泡阶段")
      });
      div2.addEventListener('click',function(){
    
    
          console.log("div2--冒泡阶段")
      });
</script>

输出结果(点击div2的时候执行的结果)

在这里插入图片描述

解决办法

function stopBubble(e) {
    
    
     if (e && e.stopPropagation) {
    
    
            e.stopPropagation(); /*因此它支持W3C的stopPropagation()方法 */
            } else {
    
    
                window.event.cancelBubble = true; //否则,我们需要使用IE的方式来取消事件冒泡 
            }
}

在这里插入图片描述

无限下拉的图片列表,如何监听每个图片的点击

用途:适合无线下拉图片列表每个图片的事件监听


function bindEvent(elem, type, selector, fn) {
    
      
    // 如果传了三个参数,则第三个参数就是fn
    if(fn == null){
    
    
        fn = selector
        selector = null
    }
    elem.addEventListener(type, event => {
    
    
        const target = event.target
        if (selector) {
    
    
            // 代理绑定,绑定无限个
            if (target.matches(selector)) {
    
    
                fn.call(target, event)
            }
        } else {
    
    
            // 普通绑定
            fn.call(target, event)
        }
    })
}


const test1 = document.getElementById("tyre")
const event1 = function () {
    
      
    console.log(this.innerText);
}
// 绑定多个
bindEvent(test1,'click','p',event1)

// 绑定一个
const event2 = function () {
    
      
    console.log(this.id);
}
bindEvent(test1,'click',event2)

    <div id="tyre">
        <p>1</p>
        <p>2</p>
        <p>3</p>
        <p>4</p>
        <p>5</p>
        <p>6</p>
        <p>7</p>
        <p>8</p>
        <p>9</p>
    </div>

在这里插入图片描述
函数bindEvent传入四个参数(父节点,需要绑定的事件类型,需要绑定的节点,事件内容)

知识点

  • 事件绑定
  • 事件冒泡
  • 事件代理
事件绑定
1、事件绑定的几种方式

在Javascript中,事件绑定一共有3种方式:
① 行内绑定
② 动态绑定
③ 事件监听

2、行内绑定

在这里插入图片描述
在这里插入图片描述

基本语法:
<标签 属性列表 事件=”事件的处理程序” />
例:
示例代码:

在这里插入图片描述

以上代码就是最典型的行内绑定,虽然可以完成我们需要的功能,但是其把结构+样式+行为都绑定在同一个标签中,不利于后期维护。

3、动态绑定

基本语法:

dom对象.事件 = 事件的处理程序(通常是一个匿名函数)
通过动态绑定这种思想改进上题,效果如下图所示:

在这里插入图片描述

4、行内绑定与动态绑定的区别

在Javascript中,有一个特殊对象叫做this,其随着运行环境的不同,其指向也是不同的!
例1:探究行内绑定中的this指向

在这里插入图片描述

运行结果:this.style为空或不是对象,所以由此可以得出一个结论:在行内绑定中其this并不是指向当前正在操作的dom对象,那请问其指向何方呢?

使用调试工具调试结果如下
在这里插入图片描述
由此可知:行内绑定中其this指向了全局window对象!
例2:探究动态绑定中的this指向
在这里插入图片描述

运行结果:id为box的div元素背景发生了变化,由此可以得出结论:动态绑定中其this指向了当前正在操作的DOM对象。

综上所述:

行内绑定中,其事件处理中的this指向了全局window对象
动态绑定中,其事件处理中的this指向了当前正在操作的dom对象

5、封装一个自定义函数

在Javascript中,我们操作的大多数都是dom对象,获取方式都是通过document.的形式,这样代码过于冗余,所以为了解决这个问题,我们封装一个自定义函数,专门用于获取指定id的dom对象。

在这里插入图片描述

事件冒泡

在这里插入图片描述

1、事件阶段

如下图所示
在这里插入图片描述
一般的,事件分为三个阶段:捕获阶段、目标阶段和冒泡阶段。

(1)捕获阶段(Capture Phase)

事件的第一个阶段是捕获阶段。事件从文档的根节点流向目标对象节点。途中经过各个层次的DOM节点,并在各节点上触发捕获事件,直到到达事件的目标节点。捕获阶段的主要任务是建立传播路径,在冒泡阶段,事件会通过这个路径回溯到文档跟节点。

(2)目标阶段(Target Phase)

当事件到达目标节点的,事件就进入了目标阶段。事件在目标节点上被触发,然后会逆向回流,直到传播至最外层的文档节点。

(3)冒泡阶段(Bubble Phase)

事件在目标元素上触发后,并不在这个元素上终止。它会随着DOM树一层层向上冒泡,回溯到根节点。
冒泡过程非常有用。它将我们从对特定元素的事件监听中释放出来,如果没有事件冒泡,我们需要监听很多不同的元素来确保捕获到想要的事件。

2、冒泡阶段调用事件处理函数

在这里插入图片描述

封装一个getTag函数,当点击a标签的时候,由于是冒泡机制,会从目标节点向上逐级触发各个节点a,li,ul,div的事件处理函数。

3、捕获阶段调用事件处理函数

在这里插入图片描述

在function回调函数里加个true,则为事件捕获。当点击a标签的时候,由于是捕获机制,会从根节点向下逐级触发各个节点直到目标节点触发事件处理函数即div,ul,li,a

4、事件代理
  • 代码简洁
  • 减少浏览器内存占用
  • 但是,不要滥用
    在这里插入图片描述

在传统的事件处理中,你按照需要为每一个元素添加或者是删除事件处理器。然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越大。JavaScript事件代理可以把事件处理器添加到一个父元素上,这样就避免了把事件处理器添加到多个子元素上。

(1)它是怎么运作的呢?

事件代理用到了两个JavaSciprt事件特性:事件冒泡以及目标元素。当一个元素上的事件被触发的时候,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。

(2)这对我有什么好处呢?

比如说在一个10列、100行的HTML表格里,让其每一个单元格在被点击的时候变成可编辑状态。如果把事件处理器加到这1000个单元格会产生一个很大的性能问题,并且有可能导致内存泄露甚至是浏览器的崩溃。相反地,使用事件代理,你只需要把一个事件处理器添加到table元素上就可以了,这个函数可以把点击事件给截下来,并且判断出是哪个单元格被点击了。

(3)另一个简单的例子

在这里插入图片描述

如图所示,如果我们需要对每个li元素进行点击,触发事件。传统的我们要像绿色注释部分那样给每一个li元素添加事件处理函数。但是通过事件代理,我们只需要在ul元素进行事件监听就可以实现点击每一个li都能触发事件了

5、阻止事件冒泡(stopPropagation)
(1)为什么要阻止事件冒泡

有种可能是,某个DOM节点绑定了某事件监听器,本来是想当该DOM节点触发事件,才会执行回调函数。结果是该节点的某后代节点触发某事件,由于事件冒泡,该DOM节点事件也会触发,执行了回调函数,这样就违背了最初的本意了。

(2)如何阻止事件冒泡

在这里插入图片描述

如图所示,在第一个li标签加上event.stopPropagation()方法就能阻止事件的冒泡,这样的话在点击第一个li标签就无法触发事件处理函数了。

(3)无法在捕获阶段阻止事件冒泡

这里需要注意的是,我们无法在事件捕获阶段阻止事件冒泡!!!例如,我们在代码里加上true,如图所示,第一个li会触发事件。因为捕获是从根节点向目标节点触发,而冒泡是从目标节点向根节点触发。

在这里插入图片描述

事件代理

将事件代理到父容器上,这样修改子元素,父容器都会监听到,从而执行对应的操作,若是将事件绑定到子元素上,假设新增了子元素,又得重新给子元素绑定事件,是非常繁琐的。

举个例子

点击 add 添加子元素,点击子元素输出对应的内容


//html
<div class="container">
  <div class="box">box1</div>
  <div class="box">box2</div>
  <div class="box">box3</div>
</div>
<button id="add">add</button>

//js
function $(selector) {
    
    
  return document.querySelector(selector)
}
function $$(selector) {
    
    
  return document.querySelectorAll(selector)
}
//将事件代理到父容器上,点击目标元素,父容器监听到,执行对应的操作
//点击新增的 box 也会输出对应的 innerText
$('.container').onclick = function(e) {
    
    
  // console.log(this)
  // console.log(e.target)
  if(e.target.classList.contains('box')) {
    
    
    console.log(e.target.innerText)
  }
}
//点击添加 box
var i = 4
$('#add').onclick = function() {
    
    
  var box = document.createElement('div')
  box.classList.add('box')
  box.innerText = 'box' + (i++)
  $('.container').appendChild(box)
}

效果
在这里插入图片描述
在这里插入图片描述

点击新增的子元素,也会输出对应的内容,但是我们只绑定了一次事件,在父容器上绑定的(利用了事件的冒泡与捕获)

在这里插入图片描述

问题解答

– 编写一个通用的事件监听函数

  • 描述事件冒泡的流程
  • 无限下拉的图片列表,如何监听每个图片的点击
编写一个通用的事件监听函数

在这里插入图片描述

描述事件冒泡的流程
  • 基于 DOM 树形结构
  • 事件会顺着触发元素往上冒泡
  • 应用场景:代理
无限下拉的图片列表,如何监听每个图片的点击
  • 事件代理
  • 用 e.target 获取触发元素
  • 用 matches 来判断是否是触发元素

小结

  • 事件绑定
  • 事件冒泡
  • 事件代理

猜你喜欢

转载自blog.csdn.net/WLIULIANBO/article/details/115094921