After reading the parser code, this one will read the renderer.
In the previous interpretation, parsetemplate How to parse the template code into a tree structure of the tokens array, according to the usual writing mustache habit, after using the parse, is directly used xx.innerhtml = Mustache.render ( template, obj), because the parse parsing will be called before parsing, the parsing results will be cached, so when the call to render, the cache will be read first, if there is no relevant parsing data in the cache, then call parse to parse.
function (template, view, partials) { varthis. Parse (template); // instantiate the incoming JS object into a context object var instanceof New Context (view); return This . Rendertokens (tokens, context, partials, template); };
Visible, before the final parsing of the Rendertokens function, but also to pass in the object to be rendered in the data processing, that is, to wrap the data into a context object. So let's look at the code for the context section first:
functionContext (view, parentcontext) { This. View = View = =NULL?{}: view; This. cache = {'. ': This. View}; This. Parent =Parentcontext; } /** Instantiate a new context object, passing in the current context object into the parent object property of the newly generated context object in parent*/Context.prototype.push=function(view) {return NewContext (View, This); }; /** * Get the value of name in JS object*/Context.prototype.lookup=function(name) {varCache = This. Cache; varvalue; if(Nameinchcache) {Value=Cache[name]; } Else { varContext = This, names, index; while(context) {if(Name.indexof ('. ') > 0) {Value=Context.view; Names= Name.split ('. ')); Index= 0; while(Value! =NULL&& Index <names.length) Value= value[names[index++]]; } Else if(typeofContext.view = = ' object ') {Value=Context.view[name]; } if(Value! =NULL) Break; Context=context.parent; } Cache[name]=value; } if(isfunction (value)) value= Value.call ( This. View); Console.log (value)returnvalue; };
The context part of the code is also very few, the context is specifically for the tree structure to provide the factory class, the context of the constructor, This.cache = {'. ': This.view} is to cache the data that needs to be rendered, and in the later lookup method, Peel the required attribute value from This.view to the first level of the cache, which is the cache[name in the lookup method] = value, which makes it easier to find it in the cache first.
The context push method is simple, which is to form a tree-like relationship, to pass the new data into a new context object, and to point the parent value of the new context object to the original context object.
The lookup method of the context is to get the value of name in the rendered object, we step by step to analyze, first determine whether the name is in the first layer of the cache, if not, in depth acquisition. Then a while loop will be made:
First determine if the name has. This character, if a bit, describes the format of name xxx.xx, which is typically the form of a key value. The name is then passed. Separate into an array names, traversing the names array through the while loop, looking for the value with the name key in the data that needs to be rendered.
If name does not exist. This character, the description is a simple key, first determine whether the data type to be rendered is an object, if so, directly get the value of name in the rendered data.
With two levels of judgment, if a value is not found, the current context is placed as the parent of the context and the parent object is searched until value is found or the current context has no parent object. If found, the value is cached.
After reading the context class code, you can see the code of the renderer:
Writer.prototype.renderTokens =function(tokens, context, partials, originaltemplate) {varBuffer = "; varSelf = This; functionSubrender (template) {returnSelf.render (template, context, partials); } vartoken, value; for(vari = 0, numtokens = tokens.length; i < Numtokens; ++i) {token=Tokens[i]; Switch(token[0]) { Case‘#‘: Value= Context.lookup (token[1]);//gets the value of XX in the passed-in object in {{#XX}} if(!value)Continue;//Skip if not present //if it is an array, it means to make a copy of the HTML and get the rendered result in the array recursively . if(IsArray (value)) { for(varj = 0, valuelength = value.length; J < Valuelength; ++j) {//gets the HTML rendered by valueBuffer + = This. Rendertokens (token[4], Context.push (Value[j]), partials, originaltemplate); } } Else if(typeofValue = = = ' object ' | |typeofValue = = = ' String ') { //if value is an object, no loop is used, and the next recursion is entered according to valueBuffer + = This. Rendertokens (token[4], Context.push (value), partials, originaltemplate); } Else if(isfunction (value)) {//if value is a method, the method is executed, and the return value is saved if(typeofOriginaltemplate!== ' string ') Throw NewError (' cannot use Higher-order sections without the original template '); //Extract the portion of the original template , the section contains.Value = Value.call (Context.view, Originaltemplate.slice (token[3], token[5]), subrender); if(Value! =NULL) Buffer+=value; } Else{buffer+= This. Rendertokens (token[4], context, partials, originaltemplate); } Break; Case^: //if {{^xx}}, the rendering is triggered when value does not exist (NULL, undefine, 0, ') or is an empty arrayValue = Context.lookup (token[1]); //Use JavaScript ' s definition of falsy. Include empty arrays. //See https://github.com/janl/mustache.js/issues/186 if(!value | | (IsArray (value) && value.length = = = 0)) Buffer+= This. Rendertokens (token[4], context, partials, originaltemplate); Break; Case' > ': //prevent an object from being present if(!partials)Continue; //> reads the value directly and executes if Partials is the method, otherwise gets the value with the token as the keyValue = Isfunction (partials)? Partials (Token[1]): partials[token[1]]; if(Value! =NULL) Buffer+= This. Rendertokens ( This. Parse (value), context, partials, value); Break; Case' & ': //if &, indicates that the property appears as HTML, gets its value through the lookup method, and then overlays it in bufferValue = Context.lookup (token[1]); if(Value! =NULL) Buffer+=value; Break; Case' Name ': //if the name is described as a property value and is not displayed as HTML, the HTML keyword in value is transcoded by Mustache.escape the Escapehtml methodValue = Context.lookup (token[1]); if(Value! =NULL) Buffer+=Mustache.escape (value); Break; Case' Text ': //if text is normal HTML code, overlay directlyBuffer + = Token[1]; Break; } } returnbuffer; };
The principle is still relatively simple, because the tree structure of the tokens has been formed, rendering data only need to follow the tree structure of the sequence to traverse the output.
However, it is still approximate to describe that buffer is used to store the rendered data, traversing the tokens array, and using switch to determine the current token type:
If it is #, get the value of XX in the rendered object to {{#XX}} first, and if there is no value, skip the secondary loop directly, and if so, determine if value is an array, if it is an array, the HTML is to be replicated, then the value is traversed, and the rendered HTML data is fetched recursively. If value is an object or a normal string, the HTML that is rendered with value is directly obtained without looping the output, and if value is a method, the method is executed and the return value is superimposed as a result into buffer. if it is ^, when value does not exist or if value is an array and the array is empty, the rendering data is obtained, and other judgments are similar.
With this stack of judgments and recursive calls, the data can be rendered.
At this point, mustache's source code is also read, the core of mustache is a parser plus a renderer, with very concise code to implement a powerful template engine.
Mustache.js front-end template engine source interpretation "two"