實(shí)現(xiàn)通用的事件注冊(cè)方法


title: 實(shí)現(xiàn)通用的事件注冊(cè)方法
date: 2017-07-15
tags: [原生js]
categories: js


初稿:2017-07-15

更新: 2018-07-29

實(shí)現(xiàn)通用的事件注冊(cè)方法

為什么要討論事件注冊(cè)的兼容性?

由于歷史原因,不同的瀏覽器對(duì)事件注冊(cè)的實(shí)現(xiàn)方式有些許差異。

事件注冊(cè)有哪些方法?

on+"event"

例如:onclick/onmouseover/onmouseenter/...支持最廣,筆者用的最多,倘若要在一個(gè)元素上添加多次同一事件,此時(shí)就顯得無(wú)能為力了,以最后一次綁定的事件為準(zhǔn)。

addEventListener

W3C 標(biāo)準(zhǔn)方法,功能也最強(qiáng)大,支持添加多個(gè)事件

//element.addEventListener(type,listener,useCapture);
obj.addEventListener('click', method1, false)
obj.addEventListener('click', method2, false)
obj.addEventListener('click', method3, false)

執(zhí)行順序?yàn)?method1->method2->method3,第三個(gè)參數(shù)是指以“冒泡”還是“捕獲”的標(biāo)準(zhǔn)綁定事件,一般為 false(冒泡).

并且可以使用 removeEventListener() 方法移除由 addEventListener()方法添加的事件句柄。

注意: 如果要移除事件句柄,addEventListener() 的執(zhí)行函數(shù)必須使用外部函數(shù),如上實(shí)例所示 (method1/2/3)。
匿名函數(shù),類似 "window.removeEventListener("event", function(){ myScript });" 該事件是無(wú)法移除的。

如果瀏覽器不支持 removeEventListener() 方法,你可以使用 detachEvent() 方法實(shí)現(xiàn)。

var x = window.getElementById('myDIV')
if (x.removeEventListener) {
  // // 所有瀏覽器,除了 IE 8 及更早IE版本
  x.removeEventListener('mousemove', myFunction)
} else if (x.detachEvent) {
  // IE 8 及更早IE版本
  x.detachEvent('onmousemove', myFunction)
}

attachEvent

IE 家的方法,火狐與其他家瀏覽器都不支持,attachEvent——兼容:IE7、IE8;不兼容 firefox、chrome、IE9、IE10、IE11、safari、opera.盡量不要用,支持綁定多個(gè)事件,與 addEventListener()執(zhí)行順序相反,即 method3->method2->method1

進(jìn)行兼容性處理

1.簡(jiǎn)單通過(guò) if 判斷

if (window.addEventListener) {
  //功能最強(qiáng)大
  div.addEventListener('click', function() {
    alert('hello,world')
  })
} else if (window.attachEvent) {
  //非標(biāo)準(zhǔn)特性 盡量不要使用
  div.attachEvent('click', function() {
    alert('hello,world')
  })
} else {
  //支持最好
  div['onclick'] = function() {
    alert('hello,world')
  }
}

2.封裝成函數(shù)

function registeEvent(elem, type, handler, useCapture) {
  if (window.addEventListener) {
    //功能最強(qiáng)大
    elem.addEventListener(type, handler, useCapture)
  } else if (window.attachEvent) {
    //非標(biāo)準(zhǔn)特性 盡量不要使用
    elem.attachEvent(type, handler)
  } else {
    //支持最好
    elem['on' + type] = handler
  }
}

到這里,我們每次注冊(cè)事件時(shí)都通過(guò) registeEvent 注冊(cè),很明顯,每次注冊(cè)都要判斷瀏覽器的能力是否支持,每一次都要檢測(cè),這不是我們想要的。

3. 使用立即執(zhí)行函數(shù)進(jìn)行優(yōu)化

var registeEvent = (function createEventRegister() {
  if (window.addEventListener) {
    return function(elem, type, handler, useCapture) {
      elem.addEventListener(type, handler, useCapture)
    }
  } else if (window.attachEvent) {
    return function(elem, type, handler) {
      elem.attachEvent(type, function() {
        handler.call(elem, window.event) //注:attachEvent內(nèi)部this指向window而不是觸發(fā)對(duì)象,使用call方法修改this
      })
    }
  } else {
    return function(elem, type, handler) {
      elem['on' + type] = handler
    }
  }
})()

此后只用通過(guò) registeEvent 方法注冊(cè)事件即可,且只在最開(kāi)始的時(shí)候進(jìn)行一次能力檢測(cè).

registeEvent(div, 'click', function() {
  alert('hello,world')
})

但是此時(shí)依舊存在一個(gè)缺點(diǎn),如果我們從頭到尾沒(méi)有綁定過(guò)事件,即使用 registeEvent 函數(shù),那么立即執(zhí)行函數(shù)白白執(zhí)行了一次完全是多余的(或許有些吹毛求疵),我們可以使用惰性載入函數(shù)來(lái)進(jìn)行優(yōu)化。

4. 惰性載入函數(shù)方案

此時(shí) registerEvent 依然被聲明為一個(gè)普通函數(shù),在函數(shù)里依然有一些分支判斷。但是在第一次進(jìn)入條件分支之后,在函數(shù)內(nèi)部會(huì)重寫這個(gè)函數(shù),重寫之后的函數(shù)就是我們期望的 registerEvent 函數(shù),在下一次進(jìn)入 registerEvent registerEvent 函數(shù)里不再存在條件分支語(yǔ)句:

var registeEvent = function createEventRegister() {
  if (window.addEventListener) {
    registeEvent = function(elem, type, handler, useCapture) {
      elem.addEventListener(type, handler, useCapture)
    }
  } else if (window.attachEvent) {
    registeEvent = function(elem, type, handler) {
      elem.attachEvent(type, function() {
        handler.call(elem, window.event) //注:attachEvent內(nèi)部this指向window而不是觸發(fā)對(duì)象,使用call方法修改this
      })
    }
  } else {
    registeEvent = function(elem, type, handler) {
      elem['on' + type] = handler
    }
  }
}

附上一些兼容性解決方案 EventUtil:

var EventUtil = {
  addEvent: function(element, type, handler) {
    // 添加綁定
    if (element.addEventListener) {
      // 使用DOM2級(jí)方法添加事件
      element.addEventListener(type, handler, false)
    } else if (element.attachEvent) {
      // 使用IE方法添加事件
      element.attachEvent('on' + type, handler)
    } else {
      // 使用DOM0級(jí)方法添加事件
      element['on' + type] = handler
    }
  },
  // 移除事件
  removeEvent: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false)
    } else if (element.datachEvent) {
      element.detachEvent('on' + type, handler)
    } else {
      element['on' + type] = null
    }
  },
  getEvent: function(event) {
    // 返回事件對(duì)象引用
    return event ? event : window.event
  },
  // 獲取mouseover和mouseout相關(guān)元素
  getRelatedTarget: function(event) {
    if (event.relatedTarget) {
      return event.relatedTarget
    } else if (event.toElement) {
      // 兼容IE8-
      return event.toElement
    } else if (event.formElement) {
      return event.formElement
    } else {
      return null
    }
  },
  getTarget: function(event) {
    //返回事件源目標(biāo)
    return event.target || event.srcElement
  },
  preventDefault: function(event) {
    //取消默認(rèn)事件
    if (event.preventDefault) {
      event.preventDefault()
    } else {
      event.returnValue = false
    }
  },
  stoppropagation: function(event) {
    //阻止事件流
    if (event.stoppropagation) {
      event.stoppropagation()
    } else {
      event.canceBubble = false
    }
  },
  // 獲取mousedown或mouseup按下或釋放的按鈕是鼠標(biāo)中的哪一個(gè)
  getButton: function(event) {
    if (document.implementation.hasFeature('MouseEvents', '2.0')) {
      return event.button
    } else {
      //將IE模型下的button屬性映射為DOM模型下的button屬性
      switch (event.button) {
        case 0:
        case 1:
        case 3:
        case 5:
        case 7:
          //按下的是鼠標(biāo)主按鈕(一般是左鍵)
          return 0
        case 2:
        case 6:
          //按下的是中間的鼠標(biāo)按鈕
          return 2
        case 4:
          //鼠標(biāo)次按鈕(一般是右鍵)
          return 1
      }
    }
  },
  //獲取表示鼠標(biāo)滾輪滾動(dòng)方向的數(shù)值
  getWheelDelta: function(event) {
    if (event.wheelDelta) {
      return event.wheelDelta
    } else {
      return -event.detail * 40
    }
  },
  // 以跨瀏覽器取得相同的字符編碼,需在keypress事件中使用
  getCharCode: function(event) {
    if (typeof event.charCode == 'number') {
      return event.charCode
    } else {
      return event.keyCode
    }
  }
}

參考書籍:《JavaScript 高級(jí)程序設(shè)計(jì)第三版》《JavaScript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》
參考文章:手把手教你用原生 JavaScript 造輪子(1)——分頁(yè)器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 本文主要談及問(wèn)題: 關(guān)于編寫跨瀏覽器的事件處理函數(shù)和事件對(duì)象 關(guān)于編寫跨瀏覽器的事件處理函數(shù)和事件對(duì)象 為什么要編...
    JimmyChung閱讀 2,141評(píng)論 0 4
  • 什么是事件: 我們可以簡(jiǎn)單的把事件理解為瀏覽器的感知系統(tǒng)。比如說(shuō):他可以感覺(jué)到用戶是否點(diǎn)擊(click)了頁(yè)面、鼠...
    張松1366閱讀 6,818評(píng)論 1 6
  • dom對(duì)象的innerText和innerHTML有什么區(qū)別? innerHTML指的是從對(duì)象的起始位置到終止位置...
    coolheadedY閱讀 496評(píng)論 0 0
  • 什么是事件: 事件是交互體驗(yàn)的核心功能 一.事件冒泡: 當(dāng)一個(gè)事件發(fā)生時(shí),這個(gè)事件會(huì)從內(nèi)向外逐層傳遞。 二.為什么...
    輕描淡寫mua閱讀 522評(píng)論 0 0
  • 以下文章為轉(zhuǎn)載,對(duì)理解JavaScript中的事件處理機(jī)制很有幫助,淺顯易懂,特分享于此。 什么是事件? 事件(E...
    jxyjxy閱讀 3,057評(píng)論 1 10