事件
- 在元素對象有一些天生自帶的屬性名,比如onclick、onmouseover… 當鼠標觸發了這些相關的操作,那麼就會執行這些屬性對應的屬性值函數;
- 事件是元素天生自帶的一種行爲
- 事件是瀏覽器的一種行爲,也可以是用戶行爲
- 發生在HTML元素上的事
事件綁定
- DOM0級事件綁定:都是冒泡階段的綁定
- DOM2級事件可以控制階段
添加事件的幾種方法(DOM0級)
行內加
<div id="box" onclick="fn()">可以點擊</div>
<script>
function fn(){
console.log(100);
}
js加
box.onclick = fn;
jquery加
$("#box").click(fn);
$("#box").on("click",fn);
</script>
- addEventListener(事件行爲(去掉on),事件回調函數,布爾) ( DOM2級事件綁定 )
- 第三個參數是布爾值:false代表冒泡階段執行, true:捕獲階段執行
- 只能給同一個元素的同一個事件行爲綁定同一個方法,如果都相同,就會覆蓋
- 可以給同一個元素的同一個事件行爲綁定多個不同的方法
- removeEventListener() 移除事件,需要和添加的時候參數保持一致
- 只有元素才能調用這個方法,因爲只有元素的原型鏈上纔有
- 先綁定誰,誰先執行
- 函數中的this指向被點擊的那個元素
- 正序執行(包括 IE9、IE10、IE11)
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
//
box.addEventListener("click",fn1,true);
box.removeEventListener("click",fn1,false);
//
box.addEventListener("mouseover",fn1,false)
box.addEventListener("click",fn2,false);
//
box.addEventListener("click",fn1,false)
box.addEventListener("click",fn2,false)
//只會執行一次,執行第二個
- attachEvent(“on”+事件,fn) ( DOM2級事件綁定 )
- 在IE8以上不兼容的; 谷歌不能用
- 倒序執行(在IE8)
- 方法中的this指向全局下的window對象
- 可以重複綁定
- detachEvent 刪除事件
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
box.attachEvent("onclick",fn1)
box.attachEvent("onclick",fn2)
box.attachEvent("onclick",fn1)
box.attachEvent("onclick",fn1)
//每個fn1都會執行
事件對象
- 事件對象:當用戶通過鼠標鍵盤去操作或觸發元素的事件行爲時,瀏覽器會默認將一些事件的信息傳遞給這個函數的第一個參數(比如鼠標點擊的位置距離頁面左右的距離,或距離點擊元素邊框的距離)
- clientX : 當前鼠標點擊的位置距離可視窗口左邊的距離
- clientY : 當前鼠標點擊的位置距離可視窗口上邊的距離
- offsetX : 當前鼠標點擊的位置距離盒子左邊框的距離
- offsetY : 當前鼠標點擊的位置距離盒子上邊框的距離
- pageX :當前鼠標點擊的位置距離頁面左邊框的距離
- pageY :當前鼠標點擊的位置距離頁面上邊框的距離
- target :事件源,事件在哪個元素上觸發,事件源就是誰
- type :事件類型(‘click’)
- e.cancelBubble=true :取消事件默認的冒泡傳播
- e.stopPropagation();// IE8及以下不兼容 取消事件默認的冒泡傳播
- 在IE8及以下,瀏覽器將事件信息放到了window.event上,並沒有傳遞給函數的形參
box.onclick=function(e){
e=e||window.event;
}
事件的默認行爲
- a標籤的點擊默認跳轉就是a的默認行爲
- form表單subimt,會默認提交
- 阻止事件的默認行爲:
- 形參.preventDefault() : 阻止事件的默認行爲
- 形參.returnValue = false
<form action="">
<input type="text" name="user">
<input type="text" value="提交">
</form>
<a href="" id="abc">點我一下</a>
<script>
var abc = document.getElementById("abc");
abc.onclick = function(e){
//e.preventDefault();
e.returnValue=false;
}
</script>
input框事件
- onfocus : 獲取鼠標焦點
- onblur : 失去鼠標焦點
- onchange : 當鼠標離開input框,並且input框中的內容發生改變,會觸發改事件
- oninput : 當input框每改變一次,就會執行一次
- onkeydown : 當鍵盤按下時,觸發的事件(獲取到上一次的值)
- onkeyup : 當鍵盤擡起獲取到最新的值
<input type="text" id="btn">
<script>
let btn = document.getElementById("btn");
btn.onfocus=function(){
console.log(100);
}
btn.onclick = function(){
console.log(200);
}
btn.onblur=function(){
console.log(300);
}
btn.onchange=function(){
console.log(400);
}
btn.oninput=function(e){
console.log(e);
console.log(this.value); //獲取input框的值
}
btn.onkeydown=function(){
console.log(this.value);
}
btn.onkeyup=function(e){
console.log(e);// 鍵盤事件對象
// 在鍵盤上,每一個鍵盤的鍵都有一個對應的keyCode;根據keyCode可以判斷當前點擊的是哪一個鍵;
}
</script>
keyCode表
事件的傳播
- 捕獲階段–>目標階段–>冒泡階段
- 冒泡傳播
- 事件有冒泡傳播的機制 : 當觸發子元素的事件時,會依次觸發當前元素祖先元素上對應的事件
- 取消事件的默認冒泡傳播
- 形參.cancelBubble = true;
- 形參.stopPropagation(); IE8及以下不兼容
- center.addEventListener(“click”,fn4,true)
- 第三個參爲true時捕獲階段執行,從外向裏
- 爲false時冒泡階段執行,從裏向外
- 當找到事件源,誰先綁定誰先執行
- 事件冒泡執行過程:
從最具體的的元素(你單擊的那個元素)開始向上開始冒泡,拿我們上面的案例講它的順序是:child->box
事件捕獲執行過程:
從最不具體的元素(最外面的那個盒子)開始向裏面冒泡,拿我們上面的案例講它的順序是:box->child
<style>
*{
margin: 0;
padding: 0;
}
#outer{
width: 300px;
height: 300px;
background: red;
margin: 100px auto;
}
#inner{
width: 200px;
height: 200px;
background: green;
margin: auto;
}
#center{
width: 100px;
height: 100px;
background: yellow;
margin: auto;
}
</style>
<body>
<div id="outer">
<div id="inner">
<div id="center"></div>
</div>
</div>
<script>
let outer = document.getElementById('outer');
let inner= document.getElementById('inner');
let center = document.getElementById('center');
function fn1(e) {
console.log('center');
}
function fn2(e) {
console.log('inner');
}
function fn3(e) {
console.log('outer');
}
//冒泡階段
outer.onclick = function (e) {
// console.log(e.target);
console.log('outer');
}
inner.onclick = function (e) {
// console.log(e.target);
console.log('inner');
}
center.onclick = function (e) {
// console.log(e.target);
console.log('center');
}
//fn1 fn2 fn3
//執行階段
center.addEventListener('click',fn1,true);
inner.addEventListener('click',fn2,true);
outer.addEventListener('click',fn3,true);
//fn3 fn2 fn1
//冒泡執行階段
function fn1(e) {
console.log('center 冒泡');
}
function fn4(e) {
console.log('center 捕獲');
}
function fn2(e) {
console.log('inner 冒泡');
}
function fn5(e) {
console.log('inner 捕獲');
}
function fn3(e) {
console.log('outer 冒泡');
}
function fn6(e) {
console.log('outer 捕獲');
}
center.addEventListener('click',fn1,false);
inner.addEventListener('click',fn2,false);
outer.addEventListener('click',fn3,false);
center.addEventListener('click',fn4,true);
inner.addEventListener('click',fn5,true);
outer.addEventListener('click',fn6,true);
// fn6 fn5 fn1 fn4 fn2 fn3
事件委託
- 事件委託:主要利用事件的冒泡傳播機制,給最外層的盒子綁定事件,根據事件源的不同,進行判斷,處理不一樣的需求
<div id="parent">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
//三個div將onmouseover這個事件委託給這三個盒子的父元素
<script>
let parent = document.getElementById("parent");
parent.onmouseover=function(e){
if(e.target.innerHTML==="1"){
console.log("紅色");
}else if(e.target.innerHTML==="2"){
console.log("綠色");
}else if(e.target.innerHTML==="3"){
console.log("黃色");
}
}
</script>
封裝拖拽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
#box,#box1{
width:100px;
height:100px;
background: red;
position: absolute;
left:0;
top:0;
}
#box1{
top:200px;
}
</style>
</head>
<body>
<div id="box">
</div>
<div id="box1"></div>
<script>
function Drag(str){
// this--> Drag的實例;可以獲取到當前Drag原型上的方法
let ele = document.getElementById(str.slice(1));
this.ele = ele;// 把元素放到了實例this的自定義屬性上;
this.ele.onmousedown = this.down.bind(this);
}
Drag.prototype.down =function(e){
// 記錄位置 綁定onmousemove 和onmouseup;
// console.log(e);
console.log(this);// Drag的實例;需要將該函數中的this指向Drag的實例;方便後期使用;
this.x = e.clientX;
this.y = e.clientY;
this.startL = parseFloat(getComputedStyle(this.ele).left);
this.startT = parseFloat(getComputedStyle(this.ele).top);
document.onmousemove = this.move.bind(this);
document.onmouseup = this.up;
}
Drag.prototype.move =function(e){
// 用鼠標變化的距離+ 盒子初始的位置=盒子最新的位置;
this.ele.style.left = e.clientX-this.x+this.startL+"px";
this.ele.style.top = e.clientY-this.y+this.startT+"px";
}
Drag.prototype.up =function(){
// 清除document的事件上的方法;
document.onmousemove = null;
document.onmouseup = null;
}
new Drag("#box");
new Drag("#box1")
// let obj = {
// fn:function(){
// console.log(this);
// }
// }
// obj.fn();
// box.onclick = obj.fn;
</script>
</body>
</html>
事件循環機制(Event Loop)
微任務 : await ,promise的then
宏任務: 定時器 ajax
事件循環機制: JS代碼執行分爲主任務隊列和等待任務隊列;在執行主棧的代碼遇到同步的代碼會立即執行,遇到異步的代碼會先放到等待隊列中,放入時區分是宏任務還是微任務,按照不同的任務放到等待隊列不同的池子中;當主棧執行完成時,那麼要先去等待隊列的微任務中去遍歷,按照放入時間先後依次執行,把微任務放到主棧中去執行,微任務執行完畢,再去執行宏任務。
function fn1(){console.log(666);}
setTimeout(function(){
console.log(800);
},0)
console.log(900);
async function fn(){
console.log(999);
await fn1();
console.log(888);
}
let p = new Promise(function(resolve,reject){
console.log(200);
resolve();
console.log(300);
})
p.then(function(){
console.log(100);// 異步的
});
fn();
// 900 200 300 999 666 888 100 800
// 微任務執行的順序要看誰先放進任務隊列中,誰先執行;
// async function async1() {
// console.log('async1 start');// 2
// await async2();// await 後面的是同步
// console.log('async1 end'); // 微1 6
// }
// async function async2() {
// console.log('async2'); // 3
// }
// console.log('script start');// 1
// setTimeout(() => {
// console.log('setTimeout'); // 8
// }, 0);
// async1();
// new Promise(resolve => {
// console.log('promise1');// 4
// resolve();
// }).then(() => {
// console.log('promise2');// 微2 7
// });
// console.log('script end'); // 5
// 微任務 : await promise的then
// 宏任務: 定時器 ajax
// 事件循環機制: JS代碼執行分爲主任務隊列和對待任務隊列;
在執行主棧的代碼遇到同步的代碼會立即執行,遇到異步的代碼會先放到等待隊列中,
放入時區分是宏任務還是微任務,按照不同的任務放到等待隊列不同的池子中;當主棧執行完成時,
那麼要先去等待隊列的微任務中去遍歷,按照放入時間先後依次執行,把微任務放到主棧中去執行,
微任務執行完畢,再去執行宏任務。
//
console.log('1');
setTimeout(function () {
console.log('2');
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5');
});
});
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8');
});
setTimeout(function () {
console.log('9');
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12');
});
});
事件詳解
查閱更多事件方式:
https://developer.mozilla.org/zh-CN/docs/Web/Events
或者查看元素的屬性(屬性中onxxx就是元素擁有的事件行爲)
一、常用事件
1. 鼠標事件
click 點擊(移動端 click 被識別爲單擊)
- dblclick 雙擊
- mousedown 鼠標按下
- mouseup 鼠標擡起
- mousemove 鼠標移動
- mouseover 鼠標滑過
- mouseout 鼠標滑出
- mouseenter 鼠標進入
- mouseleave 鼠標離開
- mousewhell 鼠標滾輪滾動
- 鼠標滾輪
/*註冊事件*/
/*IE、Opera註冊事件*/
if (document.attachEvent) {
outer1.attachEvent("onmousewheel", self.scrollFunc);
}
//Firefox使用addEventListener添加滾輪事件
if (document.addEventListener) {
//firefox
outer1.addEventListener(
"DOMMouseScroll",
self.scrollFunc,
false
);
}
//Safari與Chrome屬於同一類型
outer1.onmousewheel = self.scrollFunc;
// 滾輪
scrollFunc(e) {
e = e || window.event;
console.dir(e);
if (e.wheelDelta) {
//判斷瀏覽器IE,谷歌滑輪事件
if (e.wheelDelta > 0) {
//當滑輪向上滾動時
// alert("上滾");
}
if (e.wheelDelta < 0) {
//當滑輪向下滾動時
// alert("下滾");
}
} else if (e.detail) {
//Firefox滑輪事件
if (e.detail > 0) {
//當滑輪向下滾動時
// alert("下滾");
}
if (e.detail < 0) {
//當滑輪向上滾動時
// alert("上滾");
}
}
},
2. 鍵盤事件
key…
- keydown 按下某個鍵
- keyup 擡起某個鍵
- keypress 除 shift / Fn / CapsLock 鍵以外,其他鍵按住(連續觸發)
3. 移動端手指事件
單手指事件模型 Touch
- touchstart 手指按下
- touchmove 手指移動
- touchend 手指鬆開
- touchcancel 操作取消(一般應用於非正常狀態下操作結束)
多手指事件模型 Gestrue
- gestruestart
- gesturechange / gestrueundate
- gestureend
- gesturecancel
4. 表單元素常用事件
- onchange : 當鼠標離開input框,並且input框中的內容發生改變,會觸發改事件
- onfocus : 獲取鼠標焦點
- onblur : 失去鼠標焦點
- oninput : 當input框每改變一次,就會執行一次
- onkeydown : 當鍵盤按下時,觸發的事件(獲取到上一次的值)
- onkeyup : 當鍵盤擡起獲取到最新的值
5. 音視頻常用事件
- canplay 可以播放(資源沒有加載完,播放中可能會卡頓)
- canplaythrough 可以播放(資源已經加載完,播放中不會卡頓)
- play 開始播放
- playing 播放中
- pause 暫停播放
6. 其他常用事件
- load 資源加載完
- unload 資源卸載
- beforeunload 當前頁面關閉之前
- error 資源加載失敗
- scroll 滾動事件
- readystatechange AJAX請求狀態改變事件
- contextmenu 鼠標右鍵觸發
二、DOM 0級事件
dom 0級事件綁定:
dom 0級事件綁定的原理:給元素的私有屬性賦值,當事件觸發,瀏覽器會幫我們把賦的值執行,但是這樣也導致 “只能給當前元素某一個事件行爲綁定一個方法”
元素.on事件行爲=function(){}
box.onclick = function () {
console.log('哈哈哈~~');
}
box.onclick = function () {
console.log('呵呵呵~~');
}
box.onclick = function () {
console.log('哈哈哈~~');
// 移除事件綁定:DOM0 直接賦值爲 null 即可
box.onclick = null;
}
三、DOM 2級事件
dom 2級事件綁定:
dom 2級事件綁定的原理:基於原型鏈查找機制,找到 EventTarget.prototype 上的方法並且執行,此方法執行,會把給當前元素某個事件行爲綁定的所有方法,存放到瀏覽器默認的事件池中(綁定幾個方法,會向事件池存儲幾個),當事件行爲觸發,會把事件池中存儲的對應方法,依次按照順序執行 “給當前元素某一個事件行爲綁定多個不同方法”
元素.addEventListener(事件行爲,function(){},true/false)
IE6~8中:元素.attachEvent('on事件行爲',function(){})
box.addEventListener('click', function () {
console.log('哈哈哈~~');
}, false);
box.addEventListener('click', function () {
console.log('呵呵呵~~');
}, false);
function fn() {
console.log('哈哈哈~~');
// 移除事件綁定:從事件池中移除,所以需要指定好事件類型、方法等信息(要和綁定的時候一樣纔可以移除)
box.removeEventListener('click', fn, false);
}
box.addEventListener('click', fn, false);
- dom 2級事件綁定的時候我們一般都採用實名函數
- 目的:這樣可以基於實名函數去移除事件綁定
- 基於 addEventListener 向事件池增加方法,存在去重機制:“同一個元素,同一個事件類型,在事件池中只能存儲一遍這個方法,不能重複存儲”
- dom 0級事件和 dom 2級事件可以混在一起用:執行的順序以綁定的順序爲主
box.addEventListener('click', function () {
console.log('嗶咔嗶咔~~');
});
box.onclick = function () {
console.log('哇咔咔~~');
}
box.addEventListener('click', function () {
console.log('call~~');
});
- dom 0級事件中能做的事件行爲,dom 2級事件都支持,dom 0級事件不一定能處理綁定,例如:transitionend、DOMContentLoaded…
window.addEventListener('load', function () {
// 所有資源都加載完成觸發
console.log('LOAD');
});
window.addEventListener('DOMContentLoaded', function () {
// 只要DOM結構加載完就會觸發
console.log('DOMContentLoaded');
});
// $(document).ready(function(){})
$(function () {
// JQ 中的這個處理(DOM 結構加載完觸發)採用的就是 DOMContentLoaded 事件,並且依託 DOM2 事件綁
// 定來處理,所以同一個頁面中,此操作可以被使用多次
});
$(function () {
}); */
// JQ 中的事件綁定採用的都是 DOM2 事件綁定,例如:on / off / one
- window.onload VS $(document).ready()
- $(document).ready() 採用的是 DOM2 事件綁定,監聽的是 DOMContentLoaded 這個事件,所以只要DOM 結構加載完成就會被觸發執行,而且同一個頁面中可以使用多次(綁定不同的方法,因爲基於 DOM2 事件池綁定機制完成的)
- window.onload 必須等待所有資源都加載完成纔會被觸發執行,採用 DOM0 事件綁定,同一個頁面只能綁定一次(一個方法),想綁定多個也需要改爲 window.addEventListener(‘load’, function () {})DOM2 綁定方式
三、給元素的事件行爲綁定方法
給元素的事件行爲綁定方法,當事件行爲觸發方法會被執行,不僅被執行,而且還會把當前操作的相關信息傳遞給這個函數 =》事件對象
- 如果是鼠標操作:獲取的是 MouseEvent 類的實例 =》 鼠標事件對象
鼠標事件對象 -> MOuseEvent.prototoye -> UIEvent.prototype ->Event.prototype -> Object.prototype- 如果是鍵盤操作:獲取的是KeyboardEvent類的實例 =》 鍵盤事件對象
- 除了以上還有:普通事件對象(Event)、手指事件對象(TouchEvent)等
事件對象
box.onclick = function (ev) {
// 鼠標事件對象
// clientX / clientY:當前鼠標觸發點距離當前窗口左上角的X / Y軸座標
// pageX / pageY:觸發點距離當前頁面左上角的X / Y軸座標
// type:觸發事件的類型
// target:事件源(操作的是哪個元素,哪個元素就是事件源),在不兼容的瀏覽器中可以使用srcElement
獲取,也代表的是事件源
// preventDefault():用來阻止默認行爲的方法,不兼容的瀏覽器中用 ev.returnValue = false 也可以
阻止默認行爲
// stopPropagation():阻止冒泡傳播,不兼容的瀏覽器中用 ev.cancelBubble = true 也可以阻止默認
行爲
console.log(ev);
}
四、當事件觸發時瀏覽器會做什麼
事件對象和函數以及給誰綁定的事件沒啥必然關係,它存儲的是當前本次操作的相關信息,操作一次只能有一份信息,所以在那個方法中獲取的信息都是一樣的,第二次操作,存儲的信息會把上一次操作存儲的信息替換掉…
每一次事件觸發,瀏覽器都會這樣處理一下:
- 捕獲到當前操作的行爲(把操作信息獲取到),通過創建 MouseEvent 等類的實例,得到事件對象 EV
- 通知所有綁定的方法(符和執行條件的)開始執行,並且把 EV 當作實參傳遞給每個方法,在每個方法中得到事件對象
- 後面再重新觸發這個事件行爲,會重新獲取本次操作的信息,用新的信息替換老的信息,然後繼續之前的步驟
let obj = null;
box.addEventListener('click', function (ev) {
console.log(ev);
obj = ev;
});
box.addEventListener('click', function (ev) {
console.log(ev === obj); // true
});
document.body.onclick = function (ev) {
console.log(ev === obj); // true
}
五、事件傳播機制
事件的傳播機制:
- 捕獲階段:從最外層向最裏層事件源依次進行查找(目的:是爲冒泡階段事先計算好傳播的層級路徑) CAPTURING_PHASE
- 目標階段:當前元素的相關事件行爲觸發 AT_TARGET
- 冒泡傳播:觸發當前元素的某一個事件行爲,不僅它的這個行爲被觸發了,而且它所有的祖先元素(一直到window)相關的事件行爲都會被依次觸發(從內到外的順序) BUBBLING_PHASE
let $ = selector => document.querySelector(selector);
let inner = $('.inner');
let outer = $('.outer');
inner.onclick = function (e) {
console.log('inner 的click事件觸發了');
};
outer.onclick = function (e) {
console.log('outer 的click事件觸發了');
e.stopPropagation(); // 阻止事件冒泡
};
document.body.onclick = function () {
console.log('body 的click事件被觸發了');
};
document.onclick = function () {
console.log('document 的click事件觸發了');
};
事件冒泡
事件冒泡:我們點擊 inner,inner 的父級元素 outer 的點擊事件以及整個文檔頂端的 document 的點擊事件都會被依次觸發。這種從低層級的 html 元素向高層級依次觸發事件的現象稱爲事件冒泡;
取消冒泡:e.stopPropagation() 阻止事件冒泡;
IE 的阻止冒泡: e.cancelBubble = true;