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