Browse Source

querystring: optimize parse and stringify

parse optimizations:

* Move try-catch to separate function to keep entire function from
being deoptimized.
* Use key array lookup instead of using hasOwnProperty.
* Avoid decoding known empty strings.
* Avoid possibly unnecessary switch to slower decoder for values if
key decoding throws.

stringify optimizations:

* Use manual loop for default encoder instead of encodeURIComponent.
* Use string concatenation instead of joining an array of strings.
* Avoid caching result of typeof.

PR-URL: https://github.com/iojs/io.js/pull/847
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
v1.8.0-commit
Brian White 10 years ago
parent
commit
85a92a37ef
  1. 125
      lib/querystring.js

125
lib/querystring.js

@ -4,13 +4,6 @@
const QueryString = exports; const QueryString = exports;
// If obj.hasOwnProperty has been overridden, then calling
// obj.hasOwnProperty(prop) will break.
// See: https://github.com/joyent/node/issues/1707
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function charCode(c) { function charCode(c) {
return c.charCodeAt(0); return c.charCodeAt(0);
@ -93,19 +86,68 @@ QueryString.unescape = function(s, decodeSpaces) {
}; };
var hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
QueryString.escape = function(str) { QueryString.escape = function(str) {
return encodeURIComponent(str); var len = str.length;
var out = '';
var i, c;
if (len === 0)
return str;
for (i = 0; i < len; ++i) {
c = str.charCodeAt(i);
// These characters do not need escaping (in order):
// ! - . _ ~
// ' ( ) *
// digits
// alpha (uppercase)
// alpha (lowercase)
if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E ||
(c >= 0x27 && c <= 0x2A) ||
(c >= 0x30 && c <= 0x39) ||
(c >= 0x41 && c <= 0x5A) ||
(c >= 0x61 && c <= 0x7A)) {
out += str[i];
continue;
}
// Other ASCII characters
if (c < 0x80) {
out += hexTable[c];
continue;
}
// Multi-byte characters ...
if (c < 0x800) {
out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
continue;
}
if (c < 0xD800 || c >= 0xE000) {
out += hexTable[0xE0 | (c >> 12)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
continue;
}
// Surrogate pair
++i;
c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF));
out += hexTable[0xF0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3F)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
}
return out;
}; };
var stringifyPrimitive = function(v) { var stringifyPrimitive = function(v) {
let type = typeof v; if (typeof v === 'string' || (typeof v === 'number' && isFinite(v)))
if (type === 'string')
return v; return v;
if (type === 'boolean') if (typeof v === 'boolean')
return v ? 'true' : 'false'; return v ? 'true' : 'false';
if (type === 'number')
return isFinite(v) ? v : '';
return ''; return '';
}; };
@ -121,21 +163,31 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
if (obj !== null && typeof obj === 'object') { if (obj !== null && typeof obj === 'object') {
var keys = Object.keys(obj); var keys = Object.keys(obj);
var fields = []; var len = keys.length;
var flast = len - 1;
for (var i = 0; i < keys.length; i++) { var fields = '';
for (var i = 0; i < len; ++i) {
var k = keys[i]; var k = keys[i];
var v = obj[k]; var v = obj[k];
var ks = encode(stringifyPrimitive(k)) + eq; var ks = encode(stringifyPrimitive(k)) + eq;
if (Array.isArray(v)) { if (Array.isArray(v)) {
for (var j = 0; j < v.length; j++) var vlen = v.length;
fields.push(ks + encode(stringifyPrimitive(v[j]))); var vlast = vlen - 1;
for (var j = 0; j < vlen; ++j) {
fields += ks + encode(stringifyPrimitive(v[j]));
if (j < vlast)
fields += sep;
}
if (vlen && i < flast)
fields += sep;
} else { } else {
fields.push(ks + encode(stringifyPrimitive(v))); fields += ks + encode(stringifyPrimitive(v));
if (i < flast)
fields += sep;
} }
} }
return fields.join(sep); return fields;
} }
return ''; return '';
}; };
@ -169,29 +221,23 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
decode = options.decodeURIComponent; decode = options.decodeURIComponent;
} }
var keys = [];
for (var i = 0; i < len; ++i) { for (var i = 0; i < len; ++i) {
var x = qs[i].replace(regexp, '%20'), var x = qs[i].replace(regexp, '%20'),
idx = x.indexOf(eq), idx = x.indexOf(eq),
kstr, vstr, k, v; k, v;
if (idx >= 0) { if (idx >= 0) {
kstr = x.substr(0, idx); k = decodeStr(x.substring(0, idx), decode);
vstr = x.substr(idx + 1); v = decodeStr(x.substring(idx + 1), decode);
} else { } else {
kstr = x; k = decodeStr(x, decode);
vstr = ''; v = '';
}
try {
k = decode(kstr);
v = decode(vstr);
} catch (e) {
k = QueryString.unescape(kstr, true);
v = QueryString.unescape(vstr, true);
} }
if (!hasOwnProperty(obj, k)) { if (keys.indexOf(k) === -1) {
obj[k] = v; obj[k] = v;
keys.push(k);
} else if (Array.isArray(obj[k])) { } else if (Array.isArray(obj[k])) {
obj[k].push(v); obj[k].push(v);
} else { } else {
@ -201,3 +247,12 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
return obj; return obj;
}; };
function decodeStr(s, decoder) {
try {
return decoder(s);
} catch (e) {
return QueryString.unescape(s, true);
}
}

Loading…
Cancel
Save