Loading...
墨滴

ens33

2021/11/23  阅读:14  主题:默认主题

JS-Web-API-事件(必考)

代码笔记

1. 题目

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

2. 知识点

2.1. 事件绑定

  • 代码示例
<div id="div1" class="container">
  <button id="btn1">事件绑定示例1</button>
</div>

<script>
  const btn = document.getElementById('btn1')
  btn.addEventListener('click', event=> {
      console.log('clicked');
  })
</script>
  • 写一个通用的绑定函数 - 简易版
<div id="div1" class="container">
  <button id="btn1">事件绑定示例1</button>
</div>
<a id="link1" href="https://www.baidu.com/">事件绑定示例2</a>

<script>
  // 通用的绑定函数 - 简易版
  let bindEvent = (elem, type, fn)=> {
    elem.addEventListener(type, fn)
  }

  // 按钮
  const btn = document.getElementById('btn1')
  // 函数他会返回一个参数值,即event对象
  bindEvent(btn, 'click', event=> {
    // event参数的用法
    console.log(event.target);  // 触发点击的元素 - 作代理的时候会有用
    event.preventDefault();  // 阻止默认行为

    alert('clicked');
  })

  // a标签
  const a = document.getElementById('link1')
  bindEvent(a, 'click', event=> {
    event.preventDefault();  // 在这里会阻止a标签的跳转
    console.log("link");
  })
</script>

2.2. 事件冒泡

  • 代码示例
<div id="div1">
  <p id="p1">激活</p>
  <p id="p2">取消</p>
  <p id="p3">取消</p>
  <p id="p4">取消</p>
</div>
<div id="div2">
  <p id="p5">取消</p>
  <p id="p6">取消</p>
</div>

<script>
  // 通用的绑定函数 - 简易版
  let bindEvent = (elem, type, fn)=> {
    elem.addEventListener(type, fn)
  }

  // 事件冒泡
  // 示例一 - 理解冒泡
  // 在body上监听事件
  const body = document.body
  bindEvent(body, 'click', event => {
    console.log('取消')
    console.log('body clicked')
    // 每点击一下,事件都会一层一层往上冒泡,冒到body上去
    console.log(event.target)  // 需要知道是谁触发的
  })
  // 事件可以冒泡div2上吗
  // 点击div2的时候,div2的事件先执行,再执行body里面的事件
  // 因为他是一层一层地往上冒泡,div2在body里面
  // 点击p5的时候,他是先冒泡到div2,再冒泡到body
  // 点击div1下的p标签,它只会冒泡到body
  // 因为div1下的p标签没有被监听
  const div2 = document.getElementById('div2')
  bindEvent(div2, 'click', event=> {
    console.log("div2 clicked");
    console.log(event.target);
  })

  // 示例二 - p1
  // 点击p1激活,点击取消,就都是取消状态
  // 没必要给每个p标签都做绑定(即获取所有p标签id)
  const p1 = document.getElementById('p1')
  // p
  bindEvent(p1, 'click', event => {
    // 根据冒泡机制,点击激活p标签,也会冒泡到body,显示取消
    // 这里使用阻止冒泡,即只触发点击id为p1的p标签的事件
    event.stopPropagation() // 阻止冒泡
    console.log('激活')
  })
</script>

2.3. 事件代理

  • 事件代理是在事件冒泡的机制上做的
    • 只有实现了事件冒泡,才可以做事件代理
  • 概念
    • 当dom数量较多或结果比较复杂,不能给每个dom绑定事件的时候,我们把事件绑定到它的父元素上
    • 在这个父元素上根据event.target获取触发的元素,然后判断是否为我们需要的元素,之后再去做其他的动作
    • 这就是事件代理
  • 代码示例
<!-- 
  如果要给每个a标签监听事件
  不可能给每个a标签都设定一个id去监听
  这时候可以把监听放在div1上
  通过冒泡机制去监听所有a标签去响应事件
  这就是事件代理
-->

<div id="div1">
  <a href="#">a1</a><br>
  <a href="#">a2</a><br>
  <a href="#">a3</a><br>
  <a href="#">a4</a><br>
  <button>加载更多...</button>
</div>

<script>
  // 通用的绑定函数 - 简易版
  let bindEvent = (elem, type, fn)=> {
    elem.addEventListener(type, fn)
  }

  // 事件代理示例
  const div1 = document.getElementById('div1');
  bindEvent(div1, 'click', event=> {
    // 每次点击div1下的标签,都会冒泡到div1上来
    // 点击button的时候也会冒泡到div1上来
    // a标签还有一个默认行为就是点击可能会跳转链接
    event.preventDefault()  // 阻止默认行为
    let target = event.target  // 判断当前触发元素
    console.log("当前触发元素", target, target.nodeName, target.textContent);

    if(target.nodeName === 'A') {
        alert(target.innerHTML)
    }
  })
</script>
  • 事件代理的意义
    • 代码简洁
      • 不需要给每个节点设置id,还减少了dom查询
    • 减少浏览器内存占用
      • 如果子节点数量过多,每个节点都去监听,会十分损耗内存
    • 但是不能滥用
      • 对于瀑布流这些子节点很多的可以使用,其他不建议
      • 类似一个按钮的绑定使用代理显得复杂

2.4. 通用的事件绑定函数

<div id="div1" class="container">
    <button id="btn1">事件绑定示例1</button>
</div>

<div id="div2">
  <a href="#">a1</a><br>
  <a href="#">a2</a><br>
  <a href="#">a3</a><br>
  <a href="#">a4</a><br>
  <button>加载更多...</button>
</div>

<script>
  // 通用的绑定函数
  // 既能支持普通的监听
  // 又能支持代理的监听
  // 在代理中不需要在自己的业务逻辑中判断触发的元素
  // selector表示筛选出需要触发的标签
  // 并且需要this永远指向触发的元素
  let bindEvent = (elem, type, selector, fn)=> {
    // 需要做判断,如果没有传selector,即不是代理
    // x == null  =>  x === null || x === undefind
    // 如果fn为null表示传了三个参数,需要把对应关系转换一下
    if(fn == null) {
        fn = selector
        selector = null
    }

    // 绑定
    elem.addEventListener(type, event=> {
      // 不管有没有代理,都需要target
      const target = event.target  // 我们当前触发的元素
      const targetText = event.target.textContent  // 获取文本
      console.log("当前触发的元素", target)

      if(selector) {
        console.log("代理绑定");

        // 存在selector表示他是代理绑定
        // 使用matches表示,这个dom元素(我们当前触发的元素),是否符合于这个css选择器
        if(target.matches(selector)) {
          // 将fn的this指向改成触发的元素
          // call第一个参数是this指向,后面的参数表示fn的参数
          // call会直接执行
          fn.call(target, event)
        }
      } else {
        console.log("普通绑定");

        // 不存在selector表示他是普通绑定
        // 需要this指向当前触发的元素,即target
        // event表示执行的函数的参数
        // 我们需要使用event对象做一些操作
        // call后面第二个参数开始,就可以当作函数的形参
        fn.call(target, event)
      }
    })
  }

  // 普通绑定
  const btn1 = document.getElementById('btn1')
  // 这里不能使用箭头函数,因为箭头函数没有this指向
  // 也可以说箭头函数里面的this指向当前方法块所在的环境(上级作用域)
  // 即箭头函数里面的this永远指向它的父级(即上级作用域)
  bindEvent(btn1, 'click'function (event{
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    // 如果使用箭头函数,这里的this指向window
    alert(this.innerHTML)  // this指向触发元素

    // 如果使用箭头函数
    // alert(btn1.innerHTML)
  })

  // 代理绑定
  const div2 = document.getElementById('div2')
  // 表示只允许a标签触发
  bindEvent(div2, 'click''a'function (event{
    event.preventDefault()  // 阻止默认行为
    alert(this.innerHTML)

    // 如果使用箭头函数
    // alert(div2.innerHTML)
  })
</script>

3. 面试题解答(总结)

  • 编写一个通用的事件监听函数
    • 需要考虑代理和this的问题
    • 见上
  • 描述事件冒泡的流程
    • 基于DOM树形结构
    • 事件会顺着触发元素往上冒泡
      • 监听父节点的dom,点击子节点会触发父节点的事件,如果监听body的点击事件,那么在dom树中body下任意元素触发点击事件,都会冒泡到body的事件中
    • 应用场景:代理
  • 无限下拉的图片列表,如何监听每个图片的点击
    • 使用事件代理,监听父元素,通过父元素的监听获取子元素内容
    • 使用event.target获取触发元素
    • 使用matches来判断是否触发元素


~End~

ens33

2021/11/23  阅读:14  主题:默认主题

作者介绍

ens33