Skip to content

Commit 85a92a3

Browse files
committed
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: #847 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
1 parent 65d0a8e commit 85a92a3

File tree

1 file changed

+90
-35
lines changed

1 file changed

+90
-35
lines changed

lib/querystring.js

+90-35
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@
44

55
const QueryString = exports;
66

7-
// If obj.hasOwnProperty has been overridden, then calling
8-
// obj.hasOwnProperty(prop) will break.
9-
// See: https://github.com/joyent/node/issues/1707
10-
function hasOwnProperty(obj, prop) {
11-
return Object.prototype.hasOwnProperty.call(obj, prop);
12-
}
13-
147

158
function charCode(c) {
169
return c.charCodeAt(0);
@@ -93,19 +86,68 @@ QueryString.unescape = function(s, decodeSpaces) {
9386
};
9487

9588

89+
var hexTable = new Array(256);
90+
for (var i = 0; i < 256; ++i)
91+
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
9692
QueryString.escape = function(str) {
97-
return encodeURIComponent(str);
93+
var len = str.length;
94+
var out = '';
95+
var i, c;
96+
97+
if (len === 0)
98+
return str;
99+
100+
for (i = 0; i < len; ++i) {
101+
c = str.charCodeAt(i);
102+
103+
// These characters do not need escaping (in order):
104+
// ! - . _ ~
105+
// ' ( ) *
106+
// digits
107+
// alpha (uppercase)
108+
// alpha (lowercase)
109+
if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E ||
110+
(c >= 0x27 && c <= 0x2A) ||
111+
(c >= 0x30 && c <= 0x39) ||
112+
(c >= 0x41 && c <= 0x5A) ||
113+
(c >= 0x61 && c <= 0x7A)) {
114+
out += str[i];
115+
continue;
116+
}
117+
118+
// Other ASCII characters
119+
if (c < 0x80) {
120+
out += hexTable[c];
121+
continue;
122+
}
123+
124+
// Multi-byte characters ...
125+
if (c < 0x800) {
126+
out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
127+
continue;
128+
}
129+
if (c < 0xD800 || c >= 0xE000) {
130+
out += hexTable[0xE0 | (c >> 12)] +
131+
hexTable[0x80 | ((c >> 6) & 0x3F)] +
132+
hexTable[0x80 | (c & 0x3F)];
133+
continue;
134+
}
135+
// Surrogate pair
136+
++i;
137+
c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF));
138+
out += hexTable[0xF0 | (c >> 18)] +
139+
hexTable[0x80 | ((c >> 12) & 0x3F)] +
140+
hexTable[0x80 | ((c >> 6) & 0x3F)] +
141+
hexTable[0x80 | (c & 0x3F)];
142+
}
143+
return out;
98144
};
99145

100146
var stringifyPrimitive = function(v) {
101-
let type = typeof v;
102-
103-
if (type === 'string')
147+
if (typeof v === 'string' || (typeof v === 'number' && isFinite(v)))
104148
return v;
105-
if (type === 'boolean')
149+
if (typeof v === 'boolean')
106150
return v ? 'true' : 'false';
107-
if (type === 'number')
108-
return isFinite(v) ? v : '';
109151
return '';
110152
};
111153

@@ -121,21 +163,31 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
121163

122164
if (obj !== null && typeof obj === 'object') {
123165
var keys = Object.keys(obj);
124-
var fields = [];
125-
126-
for (var i = 0; i < keys.length; i++) {
166+
var len = keys.length;
167+
var flast = len - 1;
168+
var fields = '';
169+
for (var i = 0; i < len; ++i) {
127170
var k = keys[i];
128171
var v = obj[k];
129172
var ks = encode(stringifyPrimitive(k)) + eq;
130173

131174
if (Array.isArray(v)) {
132-
for (var j = 0; j < v.length; j++)
133-
fields.push(ks + encode(stringifyPrimitive(v[j])));
175+
var vlen = v.length;
176+
var vlast = vlen - 1;
177+
for (var j = 0; j < vlen; ++j) {
178+
fields += ks + encode(stringifyPrimitive(v[j]));
179+
if (j < vlast)
180+
fields += sep;
181+
}
182+
if (vlen && i < flast)
183+
fields += sep;
134184
} else {
135-
fields.push(ks + encode(stringifyPrimitive(v)));
185+
fields += ks + encode(stringifyPrimitive(v));
186+
if (i < flast)
187+
fields += sep;
136188
}
137189
}
138-
return fields.join(sep);
190+
return fields;
139191
}
140192
return '';
141193
};
@@ -169,29 +221,23 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
169221
decode = options.decodeURIComponent;
170222
}
171223

224+
var keys = [];
172225
for (var i = 0; i < len; ++i) {
173226
var x = qs[i].replace(regexp, '%20'),
174227
idx = x.indexOf(eq),
175-
kstr, vstr, k, v;
228+
k, v;
176229

177230
if (idx >= 0) {
178-
kstr = x.substr(0, idx);
179-
vstr = x.substr(idx + 1);
231+
k = decodeStr(x.substring(0, idx), decode);
232+
v = decodeStr(x.substring(idx + 1), decode);
180233
} else {
181-
kstr = x;
182-
vstr = '';
234+
k = decodeStr(x, decode);
235+
v = '';
183236
}
184237

185-
try {
186-
k = decode(kstr);
187-
v = decode(vstr);
188-
} catch (e) {
189-
k = QueryString.unescape(kstr, true);
190-
v = QueryString.unescape(vstr, true);
191-
}
192-
193-
if (!hasOwnProperty(obj, k)) {
238+
if (keys.indexOf(k) === -1) {
194239
obj[k] = v;
240+
keys.push(k);
195241
} else if (Array.isArray(obj[k])) {
196242
obj[k].push(v);
197243
} else {
@@ -201,3 +247,12 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
201247

202248
return obj;
203249
};
250+
251+
252+
function decodeStr(s, decoder) {
253+
try {
254+
return decoder(s);
255+
} catch (e) {
256+
return QueryString.unescape(s, true);
257+
}
258+
}

0 commit comments

Comments
 (0)