JavaScript中移除由addEventListener添加的匿名函数
强行篡改闭包xhr对象的返回值
花生个人一直比较喜爱Js,喜爱它的高灵活 喜欢它的……
本文章主要采用的技术叫做“函数劫持”,有兴趣的同学可以自学百度。
话不多说,开始!
如果想要直接要代码参考的请直接翻到最后。
在群里有一个群友表示自己需要篡改某网站的xhr对象返回值(responseText),而xhr对象的responseText属性是只读的,这个可如何是好?
于是花生提议你可以这样:
window.addEventListener('load',function(){ var xhr=new XMLHttpRequest(); xhr.open('get','sleep.php?sleep=1',true); xhr.onreadystatechange = function(e){ if (this.readyState == 4 && this.status == 200){ console.log("Ajax成功返回:"+this.responseText) }; }; xhr.send(); //开始篡改 xhr.abort(); var obj={'readyState':4,'status':200,'responseText':'篡改'}; xhr.onreadystatechange.call(obj); },false);
但是他说原网站的xhr写在闭包里了,大约是这样
window.addEventListener('load',function(){ (function(){ var xhr=new XMLHttpRequest(); var dv1=document.getElementById('dv1'); xhr.open('get','sleep.php?sleep=1',true); xhr.addEventListener('readystatechange', function(e){ if (this.readyState == 4 && this.status == 200){ console.log("Ajax成功返回:"+this.responseText) }; },false); dv1.addEventListener('click',function(){ xhr.send(); },false); })(); },false);
原网站不仅仅是把xhr写在闭包里,还用addEventListener来添加事件,而且添加的还是匿名函数,嘿嘿嘿,我看你怎么办
各种查手册,w3c、Mozilla都看了,它们一致的告诉花生:想捕获addEventListener?门都没有!
简直就是在逼花生放大招!
在开头加入
window.pro_xhr_addEventListener=XMLHttpRequest.prototype.addEventListener XMLHttpRequest.prototype.addEventListener=function(){ //做你想做的事情 //do sth //保证原有功能 window.pro_xhr_addEventListener.apply(this,arguments); };
注意,这里我只是想监视xhr的addEventListener方法调用,如果读者需要监视dom节点,将开头的XMLHttpRequest替换成Element即可。
问题到这里差不多就结束了,但是猛然发现,监视xhr的addEventListener并没有什么判断条件,花生想要通过xhr的发送地址来判断是否做出什么动作,比如
XMLHttpRequest.prototype.addEventListener=function(){ if(this.url == 'sleep.php?sleep=1'){ //do sth }; //保证原有功能 window.pro_xhr_addEventListener.apply(this,arguments); };
各种查手册,w3c、Mozilla都看了,它们还是一致的告诉花生:并没有什么属性可以直接获取到xhr的url……
好吧,再放一次大招好了
window.pro_xhr_open=XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open=function(){ this.pea=new Object(); this.pea.allArguments=arguments; this.pea.method=arguments[0]; this.pea.url=arguments[1]; this.pea.async=arguments[2]; this.pea.username=arguments[3]; this.pea.password=arguments[4]; //保证原有功能 window.pro_xhr_open.apply(this,arguments); };
这次是重写了open方法,让每次调用xhr.open的时候,将open使用的参数全部暴露出来。
而最后的demo差不多就是像下面这样,按照群友的需求,要把1234567890123变成xyz4567890xyz(真是奇怪的需求)
好吧,那么修改下根目录下的sleep.php,让它返回1234567890123
好吧,接下来是实现代码
window.onload=function(){ window.pro_xhr_open=XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open=function(){ this.pea=new Object(); this.pea.allArguments=arguments; this.pea.method=arguments[0]; this.pea.url=arguments[1]; this.pea.async=arguments[2]; this.pea.username=arguments[3]; this.pea.password=arguments[4]; //保证原有功能 window.pro_xhr_open.apply(this,arguments); }; window.pro_xhr_addEventListener=XMLHttpRequest.prototype.addEventListener XMLHttpRequest.prototype.addEventListener=function(){ if(this.pea.url == 'sleep.php?sleep=1' && arguments[0]=== 'readystatechange'){ var that=this; var old_callback=arguments[1]; var fakeObj={'readyState':4,'status':200}; arguments[1]=function(){ if (this.readyState == 4 && this.status == 200){ fakeObj.responseText=this.responseText.replace(/123/g,'xyz'); old_callback.call(fakeObj); }else{ arguments[1]=old_callback; }; }; } //保证原有功能 window.pro_xhr_addEventListener.apply(this,arguments); }; (function(){ var dv1=document.getElementById('dv1'); var xhr=new XMLHttpRequest(); xhr.open('get','sleep.php?sleep=1',true); xhr.addEventListener('readystatechange', function(e){ if (this.readyState == 4 && this.status == 200){ console.log("Ajax成功返回:"+this.responseText) }; },false); dv1.addEventListener('click',function(){ xhr.send(); },false); })(); }
这样,返回值就变成了xyz4567890xyz,注意,URL里面的sleep=1只是为了方便if判断,仅此而已
好了,问题解决了
等等,好像本文到现在还是没有实现“JavaScript中移除由addEventListener添加的匿名函数”啊!
好吧,如果真的是一路看下来应该都可以自己写了,不过这里还是给出代码参考
这里的需求是,移除掉ID为dv1的DIV的第二个click绑定事件,下面的是原文件
<div id="dv1" style="width:300px;height:300px;background:red;">dv1</div> <script> var dv1=document.getElementById('dv1'); dv1.addEventListener('click',function(){ alert(this.innerHTML); }); dv1.addEventListener('click',function(){ alert(this); }); </script>
下面的修改文件,注意,重写js原有方法部分需要放在文件最开始,要不然会监视不到
<div id="dv1" style="width:300px;height:300px;background:red;">dv1</div> <script> //重写js原有方法 window.pro_elt_addEventListener=Element.prototype.addEventListener; Element.prototype.addEventListener=function(){ if(!this.eventList) this.eventList={}; if(!this.eventList[arguments[0]]) this.eventList[arguments[0]]=[]; this.eventList[arguments[0]].push(arguments[1]); window.pro_elt_addEventListener.apply(this,arguments); }; //原文件 var dv1=document.getElementById('dv1'); dv1.addEventListener('click',function(){ alert(this.innerHTML); }); dv1.addEventListener('click',function(){ alert(this); }); //输出绑定的事件列表 alert(dv1.eventList.click.join('\n')); //移除事件 dv1.removeEventListener("click",dv1.eventList.click[1]); </script>
好了,本来打算封装一个好用的类来快捷的完成这个操作,想想用到的很少,还是算了。。。
参考资料:
http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventListener
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest