3

Saw this on https://developers.google.com/speed/articles/optimizing-javascript

Can someone please explain how this is more efficient, i.e., why this avoids temporary string results?

Build up long strings by passing string builders (either an array or a helper class) into functions, to avoid temporary result strings.

For example, assuming buildMenuItemHtml_ needs to build up a string from literals and variables and would use a string builder internally, instead of using:

var strBuilder = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
  strBuilder.push(this.buildMenuItemHtml_(menuItems[i]));
}
var menuHtml = strBuilder.join();

Use:

var strBuilder = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
  this.buildMenuItem_(menuItems[i], strBuilder);
}
var menuHtml = strBuilder.join();

I assumed in the first case buildMenuItemHtml_ returned a string and in the second case buildMenuItemHtml_ pushed a string onto strBuilder.

3 Answers 3

2

The important detail is where it says that buildMenuItemHtml_ builds a string from a bunch of different elements. Between the two examples, this happens in two different ways:

Example 1 (builds a string directly):

function buildMenuItemHtml_(data) {
  return ["<li>",data.x,"</li>"].join();
}

or Example 2 (uses a supplied string builder):

function buildMenuItemHtml_(data, strBuilder) {
  strBuilder.push("<li>");
  strBuilder.push(data.x);
  strBuilder.push("</li>");
}

String building is relatively expensive and wasteful since it requires lots of allocations and copies, some of which are temporary and likely to get thrown out. Pushing onto an array is comparatively cheap, and join()ing even a large array ONCE is better than join()ing a whole bunch of small arrays many times.

So rather than having each iteration of the loop build a temporary string (expensive), only to go and build another string (expensive) with those strings, the guideline suggests passing your main string builder directly into the buildMenuItemHtml_ function. By doing this, you don't do any expensive string building until your whole collection of elements is ready -- i.e. once.

Sign up to request clarification or add additional context in comments.

Comments

1

By pushing the entire function this.buildMenutItemHtml_ into strBuilder you are essentially allocating memory each iteration for the entire this.buildMenuItemHtml function and all of it's internal variables which would create temporary string variables before it returns your string value to strBuilder. The second method will return the string from the original this.buildMenutItemHtml in memory and push that into strBuilder making it more efficient since it is not allocating new memory each iteration to handle the this.buildMenuItemHtml function.

Comments

0

I assumed in the first case buildMenuItemHtml_ returned a string

Yes.

and in the second case buildMenuItemHtml_ pushed a string onto strBuilder.

No. The relevant piece of information is "assuming buildMenuItemHtml_ needs to build up a string from literals and variables and would use a string builder internally".

Instead of using an internal string builder, evaluating it and pushing the [temporary, intermediate] result string to the passed string builder, it now would simply push all of the items to the passed string builder directly.

It does not make a difference if buildMenuItemHtml_ would just return a single literal or so and not do any string concatenation on its own.


Without actual example code for buildMenuItemHtml_, this is of course hard to understand. Maybe this helps:

Bad:

function buildMenuHtml(menuItems) {
    var strBuilder = ["<ul>"];
    for (var i = 0, length = menuItems.length; i < length; i++) {
        strBuilder.push(buildMenuItemHtml(menuItems[i]));
    }
    strBuilder.push("</ul>");
    var menuHtml = strBuilder.join();
    return menuHtml;
}
function buildMenuItemHtml(item) {
    var innerStrBuilder = ["<li>"];
    for (prop in item)
        innerStrBuilder.push(prop, ": ", item[prop]);
    innerStrBuilder.push("</li>");
    var itemHtml = innerStrBuilder.join();
    return itemHtml;
}

Good:

function buildMenuHtml(menuItems) {
    var strBuilder = ["<ul>"];
    for (var i = 0, length = menuItems.length; i < length; i++) {
        buildMenuItemHtml(menuItems[i], strBuilder);
    }
    strBuilder.push("</ul>");
    var menuHtml = strBuilder.join();
    return menuHtml;
}
function buildMenuItemHtml(item, strBuilder) {
    strBuilder.push("<li>");
    for (prop in item)
        strBuilder.push(prop, ": ", item[prop]);
    strBuilder.push("</li>");
    // no intermediate itemHtml!
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.