关于面试中的原生js实现事件代理和事件模型和事件广播的学习

来源:互联网 发布:环境保护实用数据手册 编辑:程序博客网 时间:2024/05/18 15:56

这也是看了网上一篇面试题所以想整理下,顺便对自己学习过的东西重新理解巩固下

在看事件代理之前,我们先来重温下事件,在与浏览器进行交互的时候浏览器会触发各种事件,比如当我们打开某一个网页的时候,浏览器加载完成了这个网页,就会触发一个load事件,当我们点击页面中的某一个地方,浏览器就会在那个地方触发一个click事件,这样我们可以编写js,通过监听某一事件,来实现某些功能扩展,例如监听load事件,显示欢迎信息,那么当浏览器加载完一个网页之后,就会显示欢迎信息。

现在我们再来说下js事件的三阶段,分别为捕获,目标,冒泡,什么意思呢?先举个例子(虽然盗图可耻,但是这张图真的简单明了)

比如说我现在点击一个button


问题来了,我明明只是点击了button,那其余的这些是什么?这就是我们前面提到的三个阶段:

捕获阶段:从最上层元素,知道最下层,你点击的那个target元素,路过的所有节点都可以捕捉到这事件

         目标阶段:如果给事件成功的到达了target元素,它会进行事件处理

        冒泡阶段:事件从最下层向上传递,依次出发父元素的该事件处理函数

而这里为什么先是父节点捕获了然后往下然后又回到父节点呢?

这就源于之前的浏览器大战,我们都知道现在的浏览器有几大巨头,而IE在曾经是绝对的老大,于是IE觉得事件触发就应该是从下层向上传递,就类似冒泡那种,

但是另一种浏览器不这么认为,它觉得事件捕获应该是父节点先触发,最后才是目标节点触发,当打的不可开交的时候,DOM2站出来了。为了世界的和平,

于是DOM2级事件规定事件流有三个阶段:分别是捕获,目标,冒泡(注:IE8以及更早版本不支持DOM事件流)。就是如上图所示,走一个来回。然后根据我们需求,规定我们的事件监听是在捕获阶段还是冒泡阶段。

事件监听

上面提到事件监听,那我们再来说下事件监听的几种方法吧(会的童鞋可以自动略过这一节。。。)

1.在html中直接写

<button onclick="alert('你点击了这个按钮');">点击这个按钮</button>

这种很明显缺点是代码耦合,不便于维护和开发

2.DOM绑定

var element=document.getElementById('jianting');element.onclick = function(event){    alert('你点击了这个按钮');};
这种比较常见,比较简单易懂,而且兼容性较好,但是也有缺陷,只能实现一个绑定,也就是说我们再为element绑定第二个click事件时候,会覆盖掉之前的click事件

3.使用事件监听函数

标准的事件监听函数如下:

element.addEventListener(<event-name>, <callback>, <use-capture>);
表示在element这个对象上面添加一个事件监听器,当监听到有<event-name>(如click)事件发生时候,调用callback这个回调函数,至于<use-capture>这个参数, 表示事件监听是在‘捕获’阶段监听(设置为true)还是在冒泡阶段中监听(设置为false)。
但是。。。。这个有一种浏览器不支持这个写法。。。。你应该猜到了吧,这就是某些IE,那我们应该怎么写呢

element.attachEvent("on" + type, handler)
这个你可能奇怪,怎么只有两个参数?对,因为IE只支持冒泡阶段嘛,所以没有第三个参数
所以针对不同的浏览器,为了让我们的代码有兼容性,所以我们可以这样写
              if (element.addEventListener) {                      element.addEventListener(type, handler, false);              } else if (element.attachEvent) {                    element.attachEvent("on" + type, handler);           } else {                     element["on" + type] = handler;              }
其实IE与其他浏览器对事件的解读不止是添加监听方法不同,还有其他的比如事件的event对象也不同
当一个事件被触发时候,会创建一个事件对象(event object),这个对象里面包含了一些属性和方法,事件对象会作为第一个参数传递给我们的回调函数,下面我们打印下这个属性来看下,下面这段代码在chrome下做的测试
<<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Event Bubbling</title>  <script>window.onload=function(){    var btn = document.getElementById('clickMe');    btn.addEventListener('click', function(event) {        console.log(event);    }, false);};  </script></head><body>  <button id="clickMe">Click Me</button></body></html>

将看到如下属性:

介绍下常见属性(从别处拷贝而来的)

type(string):事件名称

target(node):事件要触发的目标节点。

bubbles (boolean):表明该事件是否是在冒泡阶段触发的。

preventDefault (function):这个方法可以禁止一切默认的行为,例如点击 a 标签时,会打开一个新页面,如果为 a 标签监听事件 click 同时调用该方法,则不会打开新页面。

stopPropagation (function):停止冒泡,上面有提到,不再赘述。

stopImmediatePropagation (function):与 stopPropagation 类似,就是阻止触发其他监听函数。但是与 stopPropagation 不同的是,它更加 “强力”,阻止除了目标之外的事件触发,甚至阻止针对同一个目标节点的相同事件

cancelable (boolean):这个属性表明该事件是否可以通过调用 event.preventDefault 方法来禁用默认行为。

eventPhase (number):这个属性的数字表示当前事件触发在什么阶段。none:0;捕获:1;目标:2;冒泡:3。

pageX 和 pageY (number):这两个属性表示触发事件时,鼠标相对于页面的坐标。

isTrusted (boolean):表明该事件是浏览器触发(用户真实操作触发),还是 JavaScript 代码触发的。

别忘了IE的特立独行啊,IE的某些版本对事件属性的解读的差异如下:只是列举了下基本的

IE往回调函数中传递的事件对象和标准有差异,你需要使用window.event来获取事件对象,所以为了代码的兼容性你得这么写:

event=event||window.event

还有个几个属性

element=event.srcElement||event.target

事件的传播的阻止

w3c中,使用stopPropagation()方法,IE设置为cancelBubble=true;

阻止事件的默认行为:

w3c使用preventDefault()方法

IE设置window.event.returnValue=false;

哎呀 。。。写着写着写偏啦

回归正题

事件代理也叫做事件委托

现在有个需求,就是有个li的列表,现在我要对每个li添加以一个click监听函数,弹出所点击的li的id,我们可能一下子就想到了在每个li上写个addEventListener,如果你有100个呢?你就要遍历100遍li,这个工作量其实有点大,有没有别的办法呢,前面已经说了target就是你点击的那个对象,但是事件是可以冒泡的啊,也就是说不管你点击哪个li,这个事件都会一层层的往上触发,知道最上面的一层,但是有个target的属性是不会变的,讲到了这里,你是不是有自己的小想法了呢

所谓的事件委托或者事件代理技术能让你避免对特定的每个节点添加事件监听器,相反事件监听器是被添加到它们的元素上的,利用冒泡原理,把事件加到父级上,触发执行效果

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Event Bubbling</title>    <script>window.onload=function(){   function getEventTarget(e) {  e = e || window.event;  return e.target || e.srcElement;}var parentlist=document.getElementById("parent-list");// 获取父节点,并为它添加一个click事件if(document.addEventListener){parentlist.addEventListener("click",function(e) {  // 检查事件源e.targe是否为Li    var target = getEventTarget(e);  alert(target.id);});}else if(document.attachEvent){parentlist.attachEvent("onclick",function(e) {  // 检查事件源e.targe是否为Li    var target = getEventTarget(e);  alert(target.id);});}else{parentlist.onclick=function(){  // 检查事件源e.targe是否为Li    var target = getEventTarget(e);  alert(target.id);}}};  </script></head><body><ul id="parent-list"><li id="post-1">Item 1</li><li id="post-2">Item 2</li><liid="post-3">Item 3</li><liid="post-4">Item 4</li><liid="post-5">Item 5</li><li id="post-6">Item 6</li></ul></body></html>
普通写法:

var tags=document.getElementsBytag('li');for(var i=0;i<tags.length,i++){var li=tags[i];li.onclick=function(e){e.stopPropagation();alert(this.id); };}
事件代理的优缺点:

通过上面的这个例子,我们可以总结出事件代理的优点:

1.可以节省大量内存占用,减少事件注册

2.可以方便的动态添加和修改元素,不需要因为元素改动而修改事件绑定

3.js和DOM节点之间的关联少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率

缺点:

很多事件是不能冒泡的,比如说focus、blur、load本身就没有冒泡的特性,自然就不能用事件委托了。
事件模型:

事件模型有好几种,什么DOM事件模型,IE事件模型,简单点说就是说当对应事件被触发时候,监听该事件的所有监听函数都会被调用,如果面试官让我们手写一个事件模型的话其实就是想我们考虑到各种浏览器的兼容性,

function Emitter() {    this._listener = [];//_listener[自定义的事件名] = [所用执行的匿名函数1, 所用执行的匿名函数2]} //注册事件Emitter.prototype.bind = function(eventName, callback) {    var listener = this._listener[eventName] || [];//this._listener[eventName]没有值则将listener定义为[](数组)。    listener.push(callback);    this._listener[eventName] = listener;}  //触发事件Emitter.prototype.trigger = function(eventName) {    var args = Array.prototype.slice.apply(arguments).slice(1);//atgs为获得除了eventName后面的参数(注册事件的参数)    var listener = this._listener[eventName];     if(!Array.isArray(listener)) return;//自定义事件名不存在    listener.forEach(function(callback) {        try {            callback.apply(this, args);        }catch(e) {            console.error(e);        }    })}//实例var emitter = new Emitter();    emitter.bind("myevent", function(arg1, arg2) {        console.log(arg1, arg2);    });     emitter.bind("myevent", function(arg1, arg2) {        console.log(arg2, arg1);    });     emitter.trigger('myevent', "a", "b");

事件派发/事件广播(dispatchEvent)



0 0
原创粉丝点击