In the simplest case, you can add one digit at a time, keeping track of carry:
var ndigits = 4, i, carry = 0, d, result = "";
for (i = ndigits - 1; i >= 0; i--) {
d = parseInt(a[i], 16) + parseInt(b[i], 16) + carry;
carry = d >> 4;
result = (d & 15).toString(16) + result;
}
If performance is an issue, you might prefer to handle more than a single digit at a time, but then things become either difficult or you have to hard-code the number of digits. Even then, zero-padding stuff will take some work. Here is a solution which does 20 hex digits in three steps, so that no number is more than 32 bits long:
function pad(s, n) { while (s.length < n) s = "0" + s; return s; }
d = parseInt(a.substr(13), 16) + parseInt(b.substr(13), 16);
result = pad((d & 0xfffffff).toString(16), 7);
d = parseInt(a.substr(6, 7), 16) + parseInt(b.substr(6, 7), 16) + (d >> 28);
result = pad((d & 0xfffffff).toString(16), 7) + result;
d = parseInt(a.substr(0, 6), 16) + parseInt(b.substr(0, 6), 16) + (d >> 28);
result = pad((d & 0xffffff).toString(16), 6) + result;
According to jsPerf, this code seems to be three times faster than the above one, at least on some browsers.