js 事件【DOM0级、DOM2级】的绑定和移除(及 事件函数是 具名函数 和 匿名函数的区别)

一、事件的绑定

  1、js原生 事件程序 的绑定 方法:https://www.cnblogs.com/ypppt/p/12943349.html

    a、元素 属性 中绑定 事件(即 DOM0 级 事件处理程序):【这种事件的绑定 只能是 DOM 或 Windows 对象 可以使用】

<div id="btn" onclick="clickone()"></div> //直接在DOM里绑定事件
<script>
  function clickone(){ alert("hello"); }
</script>

//

<div id="btn"></div>
<script>
 document.getElementById("btn").onclick = function(){     
      alert("hello"); 
   } //脚本里面绑定
</script>

    b、使用事件 监听器  addeventlistener(即 DOM2 级 事件处理程序):【js中所有的 对象 都可以使用这个监听器 绑定事件,可以是任意的自定义事件

<div id="btn"></div>
<script>
 document.getElementById("btn").addEventListener("click",clickone,false); //通过侦听事件处理相应的函数,

  2、DOM0 级 事件处理程序 DOM2 级 事件处理程序 有什么区别呢:https://blog.csdn.net/gary_888/article/details/94647756

    a、 DOM0 级 事件程序程序,会在 DOM2 级 事件处理程序 之前执行。

// 先执行 DOM0 级的事件程序 test,再执行 DOM2 级 事件程序 showMsg
<input id="btn2" type="button" onclick="test();" />
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showMsg, false); //鼠标单击的时候调用showMes这个函数  
function showMsg() {
    alert("事件监听");
}

    b、DOM0 级 事件处理程序,移除绑定只要设置 为 null 就可以;而 DOM2 级 事件处理程序必须 是具名函数才可以移除。

var btn1 = document.getElementById('btn1');
btn1.onclick = function () {
    alert('abc');
}; 
btn1.onclick = null//去掉绑定的事件

    c、DOM0 级 和 DOM2 级 事件处理程序 都 是 在 其依附的 元素 的作用域中运行; 换句话说,事件处理程序中的 this 指向的 是当前 元素

    d、注意:事件移除,移除的是 事件程序,而不是 事件属性【DOM0、DOM都是】。属性 的 值 为 空 了,这个属性自然就无效了,也不占用内存。

    e、DOM0 级 事件处理程序 是通过 属性 赋值,实现绑定的事件处理程序。js 对象的 一个属性只能有一个值,即 DOM 只能绑定一个事件程序程序;

       DOM2 级 事件处理程序 是通过 方法 绑定的 事件处理程序。addeventlistener 内部做了处理,可以添加多个事件函数。所以DOM2 级 移除事件必须要 明确移除的的事件函数。

  总结:DOM0 级 事件处理程序 通过 属性 绑定 事件函数,直接设置 这个 事件属性 指向 为 null,DOM 定义的事件也就移除了;

     但是 DOM2 级 是通过 addeventlistener 函数绑定的事件【看下面的猜测】,没有给出 对象下面 指向事件函数 的 属性,无法类似 DOM0级那样 直接设置 事件属性为null 那样 移除事件程序。

     但是 提供是 removeEventListener 函数,在内部去清除 对应的 事件处理程序。

  【猜测】对 addeventlistener 绑定事件的 猜测:

      DOM2 级 没有 提供指向 事件处理程序的 属性,但是内部肯定存在这样一个 属性,只是没有暴露这个属性而已。这个属性的 值 应该是 包含 addeventlistener 绑定的事件函数的 集合【可以假设是一个数组】。

      而 removeEventListener 函数,则是把 这个 属性的 值【假设是数组】中 需要移除 的那一项去除。所以 removeEventListener 必须传入 事件函数名。

二、事件的移除【下面说的都是DOM2 级 事件处理程序 进行解绑】

  参考:https://blog.csdn.net/wkyseo/article/details/51352229

  1、分析 例子中 的现象。个人理解 js 的绑定事件时,这个对象绑定 的 是 这个事件函数 本身,而不是 指向 的标识符(即函数名)。所以 标识符 指向 的函数 虽然 改变了,但是 函数 绑定的那个 函数 本身还是 存在的。

     总结:函数 绑定 时的 事件函数(即 对象 指向),就不会改变。即使  这个 绑定 的 事件函数 名  之后甚至 为 null了,但是这个事件函数 还是指向之前  函数对象 的内存。

       换句话说,就是 事件函数 绑定的 是这个函数 的指针,而不是 标识符。后面不管标识符 指向 的指针 怎么变,这个事件函数 绑定 的指针是不变的。

      这点 和 我们平时 的 函数 调用不同,要注意点。平时函数 的调用,是 调用时,函数名 指向的最后 那个指针对应的函数。

       (function(w) {
            //第一次定义需要执行的代码块
            var fn = function() {
                console.log(1);
            };
            var btn = document.querySelector('.button');
            btn.addEventListener('click', fn, false);  // 这里 在绑定 事件 时,事件函数 绑定 的 对象就不会变。即 绑定的 函数对象 本身的指针。而不是 标识符。
            btn.click();

            //覆盖fn的引用,第二次以后需要执行的代码
            fn = function() {
                console.log(2);
            };
        })(window);

  2、事件 解绑, 为什么一定要是 具名函数(这里说的是 addEventListener 绑定的事件):

     个人理解: 如 上面事件 的绑定 ,猜测的那样。addEventListener 绑定的事件,移除的是对应 的 事件处理 函数,必须通过 函数名,移除这个函数。

           如果在 非严格模式下,还可以使用 arguments.callee【所有函数 内部 指向函数本身 的标志】 在 事件函数内部在解除绑定,如:

        let dom = document.getElementById("btn")
        dom.addEventListener("click",function(){
            console.log('test');
            dom.removeEventListener('click', arguments.callee)  // arguments.callee 指向 事件处理函数
        },false);

        严格模式下 必须是 具名的函数【严格模式下,arguments.callee 已经禁用了】,如下:

        let dom = document.getElementById("btn")
        dom.addEventListener("click",function a(){
            console.log('test');
            dom.removeEventListener('click', a)
        },false);

  3、绑定的事件 一定需要 手动去 解绑吗? 什么 情况下必须要手动解绑?

    a、H5 中 DOM 的事件一般不需要 解绑,除非这个 DOM 事件 要求 只能触发一次。H5 中 DOM 从DOM树上移除了,其 绑定 的事件自动 会 被 回收的

    b、js中的事件,其实只要把 绑定事件 的对象 销毁了,其绑定的事件函数就会被回收。但是有的对象 js 根本就无法销毁,js只是 对 那个对象做了引用。

       如:setInterval 函数【虽然不是 事件,但是道理是一样的】,他是挂在 window下的对象。我想如果能够把window都给销毁了,那他的定时器只能也能失效。

          但是我们没法销毁 window。

       再比如:nodejs 中的 数据库,js只是对数据库的一个引用,js 不能直接 销毁 数据库。数据库对象绑定的事件,只能通过 事件 解绑的API 实现。 

    c、其实在 一般页面中不会有这个困惑,但是在单页面中 这个 问题就会 比较 突出https://www.cnblogs.com/zzbp/p/5834110.html

       单页面 应用 中 路由切换页面,是一个页面的组件虽然 销毁 清除了,但是里面 有的 对象不一定清除了,比如 setInterval 函数,还是会继续执行。

       因为 不是他们 js 上的对象,只是js 对 环境 资源 的引用,挂载的对象无法消除,绑定的事件自然还是会继续作用的。要解除这种 事件处理程序 ,

       只能通过提供的 API 去 移除事件的绑定了。【这就是 决定 要不要 显式去 移除绑定的事件】

    总结:1、js 中 事件函数 内存的清除 有两种,一种是 绑定的 对象清除了,其事件函数自然就释放了【对象不在了】;

               第二种 是 通过 提供的 API,清除掉对应的事件处理程序【对象还在,相应的事件函数不在了】。   

       2、一般 H5 自带 的哪些事件,我们很清楚 需不需要 手动解绑。如果是 第三方 包 创建的 对象 上的 事件,这个时候就要注意 可能需要我们手动 移除的。

         如 腾讯云的  TRTC 音视频 和 TIM 通讯等。

    亲历问题:electron+vue 项目中,使用 腾讯云的 TIM 通讯 模块 的包,项目中引入这个包,等于是对 环境中 TIM 资源的使用。路由切换,vue组件虽然销毁了,

         但是里面引用的 TIM 并没有销毁,所以绑定的事件还是存在的。这种情况就需要手动解绑事件。

            TIM 绑定了 事件,要清除事件,只能显式 的移除事件绑定。   

  4、原生实现 只 绑定 一次:

el.addEventListener('click', function a(){
  //do sth
  this.removeEventListener('click', a)
})

  5、

待补充。。。

原文地址:https://www.cnblogs.com/wfblog/p/14218877.html