ajax解析properties,jasmine-ajax源码解析

总体概述

jasmine-ajax主要五个对象组成:

请求伪造对象(FackXMLHttpRequest):

function FakeXMLHttpRequest() {}

请求跟踪对象(RequestTracker):

function RequestTracker() {}

请求拦截对象(RequestStub):用作对伪造的AJAX请求做出响应。

function RequestStub(url, stubData, method) {}

请求拦截跟踪对象(StubTracker):

function StubTracker() {}

参数解析(ParamParser):

function ParamParser() {}

辅助函数

arrayContains

function arrayContains(arr, item) {

for (var i = 0; i < arr.length; i++) {

if (arr[i] === item) {

return true;

}

}

return false;

}

函数结构很明显,判定一个数组是否包含某一个值。

extend

function extend(destination, source, propertiesToSkip) {

propertiesToSkip = propertiesToSkip || [];

for (var property in source) {

if (!arrayContains(propertiesToSkip, property)) {

destination[property] = source[property];

}

}

return destination;

}

此函数实现对象扩展,propertiesToSkip参数为source中需排除掉的键,余下的键值对覆盖写入destination。

初始化语句

if (typeof window === "undefined" && typeof exports === "object") {

exports.MockAjax = MockAjax;

jasmine.Ajax = new MockAjax(exports);

} else {

window.MockAjax = MockAjax;

jasmine.Ajax = new MockAjax(window);

}

首先判定是否commonjs环境,不是则传递window变量。在describe内部,即可访问通过jasmine.Ajax使用MockAjax对象。

RequestTracker

其实结构非常明显,使用jasmine.Ajax.requests即可访问到RequestTracker对象,然后通过first, mostRecent, at, filter 等方法获取仿造的request对象,多用于对HTTP请求对象进行判定,如方法,路径,数据,headers等等,需要重点关注。reset, track手动调用意义不大,前者用于重置变量环境,后者在生成伪造XHR时调用。

function RequestTracker() {

var requests = [];

this.track = function(request) {

requests.push(request);

};

this.first = function() {

return requests[0];

};

this.count = function() {

return requests.length;

};

this.reset = function() {

requests = [];

};

this.mostRecent = function() {

return requests[requests.length - 1];

};

this.at = function(index) {

return requests[index];

};

this.filter = function(url_to_match) {

if (requests.length === 0) { return []; }

var matching_requests = [];

for (var i = 0; i < requests.length; i++) {

if (url_to_match instanceof RegExp &&

url_to_match.test(requests[i].url)) {

matching_requests.push(requests[i]);

} else if (url_to_match instanceof Function &&

url_to_match(requests[i])) {

matching_requests.push(requests[i]);

} else {

if (requests[i].url === url_to_match) {

matching_requests.push(requests[i]);

}

}

}

return matching_requests;

};

}

StubTracker

通过jasmine.Ajax.stubs即可访问到StubTracker对象,主动调用意义不大。reset方法用于变量环境重置,addStub方法在生成RequestStub对象时调用。findStub在xhr.open()调用时,判定是否定义过response。

function StubTracker() {

var stubs = [];

this.addStub = function(stub) {

stubs.push(stub);

};

this.reset = function() {

stubs = [];

};

this.findStub = function(url, data, method) {

for (var i = stubs.length - 1; i >= 0; i--) {

var stub = stubs[i];

if (stub.matches(url, data, method)) {

return stub;

}

}

};

}

RequestStub

请求拦截在url, stubData, method同时匹配的情况下才会启动拦截。

function RequestStub(url, stubData, method) {

var normalizeQuery = function(query) {

return query ? query.split('&').sort().join('&') : undefined;

};

if (url instanceof RegExp) {

this.url = url;

this.query = undefined;

} else {

var split = url.split('?');

this.url = split[0];

this.query = split.length > 1 ? normalizeQuery(split[1]) : undefined;

}

this.data = normalizeQuery(stubData);

this.method = method;

this.andReturn = function(options) {

this.status = options.status || 200;

this.contentType = options.contentType;

this.responseText = options.responseText;

};

this.matches = function(fullUrl, data, method) {

var matches = false;

fullUrl = fullUrl.toString();

if (this.url instanceof RegExp) {

matches = this.url.test(fullUrl);

} else {

var urlSplit = fullUrl.split('?'),

url = urlSplit[0],

query = urlSplit[1];

matches = this.url === url && this.query === normalizeQuery(query);

}

return matches && (!this.data || this.data === normalizeQuery(data)) && (!this.method || this.method === method);

};

}

ParamParser

主要负责值的解析。paramParsers参数为parser对象数组。findParser遍历paramParsers,通过test属性判定是否启用对应的parse方法处理数据。若决定调用,则遍历结束,返回该对象。add方法一般会通过jasmine.Ajax.addCustomParamParser(parser)调用,优先级上,后添加的>先添加的>默认的。

function ParamParser() {

var defaults = [

{

test: function(xhr) {

return (/^application\/json/).test(xhr.contentType());

},

parse: function jsonParser(paramString) {

return JSON.parse(paramString);

}

},

{

test: function(xhr) {

return true;

},

parse: function naiveParser(paramString) {

var data = {};

var params = paramString.split('&');

for (var i = 0; i < params.length; ++i) {

var kv = params[i].replace(/\+/g, ' ').split('=');

var key = decodeURIComponent(kv[0]);

data[key] = data[key] || [];

data[key].push(decodeURIComponent(kv[1]));

}

return data;

}

}

];

var paramParsers = [];

this.add = function(parser) {

paramParsers.unshift(parser);

};

this.findParser = function(xhr) {

for(var i in paramParsers) {

var parser = paramParsers[i];

if (parser.test(xhr)) {

return parser;

}

}

};

this.reset = function() {

paramParsers = [];

for(var i in defaults) {

paramParsers.push(defaults[i]);

}

};

this.reset();

}

FackXMLHttpRequest

代码过长,分段说明。

FakeXMLHttpRequest

实例化后,即添加进入requestTracker,便于后期访问。

function FakeXMLHttpRequest() {

requestTracker.track(this);

this.requestHeaders = {};

this.overriddenMimeType = null;

}

原型继承

此处感觉比较好玩,是继承真的XMLHttpRequest对象,只是去掉几个特殊键值对,后面会进行处理。

var iePropertiesThatCannotBeCopied = ['responseBody', 'responseText', 'responseXML', 'status', 'statusText', 'responseTimeout'];

extend(FakeXMLHttpRequest.prototype, new window.XMLHttpRequest(), iePropertiesThatCannotBeCopied);

Request伪造

熟悉原生ajax的应该很容易看懂。重点在于,send方法调用时,会立即在stubs里寻找匹配当前url,method,data的响应拦截器,如果预定义过了,则会立刻传递给response函数进行响应,否则需要之后手动调用response函数进行响应。

extend(FakeXMLHttpRequest.prototype, {

open: function() {

this.method = arguments[0];

this.url = arguments[1];

this.username = arguments[3];

this.password = arguments[4];

this.readyState = 1;

this.onreadystatechange();

},

setRequestHeader: function(header, value) {

if(this.requestHeaders.hasOwnProperty(header)) {

this.requestHeaders[header] = [this.requestHeaders[header], value].join(', ');

} else {

this.requestHeaders[header] = value;

}

},

overrideMimeType: function(mime) {

this.overriddenMimeType = mime;

},

abort: function() {

this.readyState = 0;

this.status = 0;

this.statusText = "abort";

this.onreadystatechange();

},

readyState: 0,

onload: function() {

},

onreadystatechange: function(isTimeout) {

},

status: null,

send: function(data) {

this.params = data;

this.readyState = 2;

this.onreadystatechange();

var stub = stubTracker.findStub(this.url, data, this.method);

if (stub) {

this.response(stub);

}

},

contentType: function() {

return findHeader('content-type', this.requestHeaders);

},

data: function() {

if (!this.params) {

return {};

}

return paramParser.findParser(this).parse(this.params);

},

getResponseHeader: function(name) {

return findHeader(name, this.responseHeaders);

},

getAllResponseHeaders: function() {

var responseHeaders = [];

for (var i in this.responseHeaders) {

if (this.responseHeaders.hasOwnProperty(i)) {

responseHeaders.push(i + ': ' + this.responseHeaders[i]);

}

}

return responseHeaders.join('\r\n');

},

responseText: null,

response: function(response) {

if (this.readyState === 4) {

throw new Error("FakeXMLHttpRequest already completed");

}

this.status = response.status;

this.statusText = response.statusText || "";

this.responseText = response.responseText || "";

this.readyState = 4;

this.responseHeaders = response.responseHeaders ||

{"Content-Type": response.contentType || "application/json" };

this.onload();

this.onreadystatechange();

},

responseTimeout: function() {

if (this.readyState === 4) {

throw new Error("FakeXMLHttpRequest already completed");

}

this.readyState = 4;

jasmine.clock().tick(30000);

this.onreadystatechange('timeout');

}

});

return FakeXMLHttpRequest;

}

经验交流

QQ: 491229492

Email: huang.jian@eisoo.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值