SoFunction
Updated on 2025-03-06

Simple implementation of Javascript string templates

This is a problem that I encountered two years ago when I was the first website in my life in real sense.

The website adopts the method of separation of front and back ends, and returns JSON data from the REST interface of the back end, and then renders it to the page by the front end.

Like many beginners who are new to Javascript, at first, I also used the form of splicing strings to embed JSON data into HTML. The code volume is small at the beginning and is acceptable for the time being. But when the page structure becomes complex, its weaknesses begin to become unbearable:

  1. Writing is incoherent. Every time you write a variable, you have to break it and insert a + and ". It is very easy to make mistakes.
  2. Cannot be reused. HTML fragments are discrete data, and it is difficult to extract duplicate parts.
  3. Not able to make good use of the <template> tag. This is a new tag added in HTML5. The standard highly recommends putting HTML templates into the <template> tag to make the code more concise.
  4. This was my mood at that time:

Is this TMD teasing me?

To solve this problem, I temporarily put down the project and spent half an hour to implement a very simple string template.

Requirement description

Implement a render(template, context) method to fill the placeholders in template with context. Require:

There is no need to have control flow components (such as loops, conditions, etc.), as long as there is a variable replacement function.
Cascading variables can also be expanded
The escaped delimiters { and } should not be rendered, and whitespace characters are allowed between the delimiter and the variable
example:

render('My name is {name}', {
  name: 'hsfzxjy'
}); // My name is hsfzxjy

render('I am in {}', {
  name: 'hsfzxjy',
  profile: {
    location: 'Guangzhou'
  }
}); // I am in Guangzhou

render('{ greeting }. \\{ This block will not be rendered }', {
  greeting: 'Hi'
}); // Hi. { This block will not be rendered }

accomplish

Write down the function framework first:

function render(template, context) {

}

Obviously, the first thing to do is match the placeholders in the template.

Match placeholders

The matching matter must be done by regular expressions. So, what should this regular expression look like?

According to the description of requirements 1 and 2, we can write:

var reg = /\{([^\{\}]+)\}/g;

As for requirements 3, the first concept that comes to my mind is forward matching. Unfortunately, Javascript does not support it, so I had to use a compromise method:

var reg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
// If the first or third group value is not empty, no renderingNow,This is the code:

function render(template, context) {

  var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;

  return (tokenReg, function (word, slash1, token, slash2) {
    if (slash1 || slash2) { // Match to escaped characters      return ('\\', ''); // If the delimiter is escaped, it will not be rendered    }

    // ...
  })
}

Placeholder replacement

Well, the regular expression is determined, and the next thing to do is replace it.

According to Requirement 2, the template engine must not only be able to render first-level variables, but also multi-level variables. How to do this?

It's actually very simple: separate the tokens and search them step by step:

var variables = (/\s/g, '').split('.'); // Cut tokenvar currentObject = context;
var i, length, variable;

// Find context step by stepfor (i = 0, length = , variable = variables[i]; i &lt; length; ++i)
  currentObject = currentObject[variable];

return currentObject;

However, it is possible that the variable specified by token does not exist, and the above code will report an error. For a better experience, it is best to have certain fault tolerance for the code:

var variables = (/\s/g, '').split('.'); // Cut tokenvar currentObject = context;
var i, length, variable;

for (i = 0, length = , variable = variables[i]; i &lt; length; ++i) {
  currentObject = currentObject[variable];
  if (currentObject === undefined || currentObject === null) return ''; // If the object currently indexed does not exist, the empty string is returned directly.}

return currentObject;

Combine all the code together and you get the final version:

function render(template, context) {

  var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;

  return (tokenReg, function (word, slash1, token, slash2) {
    if (slash1 || slash2) { 
      return ('\\', '');
    }

    var variables = (/\s/g, '').split('.');
    var currentObject = context;
    var i, length, variable;

    for (i = 0, length = , variable = variables[i]; i < length; ++i) {
      currentObject = currentObject[variable];
      if (currentObject === undefined || currentObject === null) return '';
    }

    return currentObject;
  })
}

Remove blank lines, there are 17 lines in total.

Hang the function to the prototype chain of String

Even, we can achieve some cool effects by modifying the prototype chain:

 = function (context) {
  return render(this, context);
};

After that, we can call it like this:

"{greeting}! My name is {  }.".render({
  greeting: "Hi",
  author: {
    name: "hsfzxjy"
  }
});
// Hi! My name is hsfzxjy.