ITPub博客

首页 > IT职业 > IT职场 > 如何优雅的封装一个DOM事件库

如何优雅的封装一个DOM事件库

IT职场 作者:ou_159 时间:2021-09-09 09:44:48 0 删除 编辑

1、DOM0级事件和DOM2级事件

DOM 0级事件是元素内的一个私有属性:div. = function () {},对一个私有属性赋值(在该事件上绑定一个方法)。由此可知DOM 0级事件只能给元素的某一个行为绑定一次方法,第二次绑定会把前面的覆盖掉。

DOM 2级事件是让DOM元素通过原型链一直找到EventTarget这个内置类原型上的addEventListener方法来实现的。

DOM 2可以给某一个元素的同一个行为绑定多个不同的方法

图片描述

//实例 1obj.addEventListener(事件类型 , 处理函数 , false)//IE9以下不兼容,可以为一个事件绑定多个处理程序,并且按照绑定时的顺序去执行//实例2div.addEventListener('click' , function f() {} , false); //1div.addEventListener('click' , function f() {} , false); //2//事件1和事件2虽然执行的函数一样,但是函数f()的地址不一样,所以是2个处理函数,执行2次//实例3function f() {};
div.addEventListener('click' , f , false); //1div.addEventListener('click' , f , false); //2//事件1和事件2执行的函数都是f(),但因为地址一样,所以只执行一次。即某一个元素的同一个行为只能绑定一次相同的方法

图片描述

1.1、DOMContentLoaded和loaded

DOM 2还提供了DOM 0中没有的行为类型 -> DOMContentLoaded:当页面中的DOM结构(HTML结构)加载完成触发的行为。而事件则是当页面中的所有资源全部加载完成(图片、html结构、音视频...)才会被执行。

jQuery中的$(document).ready(function () {}),等价于$(function () {}),事件原理就是DOM2中新增的DOMContentLoaded事件。

1.2、事件的移除

DOM 0级事件的移除:div. = null;

DOM 2级事件的移除:

function fn() {};
div.addEventListener('click',fn,false);
div.removeEventListener('click',fn,false);

1.3、DOM2事件机制

  • 只能给某个元素的同一个行为绑定多个"不同"的方法

  • 当行为触发,会按照绑定的先后顺序把绑定的方法执行

  • 执行的方法中的this是当前绑定事件的元素本身

1.4、IE6-8下的事件机制

在IE6-8浏览器中,不支持addEventListener/removeEventLiatener,如果想实现DOM 2事件绑定,只能用attachEvent(),移除用detachEvent()。

 obj.attachEvent('on'+type , func);只能在冒泡阶段发生,一个事件同样可以绑定多个处理函数。与obj.addEventListener('type' , func , false)不一样的是,即使函数的地址是一样的,绑定多少次就执行多少次。即同一个函数可以绑定多次

与标准浏览器的事件池机制对比:

  • this问题:IE6-8中当方法执行的时候,方法中的this不是当前元素,而指的是window

  • 重复问题:可以给同一个元素的同一个行为绑定多个相同的方法

  • 顺序问题:执行的时候顺序是混乱的,标准浏览器是按照绑定顺序依次执行

2、处理this问题

图片描述

/*
 *    bind: 处理DOM2级事件绑定的兼容性问题
 *    @parameter:
 *    curEle: 要绑定事件的元素
 *    eventType: 要绑定的事件类型('click','mouseover'...)
 *    eventFn: 要绑定的方法 */
 var tempFn = {}; function bind(curEle,eventType,eventFn){    if ("addEventListener" in document) {//标准浏览器
        curEle.addEventListener(eventType,eventFn,false);        return ;
    }    var tempFn[eventFn] = function () {
        eventFn.call(curEle);
    };
    curEle.attachEvent("on" + eventType,tempFn);
 } 
 function unbind(curEle,eventType,eventFn) {    if ("removeEventListener" in document) {
        curEle.removeEventListener(eventType,eventFn,false);        return ;
    }
    curEle.detachEvent("on" + eventType,tempFn[eventFn]);
 }//分析
 // 1、知若想改变IE下事件执行函数的this的指向,可以在函数执行的时候改变this,即用函数eventFn.call('curEle'),这样虽然解决了this指向的问题,
   但又抛出了一个新的问题:即不知道该如何移除该事件函数,因为绑定的是一个匿名函数,而匿名函数的地址我们是无法知道的。   所以要先把匿名函数定义时的地址赋值给一个变量temp    var tempFn = function () {
        eventFn.call('curEle');
    };//2、为什么要把tempFn设置成一个全局变量
    若tempFn不是一个全局变量,而是写在函数内部的私有变量,而私有变量只能在函数内部进行访问,
    所以我们在bind()函数里的tempFn在unbind()函数里是不能访问的,因此也就不能移除该事件函数。所以若想移除该事件函数,tempFn就必须是全局变量

图片描述

拓展:我们知道:写在函数内部的变量是私有变量,一个函数的私有变量只能在函数的内部进行访问。

  • 若在一个函数里要用到另一个函数里的变量,可以把该变量设置成全局的变量,这样两个函数都可以访问到。(这可能会造成全局污染)

  • 若几个函数的作用是为同一个/同一类元素提供方法去使用,且不同方法中要用到其它方法里的变量等,那么可以用该元素的自定义变量来存储这些变量,就可以实现在不同方法中的访问。(不会造成全局污染)

上面的代码除了全局变量可能造成污染外。还有一种不得不考虑的情况就是:当为不同的事件绑定方法时,不同的事件可能执行相同的方法(如mouseover 和 click 都执行fn1方法时),如果仍然将这些方法存储在一起,那么移除某一类事件的方法时就可能出错,因此我们需要为不同的事件创建不同的数组来存储绑定在其上的方法。

所以我们需要对代码进行进一步的优化。

图片描述

function bind(curEle,eventType,eventFn){        ...        var tempFn = function () {
            eventFn.call('curEle');
        };
        tempFn.photo = eventFn;//给传入的每一个函数做一个唯一标识
        //首先判断该自定义属性之前是否存在,不存在的话创建一个,由于要存储多个方法,所以我们让其值是一个数组
        //为什么要对不同的事件类型创建不同的数组呢,因为不同的事件可能执行相同的方法。如mouseover 和 click 都执行fn1方法时,移除的时候就可能出错
        if (!curEle['bindFn' + eventType]) {
            curEle['bindFn' + eventType] = [];
        }
        curEle['bindFn' + eventType].push(tempFn);
        curEle.attachEvent("on" + eventType,tempFn);
    } 
    function unbind(curEle,eventType,eventFn) {        ...        var arr = curEle['bindFn' + eventType];        for (var i = 0; i 

图片描述

3、处理重复问题

图片描述

function bind(curEle,eventType,eventFn){        if ("addEventListener" in document) {            //省略代码        }        //省略代码
        //处理重复问题:如果每一次往自定义属性添加方法前,看一下是否已经有了,有的话就不用重复添加,同理,也就不用往事件池里存储了
        var arr = curEle['bindFn' + eventType];        for (var i = 0; i 

图片描述

4、处理顺序问题

我们知道在IE6-8下,事件的执行顺序是无序的,这是由浏览器的事件池机制所决定的。所以要改善这个问题,我们模仿标准浏览器的事件执行顺序,可以自己写一个事件池来使方法的执行顺序有序执行。听起来有点绕,我们来看一下具体的实现就清楚了。

图片描述

  //创建自己的事件池,并把需要给当前元素绑定的方法依次增加到事件池中    function on(curEle,eventType,eventFn) {        if (!curEle['myEvent' + eventType]) {
            curEle['myEvent' + eventType] = [];
        }        var arr = curEle['myEvent' + eventType];        for (var i = 0; i 

图片描述

5、一个完整的DOM库

以上就是对封装整个DOM库的思考,可见分析的整个过程是多么的煎熬。然而整个的DOM库封装后,代码却少的可怜。我们一起来看一下。

图片描述

//绑定事件
function on(ele,type,fn) {    if(ele.addEventListener) {
        ele.addEventListener(type,fn,false);
    } else{        if (!ele['myEvent' + type]) {
            ele['myEvent' + type] = [];
            ele.attachEvent('on' + type,function(){//在这里绑定run方法                run.call(ele);
            })
        }
        let arr = ele['myEvent' + type];        for(let i = 0; i 

图片描述

原文出处:https://www.cnblogs.com/yuliangbin/p/9460917.html

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/1192/viewspace-2811785/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: linux下安装mysql
请登录后发表评论 登录
全部评论

注册时间:2019-04-15

  • 博文量
    117
  • 访问量
    1299