JS里关于事件的常被考察的知识点:事件派发、事件流、事件广播、原生JS实现事件代理
事件派发dispatchEvent
dispatchEvent 向指定目标事件派发一个事件;一般的事件触发是用户进行某些操作时才会触发,而使用dispatchEvent可以在代码中手动触发事件。
定义事件的目的就是为了执行某一方法,手动触发事件的目的也是执行该事件下的方法。
window.onload = function(){
var btn = document.querySelector('#click');
btn.addEventListener('click', function(e){ //为元素绑定事件监听
}, false);
var event = new Event('click'); //创建一个click事件
btn.dispatchEvent(event); //派发事件
}
事件派发的使用场景
1、触发自定义事件:浏览器自带事件一般由浏览器接收某些操作之后触发,而自定义事件的触发需要使用dispatchEvent来进行手动触发。
2、触发浏览器标准事件:根据需求决定,某些操作如果正好与某个元素事件的触发一致,且该事件很好模拟,就可以触发该事件来达到某些需要的执行结果。
事件派发的作用
1、派发数据:将封闭在模块中的数据传递给另一个封闭模块
2、完成较为复杂的解耦
事件和回调函数区别
1、如果需要完成内容后执行函数,回调函数就只能在一个地方调用;而事件却可以在任何地方接收到数据。
2、回调函数与当前的代码紧密关联,如果修改一个地方,可能会造成错误;但事件机制通过事件的侦听获取,不管发送还是接收、删除,修改后都不会引起任何相关联的错误。
实例代码解析
// 回调函数版
var Method=(function () {
return {
loadImage:function (arr,callback) {
var img=new Image();
img.arr=arr;
img.list=[];
img.num=0;
img.callback=callback;
img.addEventListener("load",this.loadHandler);
img.self=this;
img.src=arr[img.num];
},
loadHandler:function (e) {
this.list.push(this.cloneNode(false));
this.num++;
if(this.num>this.arr.length-1){
this.removeEventListener("load",this.self.loadHandler);
//全部加载完成后通过回调函数将list回调抛出
this.callback(this.list);
return;
}
this.src=this.arr[this.num];
},
}
})();
// 派发事件版
var Method=(function () {
return {
EVENT_ID:"event_id",
loadImage:function (arr) {
var img=new Image();
img.arr=arr;
img.list=[];
img.num=0;
img.addEventListener("load",this.loadHandler);
img.self=this;
img.src=arr[img.num];
},
loadHandler:function (e) {
this.list.push(this.cloneNode(false));
this.num++;
if(this.num>this.arr.length-1){
this.removeEventListener("load",this.self.loadHandler);
//事件派发类型必须与事件侦听接收类型一致,这样事件才会收到
//创建一个事件对象
var evt=new Event(Method.EVENT_ID)
//将list作为事件对象的属性
evt.list=this.list;
//派发事件
document.dispatchEvent(evt);
return;
}
this.src=this.arr[this.num];
},
}
})();
<script>
var arr=["img/a.jpeg","img/b.jpeg","img/c.jpeg","img/d.jpeg","img/e.jpeg"];
Method.loadImage(arr); // 此处Method为上面封装的方法,将arr图片地址数组放入
// 给document添加监听自定义的那个事件
document.addEventListener(Method.EVENT_ID,loadFinishHandler);
function loadFinishHandler(e) {
// 图片加载完就可以获取到图片list,从event对象上拿到list
console.log(e.list);
}
</script>
// 另一个封装js文件里:
(function () {
document.addEventListener(Method.EVENT_ID,loadFinishHandler);
function loadFinishHandler(e) {
//这里仍然能获取到图片list
console.log(e.list);
}
})();
通过使用事件派发机制完成了较为复杂的解耦,代码之间耦合度降低,事件机制通过事件的侦听获取,因此不管发送还是接收,删除修改后都不会引起任何相关联的错误,提供了一种将一个封闭模块中的数据传递给另一个封闭模块方法
JS里面的事件流
DOM2级事件模型中规定了事件流的三个阶段:捕获阶段、目标阶段、冒泡阶段,低版本IE(IE8及以下版本)不支持捕获阶段
捕获事件流:Netscape提出的事件流,即事件由页面元素接收,逐级向下,传播到最具体的元素。
冒泡事件流:IE提出的事件流,即事件由最具体的元素接收,逐级向上,传播到页面。
关于js事件,这里有一篇非常详细的介绍,可以看下:http://www.cnblogs.com/hyaaon/p/4630128.html
IE和W3C不同绑定事件解绑事件的方法有什么区别,参数分别是什么,以及事件对象e有什么区别
绑定事件:
W3C:target.addEventListener(event, listener, useCapture);
event —— 事件类型;
listener —— 事件触发时执行的函数;
useCapture —— 指定事件是否在捕获或冒泡阶段执行,为true时事件句柄在捕获阶段执行,为false(默认false)时,事件句柄在冒泡阶段执行。
btn.addEventListener('click',function(){
//do something...
},false)
对应的事件移除:
removeEventListener(event,function,capture/bubble);
IE:target.attachEvent(type, listener);
type - 字符串,事件名称,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等。
listener —— 实现了 EventListener 接口或者是 JavaScript 中的函数。
btn.attachEvent('onclick',function(){
//do something...
})
对应的事件移除:
detachEvent(event,function);
事件的委托(代理 Delegated Events)的原理以及优缺点
委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。这是靠事件的冒泡机制来实现的,
优点是:
(1)可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
(2)可以实现当新增子对象时无需再次对其绑定事件,对于动态内容部分尤为合适
缺点是:
事件代理的应用常用应该仅限于上述需求下,如果把所有事件都用代理就可能会出现事件误判,即本不应用触发事件的被绑上了事件。
var toolbar = document.querySelector(".toolbar");
toolbar.addEventListener("click", function(e) {
var button = e.target;
if(!button.classList.contains("active"))
button.classList.add("active");
else
button.classList.remove("active");
});
手写原生js实现事件代理,并要求兼容浏览器
// 简单的事件委托
function delegateEvent(interfaceEle, selector, type, fn) {
if(interfaceEle.addEventListener){
interfaceEle.addEventListener(type, eventfn);
}else{
interfaceEle.attachEvent("on"+type, eventfn);
}
function eventfn(e){
var e = e || window.event;
var target = e.target || e.srcElement;
if (matchSelector(target, selector)) {
if(fn) {
fn.call(target, e);
}
}
}
}
/**
* only support #id, tagName, .className
* and it's simple single, no combination
*/
function matchSelector(ele, selector) {
// if use id
if (selector.charAt(0) === "#") {
return ele.id === selector.slice(1);
}
// if use class
if (selector.charAt(0) === ".") {
return (" " + ele.className + " ").indexOf(" " + selector.slice(1) + " ") != -1;
}
// if use tagName
return ele.tagName.toLowerCase() === selector.toLowerCase();
}
//调用
var odiv = document.getElementById("oDiv");
delegateEvent(odiv,"a","click",function(){
alert("1");
})