How can I do string interpolation in JavaScript?

Viewed 484888

Consider this code:

var age = 3;

console.log("I'm " + age + " years old!");

Are there any other ways to insert the value of a variable in to a string, apart from string concatenation?

21 Answers

The simplest would be

`my string ${VARIABLE}`

a less efficient way to accomplish this would be

function format(str, ...params) {
  for(const param of params)
    str = str.replace("%", param);
   return str;
}

which can be used with

format("My % string", "interpolation")
let age = 3;

console.log(`I'm ${age} years old!`);

You can use the backticks `` and ES6 template string

I can show you with an example:

function fullName(first, last) {
  let fullName = first + " " + last;
  return fullName;
}

function fullNameStringInterpolation(first, last) {
  let fullName = `${first} ${last}`;
  return fullName;
}

console.log('Old School: ' + fullName('Carlos', 'Gutierrez'));

console.log('New School: ' + fullNameStringInterpolation('Carlos', 'Gutierrez'));

Couldn't find what I was looking for, then found it -

If you're using Node.js, there's a built-in utilpackage with a format function that works like this:

util.format("Hello my name is %s", "Brent");
> Hello my name is Brent

Coincidentally this is now built into console.log flavors too in Node.js -

console.log("This really bad error happened: %s", "ReferenceError");
> This really bad error happened: ReferenceError

Since ES6, if you want to do string interpolation in object keys, you will get a SyntaxError: expected property name, got '${' if you do something like:

let age = 3
let obj = { `${age}`: 3 }

You should do the following instead:

let obj = { [`${age}`]: 3 }

Supplant more for ES6 version of @Chris Nielsen's post.

String.prototype.supplant = function (o) {
  return this.replace(/\${([^\${}]*)}/g,
    (a, b) => {
      var r = o[b];
      return typeof r === 'string' || typeof r === 'number' ? r : a;
    }
  );
};

string = "How now ${color} cow? {${greeting}}, ${greeting}, moo says the ${color} cow.";

string.supplant({color: "brown", greeting: "moo"});
=> "How now brown cow? {moo}, moo, moo says the brown cow."

Using template syntax fails in older browsers, important if you are creating HTML for public use. Using concatenation is tedious and hard to read, particularly if you have many or long expressions, or if you must use parentheses to handle mixtures of number and string items (both of which use the + operator).

PHP expands quoted strings containing variables and even some expressions using a very compact notation: $a="the color is $color";

In JavaScript, an efficient function can be written to support this: var a=S('the color is ',color);, using a variable number of arguments. While there is no advantage over concatenation in this example, when the expressions get longer this syntax may be clearer. Or one can use the dollar sign to signal the start of an expression using a JavaScript function, as in PHP.

On the other hand, writing an efficient workaround function to provide template-like expansion of strings for older browsers wouldn't be hard. Someone has probably done it already.

Finally, I imagine that sprintf (as in C, C++, and PHP) could be written in JavaScript, although it would be a little less efficient than these other solutions.

Custom flexible interpolation:

var sourceElm = document.querySelector('input')

// interpolation callback
const onInterpolate = s => `<mark>${s}</mark>`

// listen to "input" event
sourceElm.addEventListener('input', parseInput) 

// parse on window load
parseInput() 

// input element parser
function parseInput(){
  var html = interpolate(sourceElm.value, undefined, onInterpolate)
  sourceElm.nextElementSibling.innerHTML = html;
}

// the actual interpolation 
function interpolate(str, interpolator = ["{{", "}}"], cb){
  // split by "start" pattern
  return str.split(interpolator[0]).map((s1, i) => {
    // first item can be safely ignored
   if( i == 0 ) return s1;
    // for each splited part, split again by "end" pattern 
    const s2 = s1.split(interpolator[1]);

    // is there's no "closing" match to this part, rebuild it
    if( s1 == s2[0]) return interpolator[0] + s2[0]
    // if this split's result as multiple items' array, it means the first item is between the patterns
    if( s2.length > 1 ){
        s2[0] = s2[0] 
          ? cb(s2[0]) // replace the array item with whatever
          : interpolator.join('') // nothing was between the interpolation pattern
    }

    return s2.join('') // merge splited array (part2)
  }).join('') // merge everything 
}
input{ 
  padding:5px; 
  width: 100%; 
  box-sizing: border-box;
  margin-bottom: 20px;
}

*{
  font: 14px Arial;
  padding:5px;
}
<input value="Everything between {{}} is {{processed}}" />
<div></div>

While templates are probably best for the case you describe, if you have or want your data and/or arguments in iterable/array form, you can use String.raw.

String.raw({
  raw: ["I'm ", " years old!"]
}, 3);

With the data as an array, one can use the spread operator:

const args = [3, 'yesterday'];
String.raw({
  raw: ["I'm ", " years old as of ", ""]
}, ...args);
Related