summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian White <mscdex@mscdex.net>2015-02-19 09:53:04 -0500
committerBrian White <mscdex@mscdex.net>2015-03-15 23:11:38 -0400
commit85a92a37ef76059a4733c8e9462ff8da733dfb9e (patch)
tree8f5d41fbc35e656366e43a36d21e72217875f749
parent65d0a8eca8ea7b61b54bd6505b0ba26af5e4b769 (diff)
downloadnode-new-85a92a37ef76059a4733c8e9462ff8da733dfb9e.tar.gz
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>
-rw-r--r--lib/querystring.js125
1 files changed, 90 insertions, 35 deletions
diff --git a/lib/querystring.js b/lib/querystring.js
index b574978bc7..af320cf8f8 100644
--- a/lib/querystring.js
+++ b/lib/querystring.js
@@ -4,13 +4,6 @@
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) {
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) {
- 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) {
- let type = typeof v;
-
- if (type === 'string')
+ if (typeof v === 'string' || (typeof v === 'number' && isFinite(v)))
return v;
- if (type === 'boolean')
+ if (typeof v === 'boolean')
return v ? 'true' : 'false';
- if (type === 'number')
- return isFinite(v) ? v : '';
return '';
};
@@ -121,21 +163,31 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
if (obj !== null && typeof obj === 'object') {
var keys = Object.keys(obj);
- var fields = [];
-
- for (var i = 0; i < keys.length; i++) {
+ var len = keys.length;
+ var flast = len - 1;
+ var fields = '';
+ for (var i = 0; i < len; ++i) {
var k = keys[i];
var v = obj[k];
var ks = encode(stringifyPrimitive(k)) + eq;
if (Array.isArray(v)) {
- for (var j = 0; j < v.length; j++)
- fields.push(ks + encode(stringifyPrimitive(v[j])));
+ var vlen = v.length;
+ 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 {
- fields.push(ks + encode(stringifyPrimitive(v)));
+ fields += ks + encode(stringifyPrimitive(v));
+ if (i < flast)
+ fields += sep;
}
}
- return fields.join(sep);
+ return fields;
}
return '';
};
@@ -169,29 +221,23 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
decode = options.decodeURIComponent;
}
+ var keys = [];
for (var i = 0; i < len; ++i) {
var x = qs[i].replace(regexp, '%20'),
idx = x.indexOf(eq),
- kstr, vstr, k, v;
+ k, v;
if (idx >= 0) {
- kstr = x.substr(0, idx);
- vstr = x.substr(idx + 1);
+ k = decodeStr(x.substring(0, idx), decode);
+ v = decodeStr(x.substring(idx + 1), decode);
} else {
- kstr = x;
- vstr = '';
+ k = decodeStr(x, decode);
+ 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;
+ keys.push(k);
} else if (Array.isArray(obj[k])) {
obj[k].push(v);
} else {
@@ -201,3 +247,12 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
return obj;
};
+
+
+function decodeStr(s, decoder) {
+ try {
+ return decoder(s);
+ } catch (e) {
+ return QueryString.unescape(s, true);
+ }
+}