Tuesday, September 18, 2012

Javascript string replacing

I spend a lot of time working in JavaScript.  I also find that I spend a lot of time replacing text in strings in this language.  Most of the time this can be done easily by using a good templating system. When you are working with localization systems, you can't rely on templating.

My first attempt at string replace was:
var stamp = function(template, tag, value){
    return template.replace('{'+ tag + '}', value);
};

This was a very simple attempt and wasn't very good.  It has a lot of problems. Top of the list is it will not get every instance of the tag in the string. You end up having to call stamp several times to replace the same thing.  So I set out to fix this problem.  I found you could use regular expressions to do this kind of thing.  I built out this simple function to do the work.
var stampValues = function (template, replacements) {
    for (var tag in replacements) {
        var reg = new RegExp('\{' + tag + '\}', 'g');
        template = template.replace(reg, replacements[tag]);
    }
    return template;
};

It's much better than the above one because it gets all the tags in the string. It's actually much better than any other code I have tried but more on that later.

The next version looked like this.  It uses underscore to do the flow control because each is better than "for" -- right?
var stampValuesEx0 = function (template, replacements) {
    _(replacements).each(function (value, tag) {
        var reg = new RegExp('\{' + tag + '\}', 'g');
        template = template.replace(reg, value);
    });
    return template;
};

It's not. It's slower.  Not noticeably slower but when you put a timer to it the stampValues is faster.

After looking at this I realized I was doing the Regular expression incorrectly. I could change the way I did it and probably get more speed out of it.  So I built this . . .
var stampValuesEx2 = function (template, replacements) {
    var setValues = (function () {
        var translateReg = /{\w+}/g;
        var cleanup = /[{}]/g;
        return function (s) {
            return (s.replace(translateReg, function (match) {
                return replacements[match.replace(cleanup, '')];
            }));
        };
    })();
    return setValues(template);
}

And It SUCKS.  It's slow; it takes double the time of the stampValues function.  There are a few things you can do to speed it up.  You can build out a closure with the regular expressions already in them this actually helped a lot. It cuts about 1/3 of the execution time.  That version looks like this:
var stampValuesEx3 = (function () {
    var translateReg = /{\w+}/g;
    var cleanup = /[{}]/g;
    return function (template, replacements) {
        return (template.replace(translateReg, function (match) {
            return replacements[match.replace(cleanup, '')];
        }));
    }
})();
It still not as fast as stampValues but it is faster than other attempts I have made.

In short I ended up using stampValues for my string replace function.

The code I used to test all these functions can be found at here take a look. If you have examples that you think are better or faster I would be interested in seeing them.