Simple implementation of the Javascript string template
- 2020-12-19 20:55:31
- OfStack
This comes from a problem I had two years ago when I was working on my first real website
The site uses a separate front and back end, with the REST interface on the back end returning JSON data, which is then rendered on the page by the front end.
Like many newcomers to Javascript, I started by embedding JSON data into HTML in the form of concatenated strings. It starts with a small amount of code and is acceptable for the time being. But as the page structure becomes more complex, its weaknesses become unbearable:
The writing is incoherent. Every time I write a variable I have to break 1, insert 1 + and ". Ten is easy to make mistakes. Can't be reused. HTML fragments are discrete data, and it is difficult to extract the repeated parts. Can't put it to good use < template > The label. This is one of the new tags in HTML5, where the HTML template is highly recommended < template > Tag to make the code cleaner. That was my feeling:Is this TMD kidding me
To solve this problem, I put my project on hold and spent half an hour implementing a very simple string template.
Requirements describe
Implement 1 render(template, context) method and fill the placeholder in template with context. Requirements:
There is no need for control flow components (such as loops, conditions, and so on), as long as there is a variable substitution function
Cascading variables can also be expanded
Escaped delimiters {and} should not be rendered. White space is allowed between delimiters and variables
Example:
render('My name is {name}' . {
name: 'hsfzxjy'
}); // My name is hsfzxjy
render('I am in {profile.location}', {
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 }
implementation
First write down the framework of the function:
function render(template, context) {
}
Obviously, the first thing to do is match the placeholder in the template.
Match the placeholder
The match must be left to the regular expression. 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 requirement 3, the first concept That comes to my mind is forward matching. Unfortunately, Javascript does not support it, so I have to use a compromise:
var reg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
// If the first 1 Or the first 3 If the group value is not null, it will not be rendered
Now, the code should look like this:
function render(template, context) {
var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
return template.replace(tokenReg, function (word, slash1, token, slash2) {
if (slash1 || slash2) { // Matches to escape characters
return word.replace('\\', ''); // if The delimiter is escaped and is not rendered
}
// ...
})
}
Placeholder substitution
Well, the regular expression confirms that, and the next thing to do is replace.
According to requirement 2, the template engine must be able to render not only level 1 variables, but also levels of variables. How do you do that?
In fact, it is very simple: separate token by., and search level by level:
var variables = token.replace(/\s/g, '').split('.'); // cutting token
var currentObject = context;
var i, length, variable;
// Step by step to find context
for (i = 0, length = variables.length, variable = variables[i]; i < 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, the code should be fault-tolerant:
var variables = token.replace(/\s/g, '').split('.'); // cutting token
var currentObject = context;
var i, length, variable;
for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i) {
currentObject = currentObject[variable];
if (currentObject === undefined || currentObject === null) return ''; // Returns an empty string if the object currently indexed does not exist.
}
return currentObject;
Combine all the code in one, and you get the final version:
function render(template, context) {
var tokenReg = /(\\)?\{([^\{\}\\]+)(\\)?\}/g;
return template.replace(tokenReg, function (word, slash1, token, slash2) {
if (slash1 || slash2) {
return word.replace('\\', '');
}
var variables = token.replace(/\s/g, '').split('.');
var currentObject = context;
var i, length, variable;
for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i) {
currentObject = currentObject[variable];
if (currentObject === undefined || currentObject === null) return '';
}
return currentObject;
})
}
Remove the blank lines for a total of 17 lines.
Hang the function to the prototype chain of String
We can even modify the prototype chain to achieve some cool effects:
String.prototype.render = function (context) {
return render(this, context);
};
After that, we can call:
"{greeting}! My name is { author.name }.".render({
greeting: "Hi",
author: {
name: "hsfzxjy"
}
});
// Hi! My name is hsfzxjy.