JavaScript中移除由addEventListener添加的匿名函数

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

QQ图片20151120175124

好吧,接下来是实现代码

[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

发表新的回复