Javascript事件机制兼容性解决方案
本文的解决方案可以用于Javascript native对象和宿主对象(dom元素),通过以下的方式来绑定和触发事件:
或者
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函数
为native对象添加事件的过程主要在after函数中完成,这个函数主要做了以下几件事:
如果obj中已有响应函数,将其替换成dispatcher函数 使用链式结构,保证多次绑定事件函数的顺序执行 返回一个handle对象,调用remove方法可以去除本次事件绑定下图为after函数调用前后onlog函数的引用
(调用前)
(调用后)
详细解释请看注释,希望读者能够跟着运行一遍
var after = function(target, method, cb, originalArgs){ var existing = target[method]; var dispatcher = existing; if (!existing || existing.target !== target) { //如果target中没有method方法,则为他添加一个方法method方法 //如果target已经拥有method方法,但target[method]中target不符合要求则将method方法他替换 dispatcher = target[method] = function(){ //由于js是此法作用域:通过阅读包括变量定义在内的数行源码就能知道变量的作用域。 //局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的 //所以在这个函数中可以访问到dispatcher变量 var results = null; var args = arguments; if (dispatcher.around) {//如果原先拥有method方法,先调用原始method方法 //此时this关键字指向target所以不用target results = dispatcher.around.advice.apply(this, args); if (dispatcher.after) {//如果存在after链则依次访问其中的advice方法 var _after = dispatcher.after; while(_after _after.advice) { //如果需要原始参数则传入arguments否则使用上次执行结果作为参数 args = _after.originalArgs ? arguments : results; results = _after.advice.apply(this, args); _after = _after.next; if (existing) { //函数也是对象,也可以拥有属性跟方法 //这里将原有的method方法放到dispatcher中 dispatcher.around = { advice: function(){ return existing.apply(target, arguments); dispatcher.target = target;
//remove的本质是将cb从函数链中移除,删除所有指向他的链接 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加入到链式结构中,处理指针关系 while(previous previous.next (previous = previous.next)){}; previous.next = signal; signal.previous = previous; } else {//如果是第一次使用调用after方法,则dispatcher的after属性指向signal dispatcher.after = signal; cb = null;//防止内存泄露 return signal; }
解决兼容性
IE浏览器从IE9开始已经支持DOM2事件处理程序,但是对于老版本的ie浏览器,任然使用attachEvent方式来为dom元素添加事件。值得庆幸的是微软已宣布2016年将不再对ie8进行维护,对于广大前端开发者无疑是一个福音。然而在曙光来临之前,仍然需要对那些不支持DOM2级事件处理程序的浏览器进行兼容性处理,通常需要处理以下几点:
多次绑定一个事件,事件处理函数的调用顺序问题 事件处理函数中的this关键字指向问题 标准化event事件对象,支持常用的事件属性
由于使用attachEvent方法添加事件处理函数无法保证事件处理函数的调用顺序,所以我们弃用attachEvent,转而用上文中的after生成的正序链式结构来解决这个问题。
//1、统一事件触发顺序 function fixAttach(target, type, listener) { debugger; var listener = fixListener(listener); var method = on + type; return after(target, method, listener, true); };
对于事件处理函数中的this关键字指向,通过闭包即可解决(出处),如:
本文也是通过这种方式解决此问题
//1、统一事件触发顺序 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){ //每次调用listenser之前都会调用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) { // 在整个函数链执行完成后将lastEvent回归到原始状态, //利用异步队列,在主程序执行完后再执行事件队列中的程序代码 //常规的做法是在emit中判断lastEvent并设为null //这充分体现了js异步编程的优势,把变量赋值跟清除代码放在一起,避免逻辑分散,缺点是不符合程序员正常思维方式 if(!lastEvent){ setTimeout(function(){ lastEvent = null; lastEvent = e; return results; }
对于事件对象的标准化,我们需要将ie提供给我们的现有属性转化为标准的事件属性。
function _fixEvent(evt, sender){ if (!evt) { evt = window.event; if (!evt) { // emit没有传递事件参数,或者通过input.onclick方式调用 return evt; if(lastEvent lastEvent.type evt.type == lastEvent.type){ //使用一个全局对象来保证在冒泡过程中访问的是同一个event对象 //chrome中整个事件处理过程event是唯一的 evt = lastEvent; var fixEvent = evt; // bubbles 和cancelable根据每次emit时手动传入参数设置 fixEvent.bubbles = typeof evt.bubbles !== undefined ? evt.bubbles : false; fixEvent.cancelable = typeof evt.cancelable !== undefined ? evt.cancelable : true; fixEvent.currentTarget = sender; if (!fixEvent.target){ // 多次绑定统一事件,只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三个函数中我们,如果被调用则listener执行完后使用一个变量保存event对象(见fixListener),以便后序事件处理程序根据event对象属性进行下一步处理。stopImmediatePropagation函数,对于这个函数的模拟,我们同样通过闭包来解决。
注意这里不能直接写成这种形式,上文中fixListener也是同样道理。
需要注意一点,我们将event标准化目的还有一点,可以在emit方法中设置参数来控制事件过程,比如:
Evt.emit(input, click);//不冒泡
Evt.emit(input, click, {bubbles: true});//冒泡
根据我的测试使用fireEvent方式触发事件,无法设置{bubbles:false}来阻止冒泡,所以这里我们用Javascript来模拟冒泡过程。同时在这个过程中也要保证event对象的唯一性。
// 模拟冒泡事件 var sythenticBubble = function(target, type, evt){ var method = on + type; var args = Array.prototype.slice.call(arguments, 2); // 保证使用emit触发dom事件时,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; 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参数中设置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方法,则为他添加一个方法method方法 //如果target已经拥有method方法,但target[method]中target不符合要求则将method方法他替换 dispatcher = target[method] = function(){ //由于js是此法作用域:通过阅读包括变量定义在内的数行源码就能知道变量的作用域。 //局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的 //所以在这个函数中可以访问到dispatcher变量 var results = null; var args = arguments; if (dispatcher.around) {//如果原先拥有method方法,先调用原始method方法 //此时this关键字指向target所以不用target results = dispatcher.around.advice.apply(this, args); if (dispatcher.after) {//如果存在after链则依次访问其中的advice方法 var _after = dispatcher.after; while(_after _after.advice) { //如果需要原始参数则传入arguments否则使用上次执行结果作为参数 args = _after.originalArgs ? arguments : results; results = _after.advice.apply(this, args); _after = _after.next; if (existing) { //函数也是对象,也可以拥有属性跟方法 //这里将原有的method方法放到dispatcher中 dispatcher.around = { advice: function(){ return existing.apply(target, arguments); dispatcher.target = target;
//remove的本质是将cb从函数链中移除,删除所有指向他的链接 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加入到链式结构中,处理指针关系 while(previous previous.next (previous = previous.next)){}; previous.next = signal; signal.previous = previous; } else {//如果是第一次使用调用after方法,则dispatcher的after属性指向signal dispatcher.after = signal; cb = null;//防止内存泄露 return signal; //1、统一事件触发顺序 //2、标准化事件对象 //3、模拟冒泡 emit时保持冒泡行为,注意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; // 使用全局变量来保证一个元素的多个listenser中事件对象的一致性,冒泡过程中事件对象的一致性;在chrome这些过程中使用的是同一个event //1、统一事件触发顺序 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){ //每次调用listenser之前都会调用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) { // 在整个函数链执行完成后将lastEvent回归到原始状态, //利用异步队列,在主程序执行完后再执行事件队列中的程序代码 //常规的做法是在emit中判断lastEvent并设为null //这充分体现了js异步编程的优势,把变量赋值跟清除代码放在一起,避免逻辑分散,缺点是不符合程序员正常思维方式 if(!lastEvent){ setTimeout(function(){ lastEvent = null; lastEvent = e; return results; function _fixEvent(evt, sender){ if (!evt) { evt = window.event; if (!evt) { // emit没有传递事件参数,或者通过input.onclick方式调用 return evt; if(lastEvent lastEvent.type evt.type == lastEvent.type){ //使用一个全局对象来保证在冒泡过程中访问的是同一个event对象 //chrome中整个事件处理过程event是唯一的 evt = lastEvent; var fixEvent = evt; // bubbles 和cancelable根据每次emit时手动传入参数设置 fixEvent.bubbles = typeof evt.bubbles !== undefined ? evt.bubbles : false; fixEvent.cancelable = typeof evt.cancelable !== undefined ? evt.cancelable : true; fixEvent.currentTarget = sender; if (!fixEvent.target){ // 多次绑定统一事件,只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; function _addStopImmediate(listener) { return function(evt) { // 除了包装listener外,还要保证所有的事件函数共用一个evt对象 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触发dom事件时,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; 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参数中设置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
欢迎各位有志之士前来交流探讨!
参考文章:
javascript线程解释(setTimeout,setInterval你不知道的事)
【问题】javascript计算丢失精度解决方案 1、问题描述: 两个有限的数字相减得到小数点后面类似00000000000001,69999999999999之类的数。像下面这样0.1 + 0.2没有返回0.3,反而返回0.30000000000000004。原因是因为计算机把数字转成二进制计算再将计算转成十进制的这个过程出现的计算误差。不仅仅是JavaScript会出现这个问题,其他语言也会有。不过解决思路都是一样的
对于使用JS动态加载, 或者将下一页地址隐藏为JavaScript void(0)的网站, 如何爬取我们要的信息呢? 本文以Chrome浏览器为工具, 36Kr为示例网站, 使用 Json Handle 作为辅助信息解析工具, 演示如何抓取此类网站.
相关文章
- JavaScript : 获取文件名后缀
- 深入理解javascript事件
- Javascript Prototypes之旅(A Plain English Guide to JavaScript Prototypes译文)
- javascript无缝滚动示例
- 第一百五十四节,封装库--JavaScript,表单验证--提交验证
- [Javascript] Chunk array
- [Javascript] Natively Format JavaScript Dates and Times
- [Javascript] Wait for Multiple JavaScript Promises to Settle with Promise.allSettled()
- [Javascript] Use requestIdleCallback to schedule JavaScript tasks at an optimal time
- [Javascript] Multiply Two Arrays over a Function in JavaScript
- [Javascript] Delegate JavaScript (ES6) generator iteration control
- [Javascript] Compose multiple functions for new behavior in JavaScript
- [Javascript] Decorators in JavaScript
- [Javascript] Proper use of console.assert in JavaScript
- [Javascript] What is JavaScript Function Currying?
- JavaScript 触发click事件 兼容FireFox,IE 和 Chrome
- JavaScript 计时事件
- [Javascript] Hide Properties from Showing Up in "for ... in" Loops in JavaScript
- [Javascript] Convert a Callback-Based JavaScript Function to a Promise-Based One
- [Javascript] Use JavaScript's for-in Loop on Objects with Prototypes
- [Javascript] Linting JavaScript with ESLint
- javascript 显示一定范围内的素数(质数)
- Javascript面向对象编程
- 10个算法提升你的JavaScript技能
- 146. 通过 SAP UI5 ODataModel API 在 JavaScript 代码里访问 OData 元数据
- JavaScript事件处理的例子:事件捕捉和冒泡 - event capture and bubble
- 两种使用JavaScript触发ABAP事件的技术手段
- 如何利用JavaScript的arguments对象实现用代码打印调用栈的需求
- JavaScript异常练习:验证输入的数字
- 从零开始学_JavaScript_系列(八)——js系列<2>(事件触发顺序、文本读取、js编写ajax、输入验证、下拉菜单)
- 【华为OD机试 2023】 人数最多的站点/小火车最多人时所在园区站点(C++ Java JavaScript Python)
- javascript 获取焦点和失去焦点事件
- javascript --- 事件托付
- Javascript 将一个句子中的单词首字母转成大写
- 零基础入门前端--JavaScript 循环结构语句
- Web前端 | JavaScript(事件)