13|事件

JavaScript与HTML之间的交互是通过事件实现的!

具体的实现是通过监听器预定事件,然后事件发生时候执行对应的代码! 在软件工程中被称为观察者模式的模型!

01|事件流

  • 事件冒泡:IE事件流 开始由最具体的元素接受逐级向上传播到不具体的节点! 由子到父

  • 事件捕获:Netscape事件流 不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件 由父到子

  • DOM事件流:规定的事件流有三个阶段

    • 事件捕获阶段
    • 事件的目标阶段
    • 事件的冒泡阶段
事件捕获(截获事件) ===> 对应的目标接收到事件 ===> 冒泡阶段(在现阶段对事件做出响应)

02|事件处理程序

响应某个事件的函数就叫做 事件处理程序 事件处理程序一般以 "on"开头!

<input type="button"  value="click me!" onclick="showMessage()" />
<script>
    let showMessage = ()=>{console.log("Hello World!");}
</script>

其中可以通过event变量,直接访问事件对象!

  • DOM0级别事件处理程序
let button = document.getElementById("mybtn");
button.onclick = function(){
    console.log(this.id);
}

DOM0 级方法指定的事件处理程序被认为是元素的方法,这时候的事件处理程序是在元素的作用域中运行的!

  • DOM2级别事件处理程序

DOM2级别事件处理程序中指定了两个方法处理指定和删除事件处理程序的操作:

  1. addEventListener
  2. removeEventListener

对应的两个方法分别接受三个参数:

  1. 事件名
  2. 事件处理程序
  3. 布尔值:boolean值 表示捕获阶段
    1. true 捕获阶段调用事件处理程序
    2. false 冒泡阶段调用事件处理程序

其实也是依附于元素的作用域中进行的! 并且添加事件监听器(addEventListener)可以

添加多个事件处理程序!

let button = document.getElementById("myBtn");
button.addEventListener('click',function(){
   console.log(this.id); 
});
button.addEventListener('click',function(){
    console.log("hello Jerry!")
});

删除事件监听器:

let button = document.getElementById("myBtn");
button.addEventListener('click',function(){
   console.log(this.id); 
});

/*
    xxxx
*/
button.removeEventListener("click",function(){
    console.log(this.id);
})
//无效

因为对应的 事件处理函数看似相同 其实并不是同一个函数!(匿名函数)

正确做法如下所示:

let button = document.getElementById("myBtn");
const handle = function(){
   console.log(this.id); 
}
button.addEventListener('click',handle);
button.removeEventListener("click",handle);

其实大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段! 因为可以最大限度的兼容各种浏览器

03|IE事件处理程序

IE中实现了与DOM类中相似的两个方法:

  1. attachEvent
  2. detachEvent

对应的两个方法分别接收两个参数:

  • 事件处理程序名称 一般都是on开头的事件处理程序名
  • 事件处理程序

同样的使用IE事件处理程序可以对同一个事件添加多个事件处理函数!

看起来好像DOM0级别的事件处理程序,有什么不同吗?

其中最大的不同就是事件处理程序的作用域不同!

let button = document.getElementById("myBtn");
const handle = function(){
   console.log(this); 
}
button.addEventListener('click',handle);//button
button.attchEvent('onclick',handle);//window

04|跨浏览器的事件处理程序

其实就是通过浏览器的不同写出兼容不同浏览器的添加事件处理器的程序函数!

const EventUtil = {
    addHandler(element,type,handler){
        if(element.addEventListener){
           element.addEventListener(type,handler,false);
        }else if(element.attchEvent){
           element.attchEvent(`on${type}`,handler);      
        }else{
           element[`on${type}`] = handler; 
        }
    },removeHandler(element,type,handler){
        if(element.addEventListener){
           element.removeEventListener(type,handler,false);
        }else if(element.attchEvent){
           element.detachEvent(`on${type}`,handler);      
        }else{
           element[`on${type}`] = handler; 
        }
    }
}

05|事件对象

1. 属性或方法
    type // 被触发的事件类型
    target // 事件的目标
    currentTarget // 事件处理程序当前正在处理事件的那个元素
    注: 在事件处理程序内部,对象this始终等于currentTarget的值,而target只包含事件的实际目标
    *** 一个函数处理多个事件可以使用switch(event.type)的方式
    event.preventDefault() // 阻止事件的默认行为
    event.stopPropagation() // 阻止事件冒泡
属性/方法 类型 读写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消事件的默认行为
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素
defaultPrevented Boolean 只读 为 true 表示已经调用了 preventDefault()(DOM3级事件中新增)
detail Integer 只读 与事件相关的细节信息
eventPhase Integer 只读 调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段
preventDefault() Function 只读 取消事件的默认行为。如果cancelable是true,则可以使用这个方法
stopImmediatePropagation() Function 只读 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3级事件中新增)
stopPropagation() Function 只读 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法
target Element 只读 事件的目标
trusted Boolean 只读 为true表示事件是浏览器生成的。为false表示事件是由开发人员通过 JavaScript 创建的(DOM3级事件中新增)
type String 只读 被触发的事件的类型
view AbstractView 只读 与事件关联的抽象视图。等同于发生事件的window对象

其中有一个属性:event.eventPhase可以用来确定当前事件位于事件流的哪个阶段!

  • eventPhase:1 捕获阶段
  • eventPhase:2 目标对象
  • eventPhase:3 冒泡阶段
属性/方法 类型 读写 说明
cancelBubble Boolean 读/写 默认值为false,但将其设置为true就可以取消事件冒泡(与DOM中的stopPropagation()方法的作用相同)
returnValue Boolean 读/写 默认值为true,但将其设置为false就可以取消事件的默认行为(与DOM中的preventDefault()方法的作用相同)
srcElement Element 只读 事件的目标(与DOM中的target属性相同)
type String 只读 被触发的事件的类型

06|跨浏览器的事件对象

const EventUtil = {
    addHandler(element,type,handler){
        if(element.addEventListener){
           element.addEventListener(type,handler,false);
        }else if(element.attchEvent){
           element.attchEvent(`on${type}`,handler);      
        }else{
           element[`on${type}`] = handler; 
        }
    },removeHandler(element,type,handler){
        if(element.addEventListener){
           element.removeEventListener(type,handler,false);
        }else if(element.attchEvent){
           element.detachEvent(`on${type}`,handler);      
        }else{
           element[`on${type}`] = handler; 
        }
    }
}

添加的部分如下所示:

const EventUtil = {
    getEvent(event){
        return event?event:window.event;
    },getTarget(event){
        return event.target || event.srcElement;
    },preventDefault(event){
        if(event.preventDefault){
           event.preventDefault();
         }else{
            event.returnValue = false;
        }
    },stopPropagation(event){
        if(event.stopPropgation){
            event.stopPropagation()
        }else{
            event.cancelBubble = true;
        }
    }
}

07|事件类型

其中JavaScript高程中讲到的事件类型如下所示:

  • UI事件
  • 焦点事件
  • 鼠标与滚轮事件
  • 键盘与文本事件
  • 复合事件
  • 变动事件
  • HTML5事件
  • 设备事件
  • 触摸与手势事件
/*
1.鼠标和滚轮事件
    1.客户区坐标位置clientX/clientY  //表示事件发生时鼠标指针在视口中的水平和垂直位置
    2.页面坐标位置 pageX/pageY  //表示事件在页面中发生的位置
    3.屏幕坐标位置 //获取事件发生时在屏幕中的位置
*/

/*
2.修改键(如果用户在触发事件时按下了shift/ctrl/alt/Meta,则返回true)
    event.shiftkey | event.altKey | event.metaKey | event.ctrlKey
*/

/*
3.鼠标按钮(event.button)
   对于mousedown和mouseup,其event中存在一个button属性,值为0表示主鼠标按钮,1表示中间鼠标按钮,2表示次鼠标按钮
*/

/*
4.鼠标滚轮事件(mousewheel)
    1.兼容方案:

*/
let getWheelDelta = function(event){
    let wheelDelta = event.wheelDelta ? event.wheelDelta : (-event.detail * 40);
    return wheelDelta;
}
//注:document在普通浏览器中通过mousewheel监听鼠标滚轮事件,在火狐中使用DOMMouseScroll监听

5.键盘与文本事件
6.变动事件
    1.DOMSubtreeModified | DOMNodeInserted | DOMNodeRemoved
    例子
        el.addEvent("DOMSubtreeModified", fn1)

7.HTML5事件
    1.contextmenu事件(自定义上下文菜单)
    2.DOMContentLoaded事件(在形成完整dom树之后就触发,不理会图像,js文件,css文件等资源是否下载完成)
    3.hashchange事件(在URL的参数列表发生变化【即#号后面的所有字符串】时触发)
    注:必须要把hashchange添加给window对象,event对象包含两个属性oldURL和newURL,分别保存着参数列表变化前后的完整URL
    
    window.addEvent("hashchange", function(event){
        // oldURL和newURL存在兼容问题,最好用location.hash代替
        console.log(event.oldURL, event.newURL);
    })

08|内存与性能

如果在页面写在之前没有清理干净事件处理程序,那他们就会滞留在内存中,每次加载完页面再卸载时,内存中滞留的对象就会增加,因为事件处理程序占用的内存并没有被释放。

【解决方案】再页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。但是使用onunload时页面不会被缓存bfcache(即往返缓存)中。

  • 事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

通过对应的代码讲解:

<ul id="myLinks"> 
 <li id="goSomewhere">Go somewhere</li> 
 <li id="doSomething">Do something</li> 
 <li id="sayHi">Say hi</li> 
</ul>
<script>
    let item1 = document.getElementById("goSomewhere"); 
    let item2 = document.getElementById("doSomething"); 
    let item3 = document.getElementById("sayHi"); 
    EventUtil.addHandler(item1, "click", function(event){ 
     location.href = "http://www.wrox.com"; 
    }); 
    EventUtil.addHandler(item2, "click", function(event){ 
     document.title = "I changed the document's title"; 
    }); 
    EventUtil.addHandler(item3, "click", function(event){ 
     alert("hi"); 
    });
</script>        

其中关于添加事件处理器的代码重复了,到时候如果需要卸载的话卸载起来比较麻烦!

我们接下来使用事件委托的方式进行解决!

let list = document.getElementById("myLinks"); 
EventUtil.addHandler(list, "click", function(event){ 
    event = EventUtil.getEvent(event); 
    var target = EventUtil.getTarget(event); 
    switch(target.id){ 
        case "doSomething": 
            document.title = "I changed the document's title"; 
            break; 
        case "goSomewhere": 
            location.href = "http://www.wrox.com"; 
            break; 
        case "sayHi": 
            alert("hi"); 
            break; 
    } 
});
  • 移除事件处理程序

在编写对应的事件处理程序的过程中,讲函数引用改为空!

let button = document.getElementById("mybutton");
button.onclick = ()=>{
    //TODO
    
    button.onclick = null;//移出事件处理程序
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358