技术思考【事件绑定】

257 阅读5分钟
  • 案例的详细解读和实现方式,自己的经验总结

js 中的事件绑定

事件是在编程时系统内发生的动作或者发生的事情,因此为元素做事件绑定是一项必不可少的事情。在 JavaScript 中,事件绑定有三种方法。

DOM 元素中直接绑定

CSS 一样,可以直接用 行内式 的方式为元素绑定事件。具体语法代码为 on+事件名="方法"

<button onclick="alert('警告框')">点击我,弹出警告框</button>

这样就为按钮绑定了一个点击事件。

这个方法的优点:

  1. 非常简单和稳定,可以确保它在你使用的不同浏览器中运作一致
  2. 处理事件时,this 关键字引用的是当前元素

这个方法的缺点:

  1. 不利于行为和结构相分离,耦合度太高,不建议在项目中使用。
  2. 在遇到相同类型的事件时,只会去处理第一个事件,而忽略后续的事件。
  3. 传统方法只会在事件冒泡中运行,而非捕获和冒泡
  4. 事件对象参数( e )仅非 IE 浏览器可用

JavaScript 代码中绑定

为了解决方面所述的问题,早期的 JavaScript 提供了在 JavaScript 中(即script 双标签中)绑定事件。具体语法为 on+事件名=function(){}

<button>点我</button>
let btn = document.querySelector('button')
btn.onclick = function() {
    console.log('我被点击啦');
}

这个方法的优点:

  1. 将行为与结构分离开了
  2. 非常简单和稳定,可以确保它在你使用的不同浏览器中运作一致
  3. 处理事件时,this关键字引用的是当前元素

这个方法的缺点同样很明显:

  1. 仅仅解决了结构分离的问题,但依旧只能在事件冒泡中运行,无法在事件捕获中进行。

  2. 一个元素一次只能绑定一个事件处理函数。新绑定的事件处理函数会覆盖旧的事件处理函数

    btn.onclick = function() {
        console.log('123');
    }
    btn.onclick = function() {
        console.log('456');
    }
    

    最后只会弹出 456 ,因为 123 的方法已被覆盖。

该事件的解绑方式:

对象.on事件名字=null;

绑定事件监听函数

为了再解决以上问题,推出了靠事件监听绑定的机制。其原理是,通过事件监听,让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为 注册事件.当用户触发了特定的条件,就会触发回调函数。

btn.addEventListener('click', function(e) {
    let newli = document.createElement('li')
    newli.innerHTML = `我是新来的`
    ul.appendChild(newli)
})

注意:

该事件类型没有 on 前缀。

这个方法的优点:

  1. 该方法同时支持事件处理的捕获和冒泡阶段。事件阶段取决于 addEventListener 最后的参数设置:false (冒泡) 或 true (捕获)。
  2. 在事件处理函数内部,this 关键字引用当前元素。
  3. 事件对象总是可以通过处理函数的第一个参数( e )捕获。即可以做事件委托的操作。
  4. 可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件

这个方法的缺点:

IE 不支持,你必须使用 IEattachEvent 函数替代。

该事件的解绑方式:

解绑事件 对象.removeEventListener(“事件类型”,函数名字,false);

全选操作的基本实现

事件绑定中,全选是一项基本的操作,常用于购物车等案例。

点击全选框让复选框跟着变化

点击全选框让复选框被选中或取消,其实只需要先用一个变量获取全选框的 被选中状态 ,然后遍历所有复选框,让他们的选中状态等于全选框的选中状态即可。代码如下所示。

checkAll.addEventListener('click', function() {
    let status = this.checked
    cks.forEach(ele => {
        ele.checked = status
    })
    all.innerHTML = status ? '取消' : '全选'
})

也可以不用变量来接收全选框的被选中状态,直接 用三元表达式 判断即可,因为全选框的被选中状态返回值是一个布尔值。因此代码可以改为以下形式。

checkAll.addEventListener('click', function() {
    cks.forEach(ele => {
        ele.checked = status
    })
    all.innerHTML = checkAll.checked ? '取消' : '全选'
})

点击全部复选框让全选框被选中

方法一

可以先声明一个变量,初始值设为 true ,默认所有的复选框被选中,然后循环遍历所有的复选框的被选中状态,只要有一个复选框没被选中,直接把变量改为 false 。最后让全选框被选中状态等于这个变量。代码如下所示。

cks.forEach(element => {
    element.addEventListener('click', function() {
        let flag = true
        cks.forEach(ele => {
            if (ele.checked === false) {
                flag = false
            }
        })
        all.innerHTML = flag ? '取消' : '全选'
        checkAll.checked = flag ? true : false
    })
})

方法二

已知有3个复选框,如果有3个复选框被选中,全选框也会进入选中状态。简单来说,如果 复选框被选中的数量等于复选框的总数 ,则说明全部复选框都被选中了。因此可以设置一个变量赋初始值为0,循环遍历所有复选框,只要有一个复选框被选中,则让该变量自增一,最后与复选框的总数做判断。语法代码如下。

cks.forEach(element => {
     element.addEventListener('click', function() {
         // 每点击一次复选框,判断全部的复选框的选中状态。
         let count = 0;
         // 设置一个变量,每次触发点击事件让其值为0。
         cks.forEach(ele => {
             // 如果有一个复选框被选中,则让变量count自增1
             if (ele.checked === true) {
                 count++
             }
         })
         if (count === cks.length) {
             checkAll.checked = true
             all.innerText = '取消'
         } else {
             checkAll.checked = false
             all.innerText = '全选'
         }
     })
 })

js 中的排他思想

JavaScript 中排他思想一般用在 Tab 切换中。排他思想的方法为,干掉所有人,留下我自己。

因此,可以先遍历 Tab 栏中所有元素,去掉所有元素的类名。去掉类名的方法为 事件元素.className='' ,或者 事件元素.classList.remove(类名) 。在不知道该事件源的类名数量情况下建议使用后者。

然后再给触发事件的那个元素添加类名。谁触发函数就给谁加,就用到了 this 指向(谁触发函数 this 就指向谁)以及事件对象 e.target

代码如下所示

lis.forEach(element => {
    element.addEventListener('click', function() { 
        lis.forEach(ele => {
            ele.classList.remove('active')
            this.classList.add('active')
        })
    })
})

又或者可以换种思路,专门找到有类名的那个事件源,去除他的类名,再给触发事件的元素节点添加类名,这样就不需要循环遍历去除所有元素的类名了。代码如下所示。

lis.forEach(element => {
    element.addEventListener('click', function() {
        document.querySelector('li.active').classList.remove('active')
        this.classList.add('active')
})