溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Javascript事件機(jī)制兼容性解決方法是什么

發(fā)布時(shí)間:2021-11-17 15:52:12 來源:億速云 閱讀:121 作者:iii 欄目:web開發(fā)

本篇內(nèi)容主要講解“Javascript事件機(jī)制兼容性解決方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Javascript事件機(jī)制兼容性解決方法是什么”吧!

本文的解決方案可以用于Javascript native對(duì)象和宿主對(duì)象(dom元素),通過以下的方式來綁定和觸發(fā)事件:

Javascript事件機(jī)制兼容性解決方法是什么

或者

var input = document.getElementsByTagName('input')[0];  var form = document.getElementsByTagName('form')[0];  Evt.on(input, 'click', function(evt){      console.log('input click1');      console.log(evt.target === input);      console.log(evt.modified);      //evt.stopPropagation();      console.log(evt.modified);  });  var handle2 = Evt.on(input, 'click', function(evt){      console.log('input click2');      console.log(evt.target === input);      console.log(evt.modified);  });  Evt.on(form, 'click', function(evt){      console.log('form click');      console.log(evt.currentTarget === input);      console.log(evt.target === input);      console.log(evt.currentTarget === form);      console.log(evt.modified);  });  Evt.emit(input, 'click');  Evt.emit(input, 'click', {bubbles: true});  handle2.remove();  Evt.emit(input, 'click');

After函數(shù)

為native對(duì)象添加事件的過程主要在after函數(shù)中完成,這個(gè)函數(shù)主要做了以下幾件事:

  1. 如果obj中已有響應(yīng)函數(shù),將其替換成dispatcher函數(shù)

  2. 使用鏈?zhǔn)浇Y(jié)構(gòu),保證多次綁定事件函數(shù)的順序執(zhí)行

  3. 返回一個(gè)handle對(duì)象,調(diào)用remove方法可以去除本次事件綁定

下圖為after函數(shù)調(diào)用前后onlog函數(shù)的引用

Javascript事件機(jī)制兼容性解決方法是什么

(調(diào)用前)

Javascript事件機(jī)制兼容性解決方法是什么

(調(diào)用后)

詳細(xì)解釋請(qǐng)看注釋,希望讀者能夠跟著運(yùn)行一遍

var after = function(target, method, cb, originalArgs){      var existing = target[method];      var dispatcher = existing;      if (!existing || existing.target !== target) {          //如果target中沒有method方法,則為他添加一個(gè)方法method方法          //如果target已經(jīng)擁有method方法,但target[method]中target不符合要求則將method方法他替換          dispatcher = target[method] = function(){              //由于js是此法作用域:通過閱讀包括變量定義在內(nèi)的數(shù)行源碼就能知道變量的作用域。              //局部變量在聲明它的函數(shù)體內(nèi)以及其所嵌套的函數(shù)內(nèi)始終是有定義的              //所以在這個(gè)函數(shù)中可以訪問到dispatcher變量              var results = null;              var args = arguments;              if (dispatcher.around) {//如果原先擁有method方法,先調(diào)用原始method方法                  //此時(shí)this關(guān)鍵字指向target所以不用target                  results = dispatcher.around.advice.apply(this, args);              }                            if (dispatcher.after) {//如果存在after鏈則依次訪問其中的advice方法                  var _after = dispatcher.after;                  while(_after && _after.advice) {                      //如果需要原始參數(shù)則傳入arguments否則使用上次執(zhí)行結(jié)果作為參數(shù)                      args = _after.originalArgs ? arguments : results;                      results = _after.advice.apply(this, args);                      _after = _after.next;                  }              }          }                    if (existing) {          //函數(shù)也是對(duì)象,也可以擁有屬性跟方法          //這里將原有的method方法放到dispatcher中              dispatcher.around = {                  advice: function(){                      return existing.apply(target, arguments);                  }              }          }          dispatcher.target = target;      }                  var signal = {          originalArgs: originalArgs,//對(duì)于每個(gè)cb的參數(shù)是否使用最初的arguments          advice: cb,          remove: function() {              if (!signal.advice) {                  return;              }              //remove的本質(zhì)是將cb從函數(shù)鏈中移除,刪除所有指向他的鏈接              var previous = signal.previous;              var next = signal.next;              if (!previous && !next) {                  dispatcher.after = signal.advice = null;                  dispatcher.target = null;                  delete dispatcher.after;              } else if (!next){                  signal.advice = null;                  previous.next = null;                  signal.previous = null;              } else if (!previous){                  signal.advice = null;                  dispatcher.after = next;                  next.previous = null;                  signal.next = null;              } else {                  signal.advice = null;                  previous.next = next;                  next.previous = previous;                  signal.previous = null;                  signal.next = null;              }          }      }                  var previous = dispatcher.after;      if (previous) {//將signal加入到鏈?zhǔn)浇Y(jié)構(gòu)中,處理指針關(guān)系          while(previous && previous.next && (previous = previous.next)){};          previous.next = signal;          signal.previous = previous;      } else {//如果是***次使用調(diào)用after方法,則dispatcher的after屬性指向signal          dispatcher.after = signal;      }            cb = null;//防止內(nèi)存泄露      return signal;  }

解決兼容性

IE瀏覽器從IE9開始已經(jīng)支持DOM2事件處理程序,但是對(duì)于老版本的ie瀏覽器,任然使用attachEvent方式來為dom元素添加事件。值得慶幸的是微軟已宣布2016年將不再對(duì)ie8進(jìn)行維護(hù),對(duì)于廣大前端開發(fā)者無疑是一個(gè)福音。然而在曙光來臨之前,仍然需要對(duì)那些不支持DOM2級(jí)事件處理程序的瀏覽器進(jìn)行兼容性處理,通常需要處理以下幾點(diǎn):

  1. 多次綁定一個(gè)事件,事件處理函數(shù)的調(diào)用順序問題

  2. 事件處理函數(shù)中的this關(guān)鍵字指向問題

  3. 標(biāo)準(zhǔn)化event事件對(duì)象,支持常用的事件屬性

由于使用attachEvent方法添加事件處理函數(shù)無法保證事件處理函數(shù)的調(diào)用順序,所以我們棄用attachEvent,轉(zhuǎn)而用上文中的after生成的正序鏈?zhǔn)浇Y(jié)構(gòu)來解決這個(gè)問題。

//1、統(tǒng)一事件觸發(fā)順序      function fixAttach(target, type, listener) {      debugger;          var listener = fixListener(listener);          var method = 'on' + type;          return after(target, method, listener, true);      };

對(duì)于事件處理函數(shù)中的this關(guān)鍵字指向,通過閉包即可解決(出處),如:

Javascript事件機(jī)制兼容性解決方法是什么

本文也是通過這種方式解決此問題

//1、統(tǒng)一事件觸發(fā)順序      function fixAttach(target, type, listener) {      debugger;          var listener = fixListener(listener);          var method = 'on' + type;          return after(target, method, listener, true);      };            function fixListener(listener) {          return function(evt){              //每次調(diào)用listenser之前都會(huì)調(diào)用fixEvent              debugger;              var e = _fixEvent(evt, this);//this作為currentTarget              if (e && e.cancelBubble && (e.currentTarget !== e.target)){                  return;              }              var results =  listener.call(this, e);               if (e && e.modified) {                  // 在整個(gè)函數(shù)鏈執(zhí)行完成后將lastEvent回歸到原始狀態(tài),                  //利用異步隊(duì)列,在主程序執(zhí)行完后再執(zhí)行事件隊(duì)列中的程序代碼                  //常規(guī)的做法是在emit中判斷l(xiāng)astEvent并設(shè)為null                  //這充分體現(xiàn)了js異步編程的優(yōu)勢(shì),把變量賦值跟清除代碼放在一起,避免邏輯分散,缺點(diǎn)是不符合程序員正常思維方式                  if(!lastEvent){                      setTimeout(function(){                          lastEvent = null;                      });                  }                  lastEvent = e;              }              return results;          }      }

對(duì)于事件對(duì)象的標(biāo)準(zhǔn)化,我們需要將ie提供給我們的現(xiàn)有屬性轉(zhuǎn)化為標(biāo)準(zhǔn)的事件屬性。

function _fixEvent(evt, sender){          if (!evt) {              evt = window.event;          }          if (!evt) { // emit沒有傳遞事件參數(shù),或者通過input.onclick方式調(diào)用              return evt;          }          if(lastEvent && lastEvent.type && evt.type == lastEvent.type){          //使用一個(gè)全局對(duì)象來保證在冒泡過程中訪問的是同一個(gè)event對(duì)象          //chrome中整個(gè)事件處理過程event是***的              evt = lastEvent;          }          var fixEvent = evt;          // bubbles 和cancelable根據(jù)每次emit時(shí)手動(dòng)傳入?yún)?shù)設(shè)置          fixEvent.bubbles = typeof evt.bubbles !== 'undefined' ? evt.bubbles : false;          fixEvent.cancelable = typeof evt.cancelable !== 'undefined' ? evt.cancelable : true;          fixEvent.currentTarget = sender;          if (!fixEvent.target){ // 多次綁定統(tǒng)一事件,只fix一次              fixEvent.target = fixEvent.srcElement || sender;                            fixEvent.eventPhase = fixEvent.target === sender ? 2 : 3;              if (!fixEvent.preventDefault) {                  fixEvent.preventDefault = _preventDefault;                  fixEvent.stopPropagation = _stopPropagation;                  fixEvent.stopImmediatePropagation = _stopImmediatePropagation;              }              //參考:http://www.nowamagic.net/javascript/js_EventMechanismInDetail.php              if( fixEvent.pageX == null && fixEvent.clientX != null ) {                  var doc = document.documentElement, body = document.body;                  fixEvent.pageX = fixEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);                  fixEvent.pageY = fixEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);              }              if (!fixEvent.relatedTarget && fixEvent.fromEvent) {                  fixEvent.relatedTarget = fixEvent.fromEvent === fixEvent.target ? fixEvent.toElement : fixEvent.fromElement;              }              // 參考: http://www.cnblogs.com/hsapphire/archive/2009/12/18/1627047.html              if (!fixEvent.which && fixEvent.keyCode) {                  fixEvent.which = fixEvent.keyCode;              }          }                    return fixEvent;      }            function _preventDefault(){          this.defaultPrevented = true;          this.returnValue = false;           this.modified = true;      }            function _stopPropagation(){          this.cancelBubble = true;           this.modified = true;      }            function _stopImmediatePropagation(){          this.isStopImmediatePropagation = true;          this.modified = true;      }

在_preventDefault、_stopPropagation、_stopImmediatePropagation三個(gè)函數(shù)中我們,如果被調(diào)用則listener執(zhí)行完后使用一個(gè)變量保存event對(duì)象(見fixListener),以便后序事件處理程序根據(jù)event對(duì)象屬性進(jìn)行下一步處理。stopImmediatePropagation函數(shù),對(duì)于這個(gè)函數(shù)的模擬,我們同樣通過閉包來解決。

Javascript事件機(jī)制兼容性解決方法是什么

Javascript事件機(jī)制兼容性解決方法是什么

注意這里不能直接寫成這種形式,上文中fixListener也是同樣道理。

Javascript事件機(jī)制兼容性解決方法是什么

需要注意一點(diǎn),我們將event標(biāo)準(zhǔn)化目的還有一點(diǎn),可以在emit方法中設(shè)置參數(shù)來控制事件過程,比如:

Evt.emit(input, 'click');//不冒泡  Evt.emit(input, 'click', {bubbles: true});//冒泡

根據(jù)我的測(cè)試使用fireEvent方式觸發(fā)事件,無法設(shè)置{bubbles:false}來阻止冒泡,所以這里我們用Javascript來模擬冒泡過程。同時(shí)在這個(gè)過程中也要保證event對(duì)象的***性。

// 模擬冒泡事件      var sythenticBubble = function(target, type, evt){          var method = 'on' + type;          var args = Array.prototype.slice.call(arguments, 2);          // 保證使用emit觸發(fā)dom事件時(shí),event的有效性          if ('parentNode' in target) {              var newEvent = args[0] = {};              for (var p in evt) {                  newEvent[p] = evt[p];              }                            newEvent.preventDefault = _preventDefault;              newEvent.stopPropagation = _stopPropagation;              newEvent.stopImmediatePropagation = _stopImmediatePropagation;              newEvent.target = target;              newEvent.type = type;          }                    do{              if (target && target[method]) {                  target[method].apply(target, args);              }          }while(target && (target = target.parentNode) && target[method] && newEvent && newEvent.bubbles);      }            var emit = function(target, type, evt){          if (target.dispatchEvent && document.createEvent){              var newEvent = document.createEvent('HTMLEvents');              newEvent.initEvent(type, evt && !!evt.bubbles, evt && !!evt.cancelable);              if (evt) {                  for (var p in evt){                      if (!(p in newEvent)){                          newEvent[p] = evt[p];                      }                  }              }                            target.dispatchEvent(newEvent);          } /*else if (target.fireEvent) {              target.fireEvent('on' + type);// 使用fireEvent在evt參數(shù)中設(shè)置bubbles:false無效,所以棄用          } */else {              return sythenticBubble.apply(on, arguments);          }      }

附上完整代碼:

<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <meta http-equiv="window-target" content="_top"> <title>Writing to Same Doc</title> <script language="JavaScript"> var after = function(target, method, cb, originalArgs){      var existing = target[method];      var dispatcher = existing;      if (!existing || existing.target !== target) {          //如果target中沒有method方法,則為他添加一個(gè)方法method方法          //如果target已經(jīng)擁有method方法,但target[method]中target不符合要求則將method方法他替換          dispatcher = target[method] = function(){              //由于js是此法作用域:通過閱讀包括變量定義在內(nèi)的數(shù)行源碼就能知道變量的作用域。              //局部變量在聲明它的函數(shù)體內(nèi)以及其所嵌套的函數(shù)內(nèi)始終是有定義的              //所以在這個(gè)函數(shù)中可以訪問到dispatcher變量              var results = null;              var args = arguments;              if (dispatcher.around) {//如果原先擁有method方法,先調(diào)用原始method方法                  //此時(shí)this關(guān)鍵字指向target所以不用target                  results = dispatcher.around.advice.apply(this, args);              }                            if (dispatcher.after) {//如果存在after鏈則依次訪問其中的advice方法                  var _after = dispatcher.after;                  while(_after && _after.advice) {                      //如果需要原始參數(shù)則傳入arguments否則使用上次執(zhí)行結(jié)果作為參數(shù)                      args = _after.originalArgs ? arguments : results;                      results = _after.advice.apply(this, args);                      _after_after = _after.next;                  }              }          }                    if (existing) {          //函數(shù)也是對(duì)象,也可以擁有屬性跟方法          //這里將原有的method方法放到dispatcher中              dispatcher.around = {                  advice: function(){                      return existing.apply(target, arguments);                  }              }          }          dispatcher.target = target;      }                  var signal = {          originalArgs: originalArgs,//對(duì)于每個(gè)cb的參數(shù)是否使用最初的arguments          advice: cb,          remove: function() {              if (!signal.advice) {                  return;              }              //remove的本質(zhì)是將cb從函數(shù)鏈中移除,刪除所有指向他的鏈接              var previous = signal.previous;              var next = signal.next;              if (!previous && !next) {                  dispatcher.after = signal.advice = null;                  dispatcher.target = null;                  delete dispatcher.after;              } else if (!next){                  signal.advice = null;                  previous.next = null;                  signal.previous = null;              } else if (!previous){                  signal.advice = null;                  dispatcher.after = next;                  next.previous = null;                  signal.next = null;              } else {                  signal.advice = null;                  previous.next = next;                  next.previous = previous;                  signal.previous = null;                  signal.next = null;              }          }      }                  var previous = dispatcher.after;      if (previous) {//將signal加入到鏈?zhǔn)浇Y(jié)構(gòu)中,處理指針關(guān)系          while(previous && previous.next && (previousprevious = previous.next)){};          previous.next = signal;          signal.previous = previous;      } else {//如果是***次使用調(diào)用after方法,則dispatcher的after屬性指向signal          dispatcher.after = signal;      }            cb = null;//防止內(nèi)存泄露      return signal;  }   //1、統(tǒng)一事件觸發(fā)順序  //2、標(biāo)準(zhǔn)化事件對(duì)象  //3、模擬冒泡 emit時(shí)保持冒泡行為,注意input.onclick這種方式是不冒泡的  //4、保持冒泡過程中event的***性   window.Evt = (function(){      var on = function(target, type, listener){      debugger;          if (!listener){              return;          }          // 處理stopImmediatePropagation,通過包裝listener來支持stopImmediatePropagation          if (!(window.Event && window.Event.prototype && window.Event.prototype.stopImmediatePropagation)) {              listener = _addStopImmediate(listener);          }                if (target.addEventListener) {              target.addEventListener(type, listener, false);                            return {                  remove: function(){                      target.removeEventListener(type, listener);                  }              }          } else {              return fixAttach(target, type, listener);          }      };      var lastEvent; // 使用全局變量來保證一個(gè)元素的多個(gè)listenser中事件對(duì)象的一致性,冒泡過程中事件對(duì)象的一致性;在chrome這些過程中使用的是同一個(gè)event      //1、統(tǒng)一事件觸發(fā)順序      function fixAttach(target, type, listener) {      debugger;          var listener = fixListener(listener);          var method = 'on' + type;          return after(target, method, listener, true);      };            function fixListener(listener) {          return function(evt){              //每次調(diào)用listenser之前都會(huì)調(diào)用fixEvent              debugger;              var e = _fixEvent(evt, this);//this作為currentTarget              if (e && e.cancelBubble && (e.currentTarget !== e.target)){                  return;              }              var results =  listener.call(this, e);               if (e && e.modified) {                  // 在整個(gè)函數(shù)鏈執(zhí)行完成后將lastEvent回歸到原始狀態(tài),                  //利用異步隊(duì)列,在主程序執(zhí)行完后再執(zhí)行事件隊(duì)列中的程序代碼                  //常規(guī)的做法是在emit中判斷l(xiāng)astEvent并設(shè)為null                  //這充分體現(xiàn)了js異步編程的優(yōu)勢(shì),把變量賦值跟清除代碼放在一起,避免邏輯分散,缺點(diǎn)是不符合程序員正常思維方式                  if(!lastEvent){                      setTimeout(function(){                          lastEvent = null;                      });                  }                  lastEvent = e;              }              return results;          }      }            function _fixEvent(evt, sender){          if (!evt) {              evt = window.event;          }          if (!evt) { // emit沒有傳遞事件參數(shù),或者通過input.onclick方式調(diào)用              return evt;          }          if(lastEvent && lastEvent.type && evt.type == lastEvent.type){          //使用一個(gè)全局對(duì)象來保證在冒泡過程中訪問的是同一個(gè)event對(duì)象          //chrome中整個(gè)事件處理過程event是***的              evt = lastEvent;          }          var fixEvent = evt;          // bubbles 和cancelable根據(jù)每次emit時(shí)手動(dòng)傳入?yún)?shù)設(shè)置          fixEvent.bubbles = typeof evt.bubbles !== 'undefined' ? evt.bubbles : false;          fixEvent.cancelable = typeof evt.cancelable !== 'undefined' ? evt.cancelable : true;          fixEvent.currentTarget = sender;          if (!fixEvent.target){ // 多次綁定統(tǒng)一事件,只fix一次              fixEventfixEvent.target = fixEvent.srcElement || sender;                            fixEventfixEvent.eventPhase = fixEvent.target === sender ? 2 : 3;              if (!fixEvent.preventDefault) {                  fixEvent.preventDefault = _preventDefault;                  fixEvent.stopPropagation = _stopPropagation;                  fixEvent.stopImmediatePropagation = _stopImmediatePropagation;              }              //參考:http://www.nowamagic.net/javascript/js_EventMechanismInDetail.php              if( fixEvent.pageX == null && fixEvent.clientX != null ) {                  var doc = document.documentElement, body = document.body;                  fixEventfixEvent.pageX = fixEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);                  fixEventfixEvent.pageY = fixEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);              }              if (!fixEvent.relatedTarget && fixEvent.fromEvent) {                  fixEventfixEvent.relatedTarget = fixEvent.fromEvent === fixEvent.target ? fixEvent.toElement : fixEvent.fromElement;              }              // 參考: http://www.cnblogs.com/hsapphire/archive/2009/12/18/1627047.html              if (!fixEvent.which && fixEvent.keyCode) {                  fixEventfixEvent.which = fixEvent.keyCode;              }          }                    return fixEvent;      }            function _preventDefault(){          this.defaultPrevented = true;          this.returnValue = false;           this.modified = true;      }            function _stopPropagation(){          this.cancelBubble = true;           this.modified = true;      }            function _stopImmediatePropagation(){          this.isStopImmediatePropagation = true;          this.modified = true;      }            function _addStopImmediate(listener) {          return function(evt) { // 除了包裝listener外,還要保證所有的事件函數(shù)共用一個(gè)evt對(duì)象              if (!evt.isStopImmediatePropagation) {                  //evt.stopImmediatePropagation = _stopImmediateProgation;                  return listener.apply(this, arguments);              }          }      }            // 模擬冒泡事件      var sythenticBubble = function(target, type, evt){          var method = 'on' + type;          var args = Array.prototype.slice.call(arguments, 2);          // 保證使用emit觸發(fā)dom事件時(shí),event的有效性          if ('parentNode' in target) {              var newEvent = args[0] = {};              for (var p in evt) {                  newEvent[p] = evt[p];              }                            newEvent.preventDefault = _preventDefault;              newEvent.stopPropagation = _stopPropagation;              newEvent.stopImmediatePropagation = _stopImmediatePropagation;              newEvent.target = target;              newEvent.type = type;          }                    do{              if (target && target[method]) {                  target[method].apply(target, args);              }          }while(target && (targettarget = target.parentNode) && target[method] && newEvent && newEvent.bubbles);      }            var emit = function(target, type, evt){          if (target.dispatchEvent && document.createEvent){              var newEvent = document.createEvent('HTMLEvents');              newEvent.initEvent(type, evt && !!evt.bubbles, evt && !!evt.cancelable);              if (evt) {                  for (var p in evt){                      if (!(p in newEvent)){                          newEvent[p] = evt[p];                      }                  }              }                            target.dispatchEvent(newEvent);          } /*else if (target.fireEvent) {              target.fireEvent('on' + type);// 使用fireEvent在evt參數(shù)中設(shè)置bubbles:false無效,所以棄用          } */else {              return sythenticBubble.apply(on, arguments);          }      }            return {          on: on,          emit: emit      };  })()  </script> <style type="text/css"></style> </head> <body>   <form>     <input type="button" value="Replace Content" >   </form> </body> </html>

到此,相信大家對(duì)“Javascript事件機(jī)制兼容性解決方法是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI