我正在尝试修改由我无法修改的函数接收的responseText。这个函数创建了一个我可以附加的XMLHttpRequest,但是我无法将其包裹起来#34; responseText允许我在原始函数接收内容之前修改内容。
这里有完整的原始功能:
function Mj(a, b, c, d, e) {
function k() {
4 == (m && 'readyState' in m ? m.readyState : 0) && b && ff(b) (m)
}
var m = new XMLHttpRequest;
'onloadend' in m ? m.addEventListener('loadend', k, !1) : m.onreadystatechange = k;
c = ('GET').toUpperCase();
d = d || '';
m.open(c, a, !0);
m.send(d);
return m
}
function ff(a) {
return a && window ? function () {
try {
return a.apply(this, arguments)
} catch(b) {
throw jf(b),
b;
}
} : a
}
我还试图操纵reiceiving函数k();试图达到我的目标,但因为它并不依赖于传递给函数的任何数据(例如k(a.responseText);)我没有成功。
有什么方法可以实现这个目标吗?我不想使用js库(例如jQuery);
编辑:我知道我无法直接更改.responseText,因为它是只读的,但我正在尝试找到一种方法来更改响应和接收函数之间的内容。
EDIT2 :在我尝试拦截和更改.responseText的方法之一下面添加了以下内容:Monkey patch XMLHTTPRequest.onreadystatechange
(function (open) {
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
if(/results/.test(url)) {
console.log(this.onreadystatechange);
this.addEventListener("readystatechange", function () {
console.log('readystate: ' + this.readyState);
if(this.responseText !== '') {
this.responseText = this.responseText.split('&')[0];
}
}, false);
}
open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest.prototype.open);
EDIT3 :我忘了包含函数Mj和ff不是全局可用的,它们都包含在一个匿名函数中(function(){functions are here})();
EDIT4 :我已经更改了已接受的答案,因为AmmarCSE没有任何与jfriend00的答案相关的问题和复杂性。
简短解释的最佳答案如下:
收听您想要修改的任何请求(确保您的侦听器在原始函数目标之前拦截它,否则在使用响应后修改它没有意义。)
在临时变量中保存原始响应(如果要修改它)
将要修改的属性更改为"可写:true",它将删除它具有的任何值。在我的情况下,我使用
Object.defineProperty(event, 'responseText', {
writable: true
});
其中event
是通过收听xhr请求的load
或readystatechange
事件返回的对象
现在,您可以为响应设置任何内容,如果您只想修改原始响应,则可以使用临时变量中的数据,然后将修改保存在响应中。
答案 0 :(得分:15)
编辑:请参阅下面的第二个代码选项(已经过测试并且有效)。第一个有一些限制。
由于您无法修改任何这些功能,因此您必须先了解XMLHttpRequest原型。这是一个想法(未经测试,但你可以看到方向):
(function() {
var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
var oldReady;
if (async) {
oldReady = this.onreadystatechange;
// override onReadyStateChange
this.onreadystatechange = function() {
if (this.readyState == 4) {
// this.responseText is the ajax result
// create a dummay ajax object so we can modify responseText
var self = this;
var dummy = {};
["statusText", "status", "readyState", "responseType"].forEach(function(item) {
dummy[item] = self[item];
});
dummy.responseText = '{"msg": "Hello"}';
return oldReady.call(dummy);
} else {
// call original onreadystatechange handler
return oldReady.apply(this, arguments);
}
}
}
// call original open method
return open.apply(this, arguments);
}
})();
这是一个针对XMLHttpRequest open()
方法的猴子补丁,然后当调用异步请求时,它会为onReadyStateChange处理程序执行一个猴子补丁,因为它应该已经设置好了。然后,修补后的函数会在调用原始onReadyStateChange处理程序之前查看responseText,以便为其分配不同的值。
最后,因为.responseText
只是就绪,所以在调用onreadystatechange
处理程序之前,它会替换一个伪XMLHttpResponse对象。这在所有情况下都不起作用,但如果onreadystatechange处理程序使用this.responseText
来获取响应,则会起作用。
而且,这是尝试将XMLHttpRequest对象重新定义为我们自己的代理对象。因为它是我们自己的代理对象,所以我们可以将responseText
属性设置为我们想要的任何属性。对于除onreadystatechange
之外的所有其他属性,此对象只是将get,set或function调用转发给真正的XMLHttpRequest对象。
(function() {
// create XMLHttpRequest proxy object
var oldXMLHttpRequest = XMLHttpRequest;
// define constructor for my proxy object
XMLHttpRequest = function() {
var actual = new oldXMLHttpRequest();
var self = this;
this.onreadystatechange = null;
// this is the actual handler on the real XMLHttpRequest object
actual.onreadystatechange = function() {
if (this.readyState == 4) {
// actual.responseText is the ajax result
// add your own code here to read the real ajax result
// from actual.responseText and then put whatever result you want
// the caller to see in self.responseText
// this next line of code is a dummy line to be replaced
self.responseText = '{"msg": "Hello"}';
}
if (self.onreadystatechange) {
return self.onreadystatechange();
}
};
// add all proxy getters
["status", "statusText", "responseType", "response",
"readyState", "responseXML", "upload"].forEach(function(item) {
Object.defineProperty(self, item, {
get: function() {return actual[item];}
});
});
// add all proxy getters/setters
["ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) {
Object.defineProperty(self, item, {
get: function() {return actual[item];},
set: function(val) {actual[item] = val;}
});
});
// add all pure proxy pass-through methods
["addEventListener", "send", "open", "abort", "getAllResponseHeaders",
"getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) {
Object.defineProperty(self, item, {
value: function() {return actual[item].apply(actual, arguments);}
});
});
}
})();
工作演示:http://jsfiddle.net/jfriend00/jws6g691/
我在IE,Firefox和Chrome的最新版本中尝试了它,它使用了一个简单的ajax请求。
注意:我没有研究过Ajax(比如二进制数据,上传等等)的所有高级方法,可以看到这个代理足够彻底,可以让所有这些工作完成(我猜它可能会还没有进一步的工作,但它正在为基本请求工作,所以看起来这个概念是有能力的。)
失败的其他尝试:
尝试从XMLHttpRequest对象派生,然后用我自己的构造函数替换构造函数,但这没有用,因为真正的XMLHttpRequest函数不会让你把它称为初始化我的函数派生对象。
尝试覆盖onreadystatechange
处理程序并更改.responseText
,但该字段为只读字段,因此您无法对其进行更改。
尝试创建一个在调用this
时作为onreadystatechange
对象发送的虚拟对象,但很多代码都没有引用this
,而是实际对象保存在闭包中的局部变量中 - 从而击败虚拟对象。
答案 1 :(得分:9)
一个非常简单的解决方法是更改responseText
本身的属性描述符
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
因此,您可以像{/ p>一样扩展XMLHttpRequest
(function(proxied) {
XMLHttpRequest = function() {
//cannot use apply directly since we want a 'new' version
var wrapped = new(Function.prototype.bind.apply(proxied, arguments));
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
return wrapped;
};
})(XMLHttpRequest);
答案 2 :(得分:6)
我需要拦截并修改请求响应,所以我想出了一些代码。我还发现有些网站喜欢使用响应以及responseText,这就是我的代码修改它们的原因。
守则
var open_prototype = XMLHttpRequest.prototype.open,
intercept_response = function(urlpattern, callback) {
XMLHttpRequest.prototype.open = function() {
arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function(event) {
if ( this.readyState === 4 ) {
var response = callback(event.target.responseText);
Object.defineProperty(this, 'response', {writable: true});
Object.defineProperty(this, 'responseText', {writable: true});
this.response = this.responseText = response;
}
});
return open_prototype.apply(this, arguments);
};
};
intercept_response函数的第一个参数是一个匹配请求url的正则表达式,第二个参数是用于修改它的响应的函数。
用法示例
intercept_response(/fruit\.json/i, function(response) {
var new_response = response.replace('banana', 'apple');
return new_response;
});
答案 3 :(得分:3)
当我制作Chrome扩展程序以允许跨源API调用时,我遇到了同样的问题。这适用于Chrome。 (更新:它不适用于最新的Chrome版本。)
delete _this.responseText;
_this.responseText = "Anything you want";
该代码段在monkeypatched XMLHttpRequest.prototype.send内运行,该请求将请求重定向到扩展后台脚本并替换响应中的所有属性。像这样:
// Delete removes the read only restriction
delete _this.response;
_this.response = event.data.response.xhr.response;
delete _this.responseText;
_this.responseText = event.data.response.xhr.responseText;
delete _this.status;
_this.status = event.data.response.xhr.status;
delete _this.statusText;
_this.statusText = event.data.response.xhr.statusText;
delete _this.readyState;
_this.readyState = event.data.response.xhr.readyState;
这在Firefox中没有用,但我发现了一个有效的解决方案:
var test = new XMLHttpRequest();
Object.defineProperty(test, 'responseText', {
configurable: true,
writable: true,
});
test.responseText = "Hey";
这在Chrome中不起作用,但这在Chrome和Firefox中都有效:
var test = new XMLHttpRequest();
var aValue;
Object.defineProperty(test, 'responseText', {
get: function() { return aValue; },
set: function(newValue) { aValue = newValue; },
enumerable: true,
configurable: true
});
test.responseText = "Hey";
复制过来的
这些解决方案都不适用于Safari。我尝试使用可写属性创建一个新的XMLHttpRequest,但不允许它打开或从中发送。我也试过这个解决方案:https://stackoverflow.com/a/28513219/3717718。不幸的是,它在Safari中产生了同样的错误:
TypeError:尝试使用不可配置属性的可配置属性。
答案 4 :(得分:3)
根据请求,我在下面包含一个示例代码段,显示如何在原始函数接收之前修改XMLHttpRequest的响应。
// In this example the sample response should be
// {"data_sample":"data has not been modified"}
// and we will change it into
// {"data_sample":"woops! All data has gone!"}
/*---BEGIN HACK---------------------------------------------------------------*/
// here we will modify the response
function modifyResponse(response) {
var original_response, modified_response;
if (this.readyState === 4) {
// we need to store the original response before any modifications
// because the next step will erase everything it had
original_response = response.target.responseText;
// here we "kill" the response property of this request
// and we set it to writable
Object.defineProperty(this, "responseText", {writable: true});
// now we can make our modifications and save them in our new property
modified_response = JSON.parse(original_response);
modified_response.data_sample = "woops! All data has gone!";
this.responseText = JSON.stringify(modified_response);
}
}
// here we listen to all requests being opened
function openBypass(original_function) {
return function(method, url, async) {
// here we listen to the same request the "original" code made
// before it can listen to it, this guarantees that
// any response it receives will pass through our modifier
// function before reaching the "original" code
this.addEventListener("readystatechange", modifyResponse);
// here we return everything original_function might
// return so nothing breaks
return original_function.apply(this, arguments);
};
}
// here we override the default .open method so that
// we can listen and modify the request before the original function get its
XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open);
// to see the original response just remove/comment the line above
/*---END HACK-----------------------------------------------------------------*/
// here we have the "original" code receiving the responses
// that we want to modify
function logResponse(response) {
if (this.readyState === 4) {
document.write(response.target.responseText);
}
}
// here is a common request
var _request = new XMLHttpRequest();
_request.open("GET", "https://gist.githubusercontent.com/anonymous/c655b533b340791c5d49f67c373f53d2/raw/cb6159a19dca9b55a6c97d3a35a32979ee298085/data.json", true);
_request.addEventListener("readystatechange", logResponse);
_request.send();
答案 5 :(得分:3)
您可以使用新函数将 camera.setPreviewDisplay(holder);
camera.startPreview();
的getter包装在原型中,然后对输出进行更改。
这是一个将html注释responseText
附加到响应文本的简单示例:
<!-- TEST -->
上述功能将更改所有请求的响应文本。
如果您只想对一个请求进行更改,请不要使用上面的函数,而只是在单个请求中定义getter:
(function(http){
var get = Object.getOwnPropertyDescriptor(
http.prototype,
'responseText'
).get;
Object.defineProperty(
http.prototype,
"responseText",
{
get: function(){ return get.apply( this, arguments ) + "<!-- TEST -->"; }
}
);
})(self.XMLHttpRequest);
答案 6 :(得分:1)
一流的功能变量是美妙的事情! function f() {a; b; c; }
与var f = function () {a; b; c; }
完全相同。这意味着您可以根据需要重新定义函数。您想要包装函数Mj
以返回修改后的对象吗?没问题。 responseText
字段是只读的这一事实很痛苦,但如果这是您需要的唯一字段......
var Mj_backup = Mj; // Keep a copy of it, unless you want to re-implement it (which you could do)
Mj = function (a, b, c, d, e) { // To wrap the old Mj function, we need its args
var retval = Mj_backup(a,b,c,d,e); // Call the original function, and store its ret value
var retmod; // This is the value you'll actually return. Not a true XHR, just mimics one
retmod.responseText = retval.responseText; // Repeat for any other required properties
return retmod;
}
现在,当您的页面代码调用Mj()时,它将调用您的包装器(当然,它仍将在内部调用原始Mj)。