[toc]
## 什么是事件
什么是事件?
事件是分為兩部分:
### 行為本身
瀏覽器天生就賦予其的行為,比如說
- onclick
- onmouseover
- onmouseenter
- onmouseout(onmouseleave)
- onmousemove
- onmousedown
- onmouseup
- onmousewheel
- onscroll
- onresize(window.onresize瀏覽器窗口的大小改變事件)
- onload(所有資源都加載完成)
- onunload(瀏覽器關閉的時)
- onfocus(文本框獲取焦點行為)
- onblur(文本框失去焦點行為)
- onkeydown (必須是文本框?)
- onkeyup
哪怕我們沒有給上述的行為綁定方法,事件也是存在的,當我們點擊這個盒子的時候,同樣會觸發它的onclick行為,只是什么事情都沒做
### 事件綁定
給元素的某一個行為綁定方法
```
var oDiv = document.getElementById("div1");
```
DOM 【0級】事件綁定
```
//onclick這個方法定義在當前元素的私有屬性上
oDiv.onclick = function(){
//->當我們觸發oDiv的click行為的時候,會把綁定的這個函數執行
};
```
為什么沒有DOM 【1級】?
DOM1級升級的時候并沒有對事件系統進行升級,故沒有 DOM 【1級】事件綁定
DOM 【2級】事件綁定
```
//addEventListener這個屬性是定義在當前元素的祖先身上的->EventTarget這個類的原型身上
oDiv.addEventListener("click",function(){
console.log("ok");
},false);
```
## 事件對象及兼容
```
//->我們把匿名函數定義的部分當做一個值賦給oDiv的點擊行為(函數表達式)
//->當我們觸發#div1點擊行為的時候,會執行對應綁定上的方法
//不僅僅把綁定的方法執行了,而且瀏覽器還默認的給這個方法傳遞了一個參數值
oDiv.onclick = function(){
console.dir(arguments) ;
//->arguments[0]->MouseEvent
//【鼠標事件對象】
//1)它是一個事件對象數據類型的值,里面包含了很多屬性名和屬性值,這些都是用來記錄當前鼠標相關信息的
//2)MouseEvent->UIEvent->Event->Object
//3)MouseEvent 記錄的是頁面中唯一一個鼠標每一次觸發時候的相關信息,和到底是在哪個元素身上觸發的值沒有關系
};
```
### 事件對象
1. 它是一個事件對象數據類型的值,里面包含了很多屬性名和屬性值,這些都是用來記錄當前鼠標相關信息的
2. MouseEvent->UIEvent->Event->Object
3. MouseEvent 記錄的是頁面中唯一一個鼠標每一次觸發時候的相關信息,和到底是在哪個元素身上觸發的值沒有關系
### 關于事件對象的兼容性問題
事件對象本身的獲取存在兼容問題:標準瀏覽器中是瀏覽器給方法傳遞的參數,我們只需要定義形參e就可以獲取到
在IE6~8中瀏覽器不會給方法傳遞參數,我們如果需要的話,需要到window.event中獲取查找
```
//->e = e || window.event;
oDiv.onclick = function(e){
e=e||window.event;
//->e.type: 存儲的是當前鼠標觸發的行為類型,比如這里是"click"
//->e.clientX / e.clientY:當前鼠標觸發點距離當前屏幕左上角的x/y軸的坐標值
//->e.target:事件源,當前鼠標觸發的是哪個元素,那么它存儲的就是哪個元素;但是在IE6~8中不存在這個屬性,我們使用e.srcElement來獲取事件源
e.target = e.target||e.srcElement;
//-> e.pageX/e.pageY:當前鼠標觸發點距離body左上角的x/y軸的坐標(頁面第一屏幕最左上端)的x/y軸的坐標,但是在IE6~8中沒有這個屬性,我們通過使用clientY+滾動條卷去的高度來獲取也可以
e.pageX = e.pageX||(e.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft));
e.pageY = e.pageY||(e.clientY+(document.documentElement.scrollTop||document.body.scrollTop));
//->e.preventDefault:阻止瀏覽器的默認行為
//IE6~8沒有這個方法
ev.preventDefault?e.preventDefault():e.returnValue=false;
return false;//->和上述的代碼是一樣的效果,都是在阻止默認行為
//如果是a標簽,還可以使用javascript:; 或 javascript:void 0 或 javascript:void 1
//->e.stopPropagation:阻止事件的冒泡傳播,在IE6~8中不兼容
e.stopPropagation?e.stopPropagation():e.cancelBubble=true;
};
```
```
document.body.onclick = function(e){
}
```
```
input.onkeyup = function(e){
//->KeyboardEvent
//->e.keyCode:當前鍵盤上每一個鍵對應的值
//空格鍵(space)->32
//回退鍵(Backspace)->8
//回車鍵(Enter)->13
//刪除鍵(Del)->46
//四個方向鍵 左上右下-> 37 38 39 40
}
document.body 也為KeyboardEvent
```
### 默認行為
a標簽:默認行為就是跳轉頁面,但是我們有時候使用a標簽,只是想應用它的特殊性,并不想點擊的時候跳轉。
## 事件的傳播機制

分為三個階段(主要是捕獲和冒泡)
- 捕獲階段:從外向內依次查找元素
- 目標階段:當前事件源本身的操作
- 冒泡階段:從內到外依次觸發相關的行為(我們最常用的就是冒泡階段)
### 0級與冒泡
使用 DOM 【0級】綁定的事件,都是在行為觸發后的冒泡階段把方法執行的
>**注意:**
> 每個瀏覽器傳播到的最頂層是不一樣的,`谷歌`中可以傳播到`document`,但是在`IE`中只能到`html`
>
> 事件對象在傳播過程中是同一個,或則說,一個頁面中的event都是一樣的(想象一下,在ie6~8下,event是在window下的一個屬性)
### 2級與捕獲
```
//第一個參數:行為的類型
//第二個參數:給當前的行為綁定的方法
//第三個參數:控制在哪個階段發生(true捕獲階段,false冒泡階段,默認false)
document.body.addEventListener("click",function(e){
console.log("body");
},false)
```
## 事件委托(事件代理)
**利用**事件的`冒泡傳播機制`,觸發當前元素的某個行為,它父級所有的元素的相關行為都會被觸發,如果一個容器中有很多元素都要綁定點擊事件,我們沒有必要的一個個的綁定了,只需要給最外層的容器綁定一個點擊事件即可,在這個方法執行的時候,通過`事件源`的區分來進行不同的操作。
不論點擊body里的哪一個都會觸發body上的click事件
```
document.body.onclick = function(e){
e = e || window.event;
console.log(e.target||e.srcElement);
}
```
一般應用在點擊隱藏上
## DOM二級事件
### DOM【0級】存在的問題
只能給一個元素的一個行為綁定一個函數
```
box.onclick = function(){}
box.onclick = function(){}
```
### DOM【2級】的優勢
我們使用DOM2事件綁定,其實是讓box通過原型鏈一直找到EventTarget這個內置類原型上的addEventListener方法實現的
#### 1)可以給某一個元素的同一個行為綁定多個不同的方法
```
box.addEventListener("click",function(e){
console.log(1);
},false);
box.addEventListener("click",function(e){
console.log(2);
},false);
```
#### 2)DOM【0級】的行為類型我們用DOM【2級】一樣可以綁定,
#### 3)新增事件【DOMContentLoaded】
而且DOM2中還提供了一些DOM0沒有的行為類型->`DOMContentLoaded`:當頁面中的DOM結構(HTML結構加載完成)觸發的行為
```
//這是指box里的HTML結構加載完成
//貌似box不行?只能document
box.addEventListener("DOMContentLoaded",function(){
},false);
document.addEventListener("DOMContentLoaded",function(){
},false);
```
關于加載完成事件
```
//當頁面中的所有資源都加載完成(圖片、HTML結構、音視頻。。。)才會執行后面的函數;且一個頁面中只能用一次,后面再寫會把前面的覆蓋掉;因為它是采用DOM 0級事件綁定,所以只能綁定一次
window.onload = function(){}
//jquery中是只要當頁面的HTML結構加載完成就會執行對應的函數;并且在同一個頁面中可以出現多次
//說明采用的DOM2 的 DOMContentLoaded
$(document).ready(function(){})->$(function(){})
```
```
//window.onload的 DOM【2級】形式
window.addEventListener("load",function(){},false)
```
### 一些細節
DOM【2級】 一般綁定實名函數,便于移除
```
var handleClick = function(e){
console.log(this);//this->box
};
box.addEventListener("click",handleClick,false)
//->移除的時候需要保證三個參數:行為、方法、哪個階段發生 一點都不能差->在使用DOM2綁定的時候,我們一般都給他綁定的是實名函數
box.removeEventListener("click",handleClick,false)
```
只能給某個元素的同一個行為綁定多個“不同”的方法(如果方法相同了只能留一個)
```
function fn1(){console.log(1)};
box.addEventListener("click",fn1,false);
box.addEventListener("click",fn1,false);
<<<
只會輸出一次1
```
#### 事件池
事件池是用來存儲當前元素行為綁定的方法的(瀏覽器自帶的機制)

yy的>如果要給同一個元素的同一個行為綁定多個不同的方法可以使用數組作為參數傳入
```
function fn1(){}
function fn2(){}
function fn3(){}
box.addEventListener("click",[fn1,fn2,fn3],false)
```
#### 兼容性
在IE6~8中我們不支持addEventListener/removeEventListener,如果想實現DOM2事件綁定只能用attchEvent/detachEvent
它只有兩個參數,不能像addEventListener那樣控制在哪個階段發生,默認只能在冒泡階段發生
且行為需要添加`on`(和DOM【0級】特別像)
```
box.attchEvent("onclick",fn1);
```
>[danger] 此方法綁定的事件的執行順序不一定,是混亂的
>并且可以給同一個元素的同一個行為綁定多次相同的方法
>this指向的是window而不是元素本身
```
/*
* bind:處理DOM2級事件綁定的兼容性問題(綁定)
* curEle ->要綁定的事件的元素
* evenType->要綁定的事件類型("click"、"mouseover"...)
* evenFn->要綁定的方法
*/
function bind(curEle,evenType,evenFn){
if(document.addEventListener){
return curEle.addEventListener(evenType,evenFn,false)
}
curEle.attachEvent("on"+evenType,evenFn);
}
function unbind(curEle,evenType,evenFn){
if(document.addEventListener){
return curEle.removeEventListener(evenType,evenFn,false)
}
curEle.detachEvent("on"+evenType,evenFn);
}
```
如何兼容this?

```
var tempFn = function(){
fn1.call(box);
};
box.attachEvent("onclick",tempFn);
box.detachEvent("onclick",tempFn);
```
```
var tempFn;
function bind(curEle,evenType,evenFn){
if(document.addEventListener){
return curEle.addEventListener(evenType,evenFn,false)
}
//->給evenFn化妝,并且把化妝前的照片貼在自己對應的腦門上
tempFn = function(){
evenFn.call(curEle);
};
tempFn.photo = evenFn;
//->首先判斷該自定義屬性之前是否存在,不存在的話創建一個,由于要存儲多個方法化妝后的結果,所以我們讓其值是一個數字
if(!curEle["myBind"]){
curEle["myBind"] = [];
}
curEle["mybind"].push(tempFn);
curEle.attachEvent("on"+evenType,tempFn);
}
function unbind(curEle,evenType,evenFn){
if(document.addEventListener){
return curEle.removeEventListener(evenType,evenFn,false)
}
var ary = curEle["myBind"];
for(var i=0;i<ary.length;++i){
if(ary[i].photo === evenFn){
curEle.detachEvent("on"+evenType,ary[i]);
break;
}
}
}
```
但這樣不同evenType都放在一起的,So我們最好`curEle["myBind"+evenType]`
然后我們需要這樣解綁

解決重復問題:每一次自己再往自定義屬性對一個的容器中添加前,看一下之前是否已經有了,有的話就不用再重新的添加了,同理也不需要往事件池中存儲了

解決順序問題:我們不用瀏覽器自帶的事件池了,而是自己模擬標準瀏覽器的事件池實現
on:創建事件池,并且把需要給當前元素綁定的方法依次增加到事件池中
on off
```
function on(curEle,eventType,evenFn){
if(!curEle["myEvent"+evenType]){
curEle["myEvent"+evenType] = [];
}
var ary = curEle["myEvent"+evenType];
for(var i=0;i<ary.length;++i){
var cur = ary[i];
if(cur === evenFn){
return;
}
}
ary.push(evenFn);
// curEle.addEventListener(evenType,run,false); //->執行on的時候,我們給當前元素綁定了一個點擊的行為,當點擊的時候執行run方法,run方法中的this是當前元素的curEle,并且瀏覽器給run傳遞了一個MouseEvent事件對象
bind(curEle,evenType,run);
}
function off(curEle,evenType,evenFn){
var ary = curEle["myEvent"+evenType];
for(var i=0;i<ary.length;++i){
ary.spice(ary[i],1);
break;
}
}
//->run: 我們只給當前元素的點擊行為綁定一個方法run,當觸發點擊的時候執行的是run方法,我在run方法中根據自己存儲的方法順序分別的再把這些方法執行
function run(e){
e = e || window.event;
var flag = e.target?true:false; //->IE6~8不兼容target,如果得到flag為false
if(!flag){
e.target = e.srcElement;
e.pageX = e.clientX + (document.documentElement.scrollLeft||document.body.scrollLeft);
e.pageY = e.clientY + (document.documentElement.scrollTop||document.body.scrollTop);
e.preventDefault = function(){
e.returnValue = false;
};
e.stopPropagation = function(){
e.cancelBubble = true;
}
}
//->獲取自己事件池中國綁定的那些方法,并且讓這些方法依次的執行
var ary = e.target["myEvent"+e.type];
for(var i=0;i<ary.length;++i){
var tempFn = ary[i];
tempFn.call(e.target,e); //this,e 也是一樣的,this和e.target這里相等
}
}
```
- 空白目錄
- window
- location
- history
- DOM
- 什么是DOM
- JS盒子模型
- 13個核心屬性
- DOM優化
- 回流與重繪
- 未整理
- 文檔碎片
- DOM映射機制
- DOM庫封裝
- 事件
- 功能組件
- table
- 圖片延遲加載
- 跑馬燈
- 回到頂部
- 選項卡
- 鼠標跟隨
- 放大鏡
- 搜索
- 多級菜單
- 拖拽
- 瀑布流
- 數據類型的核心操作原理
- 變量提升
- 閉包(scope)
- this
- 練習題
- 各種數據類型下的常用方法
- JSON
- 數組
- object
- oop
- 單例模式
- 高級單例模式
- JS中常用的內置類
- 基于面向對象創建數據值
- 原型和原型鏈
- 可枚舉和不可枚舉
- Object.create
- 繼承的六種方式
- ES6下一代js標準
- babel
- 箭頭函數
- 對象
- es6勉強筆記
- 流程控制
- switch
- Ajax
- eval和()括號表達式
- 異常信息捕獲
- 邏輯與和或以及前后自增
- JS中的異步編程思想
- 上云
- 優化技巧
- 跨域與JSONP
- 其它跨域相關問題
- console
- HTML、XHTML、XML
- jQuery
- zepto
- 方法重寫和方法重載
- 移動端
- 響應式布局開發基礎
- 項目一:創意簡歷