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

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

QQ图片20151120175124

好吧,接下来是实现代码

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

发表新的回复