前端交互体验核心之事件(一)

  • 如何绑定事件处理函数
  • 事件处理程序的运行环境
  • 解除事件处理程序
  • 事件处理模型————冒泡、捕获
  • 默认事件
  • 事件对象
  • 事件委托

如何绑定、解除事件处理函数

一、绑定事件处理函数

 1.ele.on xxx = function(event){}

  • 兼容性好,但是一个元素的同一个事件只能绑定一个处理函数
  • 基本等同于写在HTML行间
  • 行间句柄绑定示例:<div style="width:100px;height:100px;background-color:red;" onclick="console.log('谁点我了?')"></div>

2.dom.addEventListener(‘事件类型‘,处理函数,false);

  • IE9以下不兼容,可以为一个事件绑定多个处理程序(W3C标准)
  • 绑定同一个处理函数多次,不能执行多次
  • 第三个参数指定事件是否在捕获或冒泡阶段执行

3.dom.attachEvent('on'+ 事件类型 , 处理函数);

  • IE独有,一个事件同样可以绑定多个处理程序
  • 绑定同一个处理函数多次,可以执行多次
  • 示例:dom.attachEvent('onClick',function(){});

二、事件处理程序的运行环境

 1.ele.onxxx = function(event){}                  ==> 程序this指向是DOM元素本身。

 2.dom.addEventListener(type,fun,false);  ==> 程序this指向是DOM元素本身。

 3.dom.attachEvent('on' + Type ,fun);        ==>程序this指向是window。

了解this指向规则可以访问我的另一篇博客:JavaScript中的this指向规则

 封装兼容性的事件绑定函数addEvent(elem,type,handle)方法:(这个方法没有考虑事件模型设置,还可以改进)

//实现全兼容性的事件函数绑定方法
//addEventListener是w3c标准方法,IE9以下不兼容
//attachEvent是ie的独有方法,但是this指向window,通过call修正this指向dom本身
//on...方式不能绑定一个方法
function addEvent(elem,type,handle){
    if(elem.addEventListener){
        elem.addEventListener(type,handle,false);    
    }else if(elem.attachEvent){
        elem.attachEvent('on' + type, function(){
            handle.call(elem); 
        });
    }else{
     type = type.substring(0,1).toUpperCase()+type.substring(1); elem[
'on' + type] = handle; } }

三、解除事件处理程序

 1.ele.on XXX = function(){}的事件解除和应用:

  • 理解:句柄绑定的事件程序,是一个变量(属性)赋值的方式。
  • 解决方案:将值清空即等于解除绑定程序。
  • 实现:ele.on XXX = false/""/null

 应用:典型应用功能是网页内的点击跳转链接小广告,点击一次后再点击就不会出现跳转。(案例模拟逻辑,没有具体实现)

//html
<div style="width: 100px;height: 100px;background-color: red;"></div>

//js
var advDIV = document.getElementsByTaName('div')[0];
advDIV.onclick = function(){
    console.log(a);
    advDIV.onclick = null;
}

2.dom.addEventListener(‘事件类型‘,处理函数,false)的事件解除:

  • 解除方法:dom.removeEventListener(‘事件类型‘,处理函数,false)
  • 理解:与绑定的DOM对象要一致,事件、函数、要一一对应。

 3.dom.attachEvent('on'+ 事件类型 , 处理函数)的事件解除:

  • 解除方法:dom.detachEvent('on' + type, fn)
  • 理解:与removeEventListener方法一致

解除事件处理程序需要注意的问题(addEventListener、attachEvent):若绑定匿名函数,则无法解除。比如直接在绑定函数时直接在绑定方法上写入函数表达式,这种绑定是无法解除的。

advDIV.addEventListener('click',function(){
    console.log("a");
},false);

事件处理模型(冒泡、捕获)

四、事件冒泡

定义:结构上(非视觉上)嵌套关系的元素,会存在事件冒泡功能,即同一事件,自子元素冒泡向父元素。(自底向上)

解析:事件冒泡就是嵌套的元素,同一事件在子元素上触发后,从子元素到父元素及祖辈元素依照嵌套层级关系逐个触发执行。

实现:在绑定事件程序时,第三个参数设置为false(默认)及设置dom的事件触发模型为事件冒泡。(例如:addEventListener('click',function(){...},false);)

点击测试

 实例测试的事件采用句柄绑定的弹窗(句柄绑定可以默认触发事件冒泡),浏览器如果有设置禁止弹窗无法测试,最后附上代码:

//html(为了让代码更清晰,css代码大家自己填充)
<div class="wrapper">
    <div class="content">
        <div class="box"></div>
    </div>
</div>
//js(重点在于理解事件冒泡,事件绑定没有兼容IE9以下的浏览器)
var wrapper = document.getElementsByClassName("wrapper")[0];
var content = document.getElementsByClassName("content")[0];
var box = document.getElementsByClassName("box")[0];

wrapper.addEventListener('click',function(){
    console.log("wrapper");
 },false);
content.addEventListener('click',function(){
    console.log("console");
 },false);
box.addEventListener('click',function(){
    console.log("box");
 },false);

五、事件捕获

定义:结构上(非视觉上)嵌套关系的元素,会存在事件捕获功能,即同一事件,自父元素捕获至子元素。(自上向下)

解析:事件捕获就是嵌套的元素,同一事件在子元素上触发时,从顶层祖辈元素到父元素再到自身逐个被触发执行。

实现:在绑定事件程序时,第三个参数设置为true及设置dom的事件触发模型为事件冒泡。(例如:addEventListener('click',function(){...},true);)

由于句柄绑定事件程序不能设置事件模型,而句柄默认事件模型为事件冒泡,所以这里不能提供实例演示,下面代码提供参考:

wrapper.addEventListener('click',function(){
    console.log("wrapper");
 },true);
content.addEventListener('click',function(){
    console.log("console");
 },true);
box.addEventListener('click',function(){
    console.log("box");
 },true);
  • IE9以下的浏览器事件捕获没有事件捕获模型addEventListener( type, fun, true)只有IE9及以上浏览器兼容
  • attachEvent('on' + type , fun)没有第三个控制事件模型的参数,实质上与句柄类似,默认事件模型为冒泡

六、事件模型下事件程序的执行逻辑

  • 触发顺序:先捕获,后冒泡
  • 捕获:从外层向内层执行
  • 冒泡:从内层向外层执行

后面两个逻辑其实就是时间模型的本身特性,这里的重点在第一个执行逻辑。其实前面有个设置事件模型的重要点没有讲,那就是同一个事件的同一个处理程序只能设置一种事件模型,但是同一个事件的不同处理程序可以设置不同的事件模型,所以就引出了先捕获后执行的事件处理逻辑。

wrapper.addEventListener('click',function(){
    console.log("wrapper");
},true);
content.addEventListener('click',function(){
    console.log("console");
},true);
box.addEventListener('click',function(){
    console.log("box");
},true);
wrapper.addEventListener('click',function(){
    console.log("wrapperBubble");
 },false);
content.addEventListener('click',function(){
    console.log("consoleBubble");
 },false);
box.addEventListener('click',function(){
    console.log("boxBubble");
 },false);

//执行结果
// wrapper
// console
// box
// boxBubble
// consoleBubble
// wrapperBubble

这个先后更详细的来说应该是:先将事件的捕获模型的事件程序依次执行完再依次执行事件冒泡模型的事件程序。但是这里有一个很容易被忽略的问题,因为我这个示例代码是将捕获事件放到了前面,如果在后面呢?

wrapper.addEventListener('click',function(){
    console.log("wrapperBubble");
 },false);
content.addEventListener('click',function(){
    console.log("consoleBubble");
 },false);
box.addEventListener('click',function(){
    console.log("boxBubble");
 },false);

wrapper.addEventListener('click',function(){
    console.log("wrapper");
},true);
content.addEventListener('click',function(){
    console.log("console");
},true);
box.addEventListener('click',function(){
    console.log("box");
},true);

//执行结果
// wrapper
// console
// boxBubble
// box
// consoleBubble
// wrapperBubble

这次有点惊喜了,box的执行逻辑发生了变化,但是我们暂时不考虑这个问题,从总体上来看开始遵循了先执行捕获模式的事件程序,然后执行冒泡模式的事件程序。那为什么box的执行循序会发生变化呢?

这里需要注意的是,被事件模型被动触发的事件程序是按照先捕获后冒泡的原则,但是被主动触发事件的DOM执行的原则是事件执行(并非冒泡执行或捕获执行),所以会遵循先绑定先执行的原则。

注:focus,blur,change,submit,select等事件不冒泡。

七、取消事件冒泡

在一些嵌套结构复杂的设计中,可能会出现不希望被子元素冒泡触发事件程序执行的情况,这时需要在子元素的事件程序中执行取消冒泡行为的方法(event.stopPropagation()),这个方法会取消当前事件程序冒泡到父级元素。例如前面的冒泡事件坐下面修改:

wrapper.addEventListener('click',function(){
    console.log("wrapperBubble");
 },false);
content.addEventListener('click',function(e){
    console.log("consoleBubble");
    e.stopPropagation();//取消"content"冒泡到"wrapper"
 },false);
box.addEventListener('click',function(e){
    console.log("boxBubble");
 },false);

以上示例中,当点击box会执行“boxBubble”,“contentBubble”。点击content时只触发自身事件程序。

event.stopPropagation()是W3C的标准方法,但是任性IE(IE9以下)需要event.cancelBubble = true;属性来取消事件冒泡。所以需要我们实现一个兼容方法:

//封装取消冒泡的函数兼容方法stopBubble(event)
function stopBubble(event){
    if(event.stopPropagation){
        event.stopPropagation();
    }else{
        event.cancelBubble = true;
    }
}

默认事件与事件对象

八、默认事件及取消默认事件

  • 常见的默认事件:表单事件,a标签跳转,右键菜单等。

取消默认事件的方法

  • return false;以对象属性的方式注册的事件才生效
  • event.preventDefault();W3C标准方法,IE9以下不兼容
  • event.returnValue = false;兼容IE(chrome也实现了)

 例如:取消右键默认事件

document.oncontextmenu = function(event){
    console.log('a');
    //方法一: return false;
    //方法二: event.preventDefault(); (W3C标准)
    //方法三: event.returnValue = false;(兼容IE)
}

废话不多说,兼容方法走一波:

//封装阻止默认事件的函数cancelHandler(event);
function cancelHandler(event){
    if(event.preventDefault){
        event.preventDefault();
    }else{
        event.returnValue = false;
    }
}

关于取消a标签默认事件,引出的一系列思考?

在日常开发中,会经常将a标签作为按钮使用,我们知道a标签在没有设置连接的情况下,会默认有点击事件。当我们点击一个没有设置连接的a标签时会刷新页面,或者锚链接没有设置具体位置(连接内容只写了一个“#”)会跳转到页面最顶端。这时候我们就需要取消默认事件。

//没有设置连接的a标签,刷新页面
<a href="">dom<a> 

//没有具体锚定位置的a标签,回到页面顶端
<a href="#">dom</a>
  • 取消a标签的默认事件
var aDom = document.getElementsByTagName('a')[0];
aDom.onclick = function(e){
    console.log(this);
    cancelHandler(e);//使用了前面的兼容方法
}

上面的示例演示了将a标签的点击事件修改成了自己的事件程序,并且阻止了a标签的默认事件触发。但是这是采用onclick的对象属性赋值的写法,如果有多个事件程序呢?是否每一次绑定事件程序都需要操作阻止指定呢?请看以下代码:

//addEvent()是前面封装的绑定事件的兼容方法
addEvent(aDom,'click',aDomFn1);
addEvent(aDom,'click',aDomFn);

function aDomFn(e){
    console.log(this);
    cancelHandler(e);
}
function aDomFn1(e){
    console.log("我就不取消默认事件怎么滴");
}

我特别将阻止默认事件的事件程序放到了后面绑定,但是当我点击a标签时,第一个事件程序运行完以后接着执行的是第二个事件程序,所以暂时得到的答案是,只要我们在对象的事件中任意事件程序设置一个阻止默认事件触发,就可以完全阻止这个对象的指定事件的默认事件的触发。(对于这个问题的底层实现还没做深入的研究,只能说是暂时的结论)

九、事件对象

  • 获取事件对象
  • 获取事件源对象
  • 事件委托

 9.1.当浏览器引擎执行事件程序时,浏览器引擎会自动的向这个程序传入一个默认参数,这个参数就是事件对象,这个对象的属性记录了事件的各种属性,其中重要的有鼠标坐标点,事件模型状态、取消默认事件的属性和方法等,最重要的是还有一个事件源对象。(但是在IE9之前的浏览器这个事件对象不是作为参数传入的,而是被保存在window.event上)。所以兼容代码要来了:

function fnEvent(e){
    var event = e || window.event;
        //这里是事件程序的具体代码
}

9.2.在事件冒泡和事件捕获中,我们知道了有些事件是被冒泡或者捕获执行的,在很多情况下我们需要在父级或者祖辈上执行事件程序,但又需要获取触发事件的DOM对象相关的数据,所以前面说事件对象上的事件源对象很重要。

在事件对象上有target这个属性保存了触发事件的源对象DOM,IE(IE9以下)上的事件源对象属性是srcElement。在chrome上这两个属性都有。所以,封装兼容写法又得来一波:

//封装兼容性方法获取事件源对象
function getTarget(e){
    var event = e || window.event;
    var target = event.target || event.srcElement;
    return target;
} 

9.3.初级前端必会的操作:事件委托

  • 什么是事件委托?
  • 事件委托怎么实现?

 假设我们有列表包含10个“li”,我们需要在点击每个“li”的的时候打印其文本内容,以我们通常的想法就是循环每个“li”添加click事件,且不说性能问题。如果li是动态添加呢?所以事件冒泡+事件源对象就可以展现它们的强大了。先看代码,直接回答第二个问题,第一个问题你自己机会有答案了。

//html
<ul>
<li>1</li>
<li>2</li>
...//这里省略100个li
</ul>

//js
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function (e) {
    var event = e || window.event;//获取事件对象
    var target = event.target || event.srcElement;//获取事件源
    console.log(target.innerText);//通过事件源获取到点击的li的文本
}

猜你喜欢

转载自www.cnblogs.com/ZheOneAndOnly/p/10355472.html