分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 软件开发

js自定义事件、DOM/伪DOM自定义事件

发布时间:2023-09-06 01:27责任编辑:熊小新关键词:jsDOM

所谓自定义事件,就是有别于有别于带有浏览器特定行为的事件(类似clickmouseoversubmitkeydown等事件),事件名称可以随意定义,可以通过特定的方法进行添加,触发以及删除。

JS自定义事件

先看个简单的事件添加的例子:

element.addEventListener("click", function() { ???alert(1)});

这是个简单的为DOM元素分配事件处理函数的方法(IE 不支持),有别于:

element.onclick = function() { ??alert(1)};

addEventListener()可以为元素分配多个处理函数(而非覆盖),因此,我们可以继续:

element.addEventListener("click", function() { ???alert(2)});

然后,当element被click(点击)的时候,就会连续触弹出“1”和“2”。

抽象→具象→本质→数据层
你有没有觉得这种行为表现有点类似于往长枪里面塞子弹(add),(扣动扳手 – click)发射的时候按照塞进去的顺序依次出来。

这种行为表现为我们实现自定义事件提供了思路:我们可以定义一个数组,当添加事件的时候,我们push进去这个事件处理函数;当我们执行的时候,从头遍历这个数组中的每个事件处理函数,并执行。

当多个事件以及对应数据处理函数添加后,我们最终会得到一个类似下面数据结构的对象:

_listener = { ???"click": [func1, func2], ???"custom": [func3], ???"defined": [func4, func5, func6]}

因此,如果我们脱离DOM, 纯碎在数据层面自定义事件的话,我们只要以构建、遍历和删除_listener对象为目的即可。

函数式实现
还是那句话,循序渐进,我们先看看函数式的实现(只展示骨干代码):

var _listener = {};var addEvent = function(type, fn) { ???// 添加};var fireEvent = function(type) { ???// 触发};var removeEvent = function(type, fn) { ???// 删除};

上面的代码虽然显得比较初级,但是目的亦可实现。例如:

addEvent("alert", function() { ???alert("弹出!");});// 触发自定义alert事件fireEvent("alert");

但是,函数式写法缺点显而易见,过多暴露在外的全局变量(全局变量是魔鬼),方法无级联等。这也是上面懒得显示完整代码的原因,略知即可。

字面量实现
众所周知,减少全局变量的方法之一就是使用全局变量(其他如闭包)。于是,我们稍作调整

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; ???}};

字面量实现虽然减少了全局变量,但是其属性方法等都是暴露而且都是唯一的,一旦某个关键属性(如_listeners)不小心在某事件处reset了下,则整个全局的自定义事件都会崩溃。

因此,我们可以进一步改进,例如,使用原型链继承,让继承的属性(如_listeners)即使出问题也不会影响全局。

原型模式实现

var EventTarget = function() { ???this._listener = {};};EventTarget.prototype = { ???constructor: this, ???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 EventTarget = function() { ???this._listener = {};};EventTarget.prototype = { ???constructor: this, ???// .. 完全就是字面量模式实现脚本};

然后,需要实现自定义事件功能时候,先new构造下:

var myEvents = new EventTarget();var yourEvents = new EventTarget();

这样,即使myEvents的事件容器_listener跛掉,也不会污染yourEvents中的自定义事件(_listener安然无恙)。

DOM自定义事件

我们平常所使用的事件基本都是与DOM元素相关的,例如点击按钮,文本输入等,这些为自带浏览器行为事件,而自定义事件与这些行为无关。例如:

element.addEventListener("alert", function() { ???alert("弹出!");});

这里的alert就属于自定义事件,后面的function就是自定义事件函数。而这个自定义事件是直接绑定在名为element的DOM元素上的,因此,这个称之为自定义DOM事件。

由于浏览器的差异,上面的addEventListener在IE浏览器下混不来(attachEvent代替),

因此,为了便于规模使用,我们需要新的添加事件方法名(合并addEventListenerattachEvent),例如addEvent, 并附带事件触发方法fireEvent, 删除事件方法removeEvent

如何直接在DOM上扩展新的事件处理方法,以及执行自定义的事件呢?

如果不考虑IE6/7浏览器,我们可以直接在DOM上进行方法扩展。例如添加个addEvent方法:

HTMLElement.prototype.addEvent = function(type, fn, capture) { ???var el = this; ???if (window.addEventListener) { ???????el.addEventListener(type, function(e) { ???????????fn.call(el, e); ???????}, capture); ???} else if (window.attachEvent) { ???????el.attachEvent("on" + type, function(e) { ???????????fn.call(el, e); ???????}); ???} };

面代码中的HTMLElement表示HTML元素。以一个<p>标签元素举例,其向上寻找原型对象用过会是这样:HTMLParagraphElement.prototype → HTMLElement.prototype → Element.prototype → Node.prototype → Object.prototype → null。这下您应该知道HTMLElement所处的位置了吧,上述代码HTMLElement直接换成Element也是可以的,但是会让其他元素(例如文本元素)也扩展addEvent方法,有些浪费了。

这样,我们就可以使用扩展的新方法给元素添加事件了,例如一个图片元素:

elImage.addEvent("click", function() { ???alert("我是点击图片之后的弹出!");});

由于IE6, IE7浏览器的DOM水平较低,无法直接进行扩展,因此,原型扩展的方法在这两个浏览器下是行不通的。要想让这两个浏览器也支持addEvent方法,只能是页面载入时候遍历所有DOM,然后每个都直接添加addEvent方法了。

var elAll = document.all, lenAll = elAll.length;for (var iAll=0; iAll<lenAll; iAll+=1) { ???elAll[iAll].addEvent = function(type, fn) { ???????var el = this; ???????el.attachEvent("on" + type, function(e) { ???????????fn.call(el, e); ???????}); ???};}

伪DOM自定义事件

这里的“伪DOM自定义事件”是自己定义的一个名词,用来区分DOM自定义事件的。例如jQuery库,其是基于包装器(一个包含DOM元素的中间层)扩展事件的,既与DOM相关,又不直接是DOM,因此,称之为“伪DOM自定义事件”。

原型以及new函数构造不是本文重点,因此,下面这个仅展示:

 1 var $ = function(el) { 2 ????return new _$(el); ????3 }; 4 var _$ = function(el) { 5 ????this.el = el; 6 }; 7 _$.prototype = { 8 ????constructor: this, 9 ????addEvent: function() {10 ????????// ...11 ????},12 ????fireEvent: function() {13 ????????// ...14 ????},15 ????removeEvent: function() {16 ????????// ...17 ????}18 }

于是我们就可以使用类似$(dom).addEvent()的语法为元素添加事件了(包括不包含浏览器行为的自定义事件)。

自定义事件的添加
如果只考虑事件添加,我们的工作其实很简单,根据支持情况,addEventListenerattachEvent方法分别添加事件(attachEvent方法后添加事件先触发)即可:

addEvent: function(type, fn, capture) { ???var el = this.el; ???if (window.addEventListener) { ???????el.addEventListener(type, fn, capture); ???????????} else if (window.attachEvent) { ???????el.attachEvent("on" + type, fn); ???} ???return this;}

显然,事情不会这么简单,有句古话叫做“上山容易下山难”,自定义事件添加容易,但是如何触发它们呢?——考虑到自定义事件与浏览器行为无关,同时浏览器没有直接的触发事件的方法。

自定义事件的触发
又是不可避免的,由于浏览器兼容性问题,我们要分开说了,针对标准浏览器和IE6/7等考古浏览器。

1. 对于标准浏览器,其提供了可供元素触发的方法:element.dispatchEvent(). 不过,在使用该方法之前,我们还需要做其他两件事,及创建和初始化。因此,总结说来就是:

document.createEvent()event.initEvent()element.dispatchEvent()

举个板栗:

$(dom).addEvent("alert", function() { ???alert("弹弹弹,弹走鱼尾纹~~");});// 创建var evt = document.createEvent("HTMLEvents");// 初始化evt.initEvent("alert", false, false);// 触发, 即弹出文字dom.dispatchEvent(evt);

createEvent()方法返回新创建的Event对象,支持一个参数,表示事件类型,具体见下表:

参数事件接口初始化方法
HTMLEventsHTMLEventinitEvent()
MouseEventsMouseEventinitMouseEvent()
UIEventsUIEventinitUIEvent()

 自定义事件的删除
与触发事件不同,事件删除,各个浏览器都提供了对于的时间删除方法,如removeEventListenerdetachEvent。不过呢,对于IE浏览器,还要多删除一个事件,就是为了实现触发功能额外增加的onpropertychange事件:

dom.detachEvent("onpropertychange", evt);

js自定义事件、DOM/伪DOM自定义事件

原文地址:http://www.cnblogs.com/exhuasted/p/7920325.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved