SoFunction
Updated on 2025-04-14

A brief analysis of the template class of Prototype Template

Anyone who has used Prototype knows that there is a class called Template, and the usage examples are as follows:
Copy the codeThe code is as follows:

var str = '#{what} may have gone, but there is a time of #{how}';
var object = {
what : 'Swallows',
how : 'return'
}
var template_1 = new Template(str);
var result = template_1.evaluate(object);

('result:',result);
//Output: 'Swallows may have gone, but there is a time of return'

It's so convenient, so let's briefly analyze the implementation principle below, which can be regarded as a note for source code interpretation.

Let’s first look at some of the situations that will be used in general requirements, or use the above examples to decide our form first. The replaced part is content like #{what}, where what is a keyword of an object object.
Now there is a question, if object is a nested object, how should we replace it?
Right now:
Copy the codeThe code is as follows:

<script type="text/javascript">
var object = {
what : {
name : 'Swallows'
},
how : 'return'
}
</script>

The initial #{what} definitely cannot meet the requirements, so we forcefully stipulate that if we want to replace nested objects, write #{} or #{what[name]}.

So the first example can be written:
Copy the codeThe code is as follows:

<script type="text/javascript">
var str = '#{} may have gone, but there is a time of #{how}';
//or str = '#{what[name]} may have gone, but there is a time of #{how}';
var object = {
what : {
name : 'Swallows'
},
how : 'return'
}
var template_1 = new Template(str);
var result = template_1.evaluate(object);

('result:',result);
//Output: 'Swallows may have gone, but there is a time of return'
</script>

There is a regular var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; in the source code, it is used to achieve this purpose. And so on, any deep nesting can be achieved.

To do replacement, the most core thing we need is to have a method to replace characters. The source code uses gsub. The simplest version of gsub is given below:
Copy the codeThe code is as follows:

<script type="text/javascript">
function gsub(str,pattern, replacement){
var result = '',
source = str,
match;
//Each match below is divided into three segments to operate
//Equivalent to $` $& $'
while( > 0){
match = (pattern);
if(match){
result += (0, );
result += replacement(match);
source = ( + match[0].length);
}else{
result += source;
source = '';
}
}
return result;
}
</script>

This calling method and principle are similar to the replace I mentioned earlier, and are basically interchangeable. /xesam/archive/2011/12/05/
Copy the codeThe code is as follows:

<script type="text/javascript">
(gsub('there is a time of #{how}',/(^|.|\r|\n)(#\{(.*?)\})/,function{
return 'demo';
}))
//Output there is a time of demo
</script>


Let’s go back to Template, the basic requirements: There are classes and methods
Copy the codeThe code is as follows:

<script type="text/javascript">
var Template = function(template, pattern){
= ();
= pattern || ;
};
= /(^|.|\r|\n)(#\{(.*?)\})/;
= function(object){}
</script>

template is the str in the initial example, pattern is the matching rule, object is the object in the initial example; now the focus is on the implementation of the evaluation method:
The gsub method is called directly, mainly the replacement method in gsub.
What may happen,
First, object is an empty object, so we directly delete the part that needs to be replaced.
for example

var str = '#{what} may have gone, but there is a time of #{how}';
var object = {}
Then just go back ' may have gone, but there is a time of '


Second, the escaped part is directly retained
Copy the codeThe code is as follows:

var str = '\\#{what} may have gone, but there is a time of \\#{how}';
var object = {
what : 'Swallows',
how : 'return'
}

Then just return '\\#{what} may have gone, but there is a time of \\#{how}';

These situations must be handled in the code, the specific code is as follows
Copy the codeThe code is as follows:

= function(object){
//gsub(str,pattern, replacement)
return gsub(,,function(match){
var before = match[1];//The match[1] here is the part that matches in (^|.|\r|\n)
var content = match[2];//The match[1] here is the part that matches in (#\{(.*?)\})
var expr = match[3];//The match[1] here is the part that matches in (.*?)
//Example:
//For s#{what} before='s',content='#{what}',expr='what'

//First, object is an empty object, so we directly delete the part that needs to be replaced
if(object == null){
return (match[1] + '');
}
//Second, the escaped part is directly retained
if (before == '\\'){
return content;
}

//In addition to the above two situations, the following is the normal replacement process.
var ctx = object;

//I mentioned the following rule before, to match nested objects. The last one (\.|\[|$) is to determine whether there are nested sub-objects to match.
// '.' in #{}
//or '[' in #{what[name]}
// That is, the match[3] below

var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
match = (expr);

while (match != null) {
if(/^\[/.test(match[1])){
var comp = match[2].replace(/\\\\]/g, ']');
}else{
var comp = match[1];
}
ctx = ctx[comp];//If ctx[comp] is a string, then the next step can be done. If ctx[comp] is still an object, then sorry, work overtime and continue working.

if (null == ctx || '' == match[3]){//The nested child object that needs to be replaced is not an object, then the loop will be interrupted directly. Replacement is successful and get off work.
break;
}

//The following is just to peel off the keywords, and other operations are used in the loop again.
if('[' == match[3]){
expr = (match[1].length)
}else{
expr = (match[0].length)
}
match = (expr);
}

return before + ctx;
});
};

Of course, the source code is not that simple, and it also involves writing detection and judgment, such as replacing illegal characters of str, handling replacement as strings, etc. For details, you can check the source code.
Some of the complexities are the case where replacement is a string. There is a nested call between gsub and Template, but the meaning is the same. Finally, just throw a function back.