JavaScript 自定义事件(一)

所谓自定义事件,就是有别于带有浏览器特定行为的事件(鼠标事件,键盘事件,html事件等),事件名称可以自定义,可以通过特定的方法进行添加,触发以及删除。自定义事件相当于是 观察者模式 ,可以把复杂逻辑解耦,代码可以写的很清晰,而且很容易复用。

一.JS事件应用初印象

我觉得更为准确的说,下面列举的是Dom事件的应用。我们在这里可以通过了解JS关于Dom事件的绑定,触发,解绑来一步一步书写我们自己的JS自定义事件。关于自定义事件,我决定分两步讲解。第一步,也就是这篇文章将要提到的循序渐进的一步一步完善自定义JS事件。第二步,我们封装一个与Dom元素有关的自定义事件函数。

// 事件绑定
function addEvent(element, eType, handler, bol) {
    if(element.addEventListener){           //如果支持addEventListener
        element.addEventListener(eType, handler bol);
    }else if(element.attachEvent){          //如果支持attachEvent
        element.attachEvent("on"+eType, handler);
    }else{                                  //否则使用兼容的onclick绑定
        element["on"+eType] = handle;
    }
}
// 事件解绑
function removeEvent(element, eType, handler, bol) {
    if(element.addEventListener){
        element.removeEventListener(eType, handler, bol);
    }else if(element.attachEvent){
        element.detachEvent("on"+eType, handler);
    }else{
        element["on"+eType] = null;
    }
}
//实例应用
var patty=document.getElementById("patty");
var sayHello=function (){
   alert("Hello!!!");
}
addEvent(patty,click,sayHello,false); //添加点击事件

这里我们关注下click事件的实现。我们可以很清楚的将其分成三部分:添加,触发,删除。
同时,我们必须考虑一个问题:事件和其处理函数应该怎样存储。事件分为很多种,click, mouseover, submit, keydown等等,每一种事件下又可以添加处理函数。这种一对多的映射关系,我们可以很自然想到用下面这样数据结构来存储事件。

_listener = {
"click": [func1, func2],
"custom": [func3],
...
}

二.自定义我们自己的JS事件

1.函数式实现
var _listener = {};   //存储事件和其处理函数
var addEvent = function(type, fn) {
      //添加
};
var fireEvent = function(type) {
     //触发
};
var removeEvent = function(type, fn) {
     //删除
};

//添加eat事件
addEvent("eat",function(){
  alert("eat an apple!");
})
// 触发自定义eat事件
fireEvent(eat);

我们没有详细展示函数式实现的代码,是因为这种写法较为基础且过多地暴露全局变量,我们稍微了解即可,具体函数地实现,我们在下面的方式会实现。

2.用字面量方式实现
var Event = {
    _listeners: {},    
    // 添加
    addEvent: function(type, fn) {
        if (typeof this._listeners[type] === "undefined") {
            this._listeners[type] = [];
        }
        if (typeof fn === "function") {
            this._listeners[type].push(fn);
        }    
        return this;
    },
    // 触发
    fireEvent: function(type) {
        var arrayEvent = this._listeners[type];
        if (arrayEvent instanceof Array) {
            for (var i=0, length=arrayEvent.length; i<length; i+=1) {
                if (typeof arrayEvent[i] === "function") {
                    arrayEvent[i]({ type: type });    
                }
            }
        }    
        return this;
    },
    // 删除
    removeEvent: function(type, fn) {
        var arrayEvent = this._listeners[type];
        if (typeof type === "string" && arrayEvent instanceof Array) {
            if (typeof fn === "function") {
                // 清除当前type类型事件下对应fn方法
                for (var i=0, length=arrayEvent.length; i<length; i+=1){
                    if (arrayEvent[i] === fn){
                        this._listeners[type].splice(i, 1);
                        break;
                    }
                }
            } else {
                // 如果仅仅参数type, 或参数fn邪魔外道,则所有type类型事件清除
                delete this._listeners[type];
            }
        }
        return this;
    }
};

//添加eat事件
Event.addEvent("eat",function(){
  alert("eat an apple!");
})
// 触发自定义eat事件
Event.fireEvent(eat);

字面量实现虽然减少了全局变量,但是其属性方法等都是暴露而且都是唯一的,一旦某个关键属性(如_listeners)不小心在某事件处reset了,则整个全局的自定义事件都会崩溃。因此,我们可以进一步改进,例如,使用原型链继承,让继承的属性(如_listeners)即使出问题也不会影响全局。

3.原型模式实现
var EventTarget = function() {
    this._listener = {};
};

EventTarget.prototype = {
    constructor:EventTarget,
    addEvent: function(type, fn) {
        if (typeof type === "string" && typeof fn === "function") {
            if (typeof this._listener[type] === "undefined") {
                this._listener[type] = [fn];
            } else {
                this._listener[type].push(fn);    
            }
        }
        return this;
    },
    addEvents: function(obj) {
        obj = typeof obj === "object"? obj : {};
        var type;
        for (type in obj) {
            if ( type && typeof obj[type] === "function") {
                this.addEvent(type, obj[type]);    
            }
        }
        return this;
    },
    fireEvent: function(type) {
        if (type && this._listener[type]) {
            var events = {
                type: type,
                target: this    
            };
            
            for (var length = this._listener[type].length, start=0; start<length; start+=1) {
                this._listener[type][start].call(this, events);
            }
        }
        return this;
    },
    fireEvents: function(array) {
        if (array instanceof Array) {
            for (var i=0, length = array.length; i<length; i+=1) {
                this.fireEvent(array[i]);
            }
        }
        return this;
    },
    removeEvent: function(type, key) {
        var listeners = this._listener[type];
        if (listeners instanceof Array) {
            if (typeof key === "function") {
                for (var i=0, length=listeners.length; i<length; i+=1){
                    if (listeners[i] === key){
                        listeners.splice(i, 1);
                        break;
                    }
                }
            } else if (key instanceof Array) {
                for (var lis=0, lenkey = key.length; lis<lenkey; lis+=1) {
                    this.removeEvent(type, key[lenkey]);
                }
            } else {
                delete this._listener[type];
            }
        }
        return this;
    },
    removeEvents: function(params) {
        if (params instanceof Array) {
            for (var i=0, length = params.length; i<length; i+=1) {
                this.removeEvent(params[i]);
            }    
        } else if (typeof params === "object") {
            for (var type in params) {
                this.removeEvent(type, params[type]);    
            }
        }
        return this;    
    }
};

//使用
var event1=new EventTarget();
var event2=new EventTarget();

这样我们发现,event1和event2在共享方法的同时,又有自己的_listener 属性,彼此之间不会污染。

我相信大家通过上面的了解,对JS自定义事件一定有更深的理解了,OK这次就先讲这么多,谢谢观看!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,431评论 25 707
  • 在线阅读 http://interview.poetries.top[http://interview.poetr...
    程序员poetry阅读 114,237评论 24 450
  • ——观越剧《梁祝》,痛难自抑,故试谱词为曲以歌,权且记下,终不得古体风华,来日再试以旧式罢。 雨清宵夜半魂梦仍回草...
    吃夏天的薄荷阅读 913评论 0 0
  • 一个幽静的山洞之中,一袭白色衣袍中年男人正盘膝而坐,闭目养神,国字脸,浓浓的眉毛尾端有些稍白,看上去年纪约...
    微笑更灿烂阅读 208评论 0 0
  • 人这一辈子,最大的区别是什么?活不明白的人,都在活给别人看,心累无比。而活明白的人,都在为自己而活,潇洒自如! —...
    中泰医疗黄卫峰阅读 735评论 0 1