Skip to content

Commit 6752251

Browse files
committed
faster escapeHTML function
1 parent 9d6853d commit 6752251

File tree

3 files changed

+156
-16
lines changed

3 files changed

+156
-16
lines changed

lib/micro-template.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,20 @@ const template = function (id, data) {
3434
template.cache = new Map();
3535
template.get = function (id) { return document.getElementById(id).innerHTML };
3636
template.escapeHTML = (function () {
37-
const map = new Map([ [ `&`, `&amp;` ], [ `<`, `&lt;` ], [ `>`, `&gt;` ], [ `"`, `&#x22;` ], [ `'`, `&#x27;` ] ].map(([k, v]) => [k.charCodeAt(0), v]));
38-
const regexp = /[<>"'&]/g;
3937
return function (str) {
4038
if (typeof str !== 'string') str = String(str);
41-
regexp.lastIndex = 0;
42-
let match = regexp.exec(str);
43-
if (match === null) return str;
44-
let result = '', lastIndex = 0;
45-
do {
46-
result += str.slice(lastIndex, match.index) + map.get(str.charCodeAt(match.index));
47-
lastIndex = regexp.lastIndex;
48-
} while ((match = regexp.exec(str)) !== null);
49-
return result + str.slice(lastIndex);
39+
let result = '';
40+
for (let i = 0, len = str.length; i < len; i++) {
41+
switch (str.charCodeAt(i)) {
42+
case 60: result += '&lt;'; break; // <
43+
case 62: result += '&gt;'; break; // >
44+
case 34: result += '&#x22;'; break; // "
45+
case 39: result += '&#x27;'; break; // '
46+
case 38: result += '&amp;'; break; // &
47+
default: result += str[i]; break;
48+
}
49+
}
50+
return result;
5051
};
5152
})();
5253

misc/benchmark-escapeHTML.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env node
2+
3+
import assert from 'assert';
4+
import { bench, run, summary, barplot } from 'mitata';
5+
6+
const escapeHTML_self_all = (function () {
7+
return function (str) {
8+
if (typeof str !== 'string') str = String(str);
9+
let result = '';
10+
for (let i = 0, len = str.length; i < len; i++) {
11+
switch (str.charCodeAt(i)) {
12+
case 60: result += '&lt;'; break; // <
13+
case 62: result += '&gt;'; break; // >
14+
case 34: result += '&#x22;'; break; // "
15+
case 39: result += '&#x27;'; break; // '
16+
case 38: result += '&amp;'; break; // &
17+
default: result += str[i]; break;
18+
}
19+
}
20+
return result;
21+
};
22+
})();
23+
24+
const escapeHTML_self_case = (function () {
25+
const regexp = /[<>"'&]/g;
26+
return function (str) {
27+
if (typeof str !== 'string') str = String(str);
28+
regexp.lastIndex = 0;
29+
let match = regexp.exec(str);
30+
if (match === null) return str;
31+
let result = '', lastIndex = 0, e;
32+
do {
33+
switch (str.charCodeAt(match.index)) {
34+
case 60: e = '&lt;'; break; // <
35+
case 62: e = '&gt;'; break; // >
36+
case 34: e = '&#x22;'; break; // "
37+
case 39: e = '&#x27;'; break; // '
38+
case 38: e = '&amp;'; break; // &
39+
default: e = ''; break;
40+
}
41+
result += str.slice(lastIndex, match.index) + e;
42+
lastIndex = regexp.lastIndex;
43+
} while ((match = regexp.exec(str)) !== null);
44+
return result + str.slice(lastIndex);
45+
};
46+
})();
47+
48+
const escapeHTML_self_map = (function () {
49+
const map = new Map([ [ `&`, `&amp;` ], [ `<`, `&lt;` ], [ `>`, `&gt;` ], [ `"`, `&#x22;` ], [ `'`, `&#x27;` ] ].map(([k, v]) => [k.charCodeAt(0), v]));
50+
const regexp = /[<>"'&]/g;
51+
return function (str) {
52+
if (typeof str !== 'string') str = String(str);
53+
regexp.lastIndex = 0;
54+
let match = regexp.exec(str);
55+
if (match === null) return str;
56+
let result = '', lastIndex = 0;
57+
do {
58+
result += str.slice(lastIndex, match.index) + map.get(str.charCodeAt(match.index));
59+
lastIndex = regexp.lastIndex;
60+
} while ((match = regexp.exec(str)) !== null);
61+
return result + str.slice(lastIndex);
62+
};
63+
})();
64+
65+
const escapeHTML_self_map_k = (function () {
66+
const map = new Map([ [ `&`, `&amp;` ], [ `<`, `&lt;` ], [ `>`, `&gt;` ], [ `"`, `&#x22;` ], [ `'`, `&#x27;` ] ]);
67+
const regexp = /[<>"'&]/g;
68+
return function (str) {
69+
if (typeof str !== 'string') str = String(str);
70+
regexp.lastIndex = 0;
71+
let match = regexp.exec(str);
72+
if (match === null) return str;
73+
let result = '', lastIndex = 0;
74+
do {
75+
result += str.slice(lastIndex, match.index) + map.get(match[0]);
76+
lastIndex = regexp.lastIndex;
77+
} while ((match = regexp.exec(str)) !== null);
78+
return result + str.slice(lastIndex);
79+
};
80+
})();
81+
82+
const escapeHTML_self_obj = (function () {
83+
const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\x22': '&#x22;', '\x27': '&#x27;' };
84+
const regexp = /[<>"'&]/g;
85+
return function (str) {
86+
if (typeof str !== 'string') str = String(str);
87+
regexp.lastIndex = 0;
88+
let match = regexp.exec(str);
89+
if (match === null) return str;
90+
let result = '', lastIndex = 0;
91+
do {
92+
result += str.slice(lastIndex, match.index) + map[str.charAt(match.index)];
93+
lastIndex = regexp.lastIndex;
94+
} while ((match = regexp.exec(str)) !== null);
95+
return result + str.slice(lastIndex);
96+
};
97+
})();
98+
99+
const escapeHTML_replace_map = (function () {
100+
const map = new Map([ [ `&`, `&amp;` ], [ `<`, `&lt;` ], [ `>`, `&gt;` ], [ `"`, `&#x22;` ], [ `'`, `&#x27;` ] ]);
101+
const regexp = /[<>"'&]/g;
102+
return function (str) {
103+
if (typeof str !== 'string') str = String(str);
104+
return str.replace(regexp, (match) => map.get(match));
105+
};
106+
})();
107+
108+
const escapeHTML_replace_obj = (function () {
109+
const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\x22': '&#x22;', '\x27': '&#x27;' };
110+
return function (string) { return (''+string).replace(/[&<>\'\"]/g, function (_) { return map[_] }) };
111+
})();
112+
113+
114+
const target = 'Hello <world> & "everyone"\'s welcome!'.repeat(1000);
115+
barplot(() => {
116+
summary(() => {
117+
bench('escapeHTML_self_case', () => {
118+
escapeHTML_self_case(target);
119+
});
120+
bench('escapeHTML_self_map', () => {
121+
escapeHTML_self_map(target);
122+
});
123+
bench('escapeHTML_self_map_k', () => {
124+
escapeHTML_self_map_k(target);
125+
});
126+
bench('escapeHTML_self_obj', () => {
127+
escapeHTML_self_obj(target);
128+
});
129+
bench('escapeHTML_replace_map', () => {
130+
escapeHTML_replace_map(target);
131+
});
132+
bench('escapeHTML_replace_obj', () => {
133+
escapeHTML_replace_obj(target);
134+
});
135+
bench('escapeHTML_self_all', () => {
136+
escapeHTML_self_all(target);
137+
});
138+
});
139+
});
140+
141+
await run();
142+

package-lock.json

Lines changed: 2 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)