js⾃定义事件、DOM伪DOM⾃定义事件
所谓⾃定义事件,就是有别于有别于带有浏览器特定⾏为的事件(类似click, mouseover, submit, keydown等事件),事件名称可以随意定义,可以通过特定的⽅法进⾏添加,触发以及删除。
JS⾃定义事件
先看个简单的事件添加的例⼦:
element.addEventListener("click", function() {
alert(1)
});
这是个简单的为DOM元素分配事件处理函数的⽅法(IE 不⽀持),有别于:
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) {
}
} 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) {
}
} else if (typeof params === "object") {
for (var type in params) {
}
}
return this;
}
};
其实上⾯代码跟字⾯量⽅法相⽐,就是增加了下⾯点东西:
var EventTarget = function() {
this._listener = {};
};
EventTarget.prototype = {
constructor: this,
// .. 完全就是字⾯量模式实现脚本
};
js合并两个数组
然后,需要实现⾃定义事件功能时候,先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代替),
因此,为了便于规模使⽤,我们需要新的添加事件⽅法名(合并addEventListener和attachEvent),例如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函数构造不是本⽂重点,因此,下⾯这个仅展⽰:
1var $ = function(el) {
2return new _$(el);
3 };
4var _$ = function(el) {
5this.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()的语法为元素添加事件了(包括不包含浏览器⾏为的⾃定义事件)。
⾃定义事件的添加
如果只考虑事件添加,我们的⼯作其实很简单,根据⽀持情况,addEventListener与attachEvent⽅法分别添加事件(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(). 不过,在使⽤该⽅法之前,我们还需要做其他两件事,及创建和初始化。因此,总结说来就是:
event.initEvent()
element.dispatchEvent()
举个板栗:
$(dom).addEvent("alert", function() {
alert("弹弹弹,弹⾛鱼尾纹~~");
});
// 创建
var evt = ateEvent("HTMLEvents");
// 初始化
evt.initEvent("alert", false, false);
// 触发, 即弹出⽂字
dom.dispatchEvent(evt);
createEvent()⽅法返回新创建的Event对象,⽀持⼀个参数,表⽰事件类型,具体见下表:
参数事件接⼝初始化⽅法
HTMLEvents HTMLEvent initEvent()
MouseEvents MouseEvent initMouseEvent()
UIEvents UIEvent initUIEvent()
⾃定义事件的删除
与触发事件不同,事件删除,各个浏览器都提供了对于的时间删除⽅法,如removeEventListener和detachEvent。不过呢,对于IE浏览器,还要多删除⼀个事件,就是为了实现触发功能额外增加的onpropertychange事件:
dom.detachEvent("onpropertychange", evt);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。