首页 > 代码库 > Mustache.js前端模板引擎源码解读【二】
Mustache.js前端模板引擎源码解读【二】
上一篇解读完解析器的代码,这一篇就来解读一下渲染器。
在之前的解读中,解读了parseTemplate如何将模板代码解析为树形结构的tokens数组,按照平时写mustache的习惯,用完parse后,就是直接用 xx.innerHTML = Mustache.render(template , obj),因为此前会先调用parse解析,解析的时候会将解析结果缓存起来,所以当调用render的时候,就会先读缓存,如果缓存里没有相关解析数据,再调用一下parse进行解析。
Writer.prototype.render = function (template, view, partials) { var tokens = this.parse(template); //将传进来的js对象实例化成context对象 var context = (view instanceof Context) ? view : new Context(view); return this.renderTokens(tokens, context, partials, template); };
可见,进行最终解析的renderTokens函数之前,还要先把传进来的需要渲染的对象数据进行处理一下,也就是把数据包装成context对象。所以我们先看下context部分的代码:
function Context(view, parentContext) { this.view = view == null ? {} : view; this.cache = { ‘.‘: this.view }; this.parent = parentContext; } /** * 实例化一个新的context对象,传入当前context对象成为新生成context对象的父对象属性parent中 */ Context.prototype.push = function (view) { return new Context(view, this); }; /** * 获取name在js对象中的值 */ Context.prototype.lookup = function (name) { var cache = this.cache; var value; if (name in cache) { value = cache[name]; } else { var context = 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 (typeof context.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) return value; };
context部分代码也是很少,context是专门为树形结构提供的工厂类,context的构造函数中,this.cache = {‘.‘:this.view}是把需要渲染的数据缓存起来,同时在后面的lookup方法中,把需要用到的属性值从this.view中剥离到缓存的第一层来,也就是lookup方法中的cache[name] = value,方便后期查找时先在缓存里找
context的push方法比较简单,就是形成树形关系,将新的数据传进来封装成新的context对象,并且将新的context对象的parent值指向原来的context对象。
context的lookup方法,就是获取name在渲染对象中的值,我们一步一步来分析,先是判断name是否在cache中的第一层,如果不在,才进行深度获取。然后将进行一个while循环:
先是判断name是否有.这个字符,如果有点的话,说明name的格式为XXX.XX,也就是很典型的键值的形式。然后就将name通过.分离成一个数组names,通过while循环遍历names数组,在需要渲染的数据中寻找以name为键的值。
如果name没有.这个字符,说明是一个单纯的键,先判断一下需要渲染的数据类型是否为对象,如果是,就直接获取name在渲染的数据里的值。
通过两层判断,如果没找到符合的值,则将当前context置为context的父对象,再对其父对象进行寻找,直至找到value或者当前context无父对象为止。如果找到了,将值缓存起来。
看完context类的代码,就可以看渲染器的代码了:
Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) { var buffer = ‘‘; var self = this; function subRender(template) { return self.render(template, context, partials); } var token, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { token = tokens[i]; switch (token[0]) { case ‘#‘: value = context.lookup(token[1]); //获取{{#XX}}中XX在传进来的对象里的值 if (!value) continue; //如果不存在则跳过 //如果为数组,说明要复写html,通过递归,获取数组里的渲染结果 if (isArray(value)) { for (var j = 0, valueLength = value.length; j < valueLength; ++j) { //获取通过value渲染出的html buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate); } } else if (typeof value =http://www.mamicode.com/== ‘object‘ || typeof value =http://www.mamicode.com/== ‘string‘) { //如果value为对象,则不用循环,根据value进入下一次递归 buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate); } else if (isFunction(value)) { //如果value是方法,则执行该方法,并且将返回值保存 if (typeof originalTemplate !== ‘string‘) throw new Error(‘Cannot use higher-order sections without the original template‘); // Extract the portion of the original template that the section contains. value = http://www.mamicode.com/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 ‘^‘: //如果为{{^XX}},则说明要当value不存在(null、undefine、0、‘‘)或者为空数组的时候才触发渲染 value = http://www.mamicode.com/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 ‘>‘: //防止对象不存在 if (!partials) continue; //>即直接读取该值,如果partials为方法,则执行,否则获取以token为键的值 value = http://www.mamicode.com/isFunction(partials) ? partials(token[1]) : partials[token[1]]; if (value != null) buffer += this.renderTokens(this.parse(value), context, partials, value); break; case ‘&‘: //如果为&,说明该属性下显示为html,通过lookup方法获取其值,然后叠加到buffer中 value = http://www.mamicode.com/context.lookup(token[1]); if (value != null) buffer += value; break; case ‘name‘: //如果为name说明为属性值,不作为html显示,通过mustache.escape即escapeHtml方法将value中的html关键词转码 value = http://www.mamicode.com/context.lookup(token[1]); if (value != null) buffer += mustache.escape(value); break; case ‘text‘: //如果为text,则为普通html代码,直接叠加 buffer += token[1]; break; } } return buffer; };
原理还是比较简单的,因为tokens的树形结构已经形成,渲染数据就只需要按照树形结构的顺序进行遍历输出就行了。
不过还是大概描述一下,buffer是用来存储渲染后的数据,遍历tokens数组,通过switch判断当前token的类型:
如果是#,先获取到{{#XX}}中的XX在渲染对象中的值value,如果没有该值,直接跳过该次循环,如果有,则判断value是否为数组,如果为数组,说明要复写html,再遍历value,通过递归获取渲染后的html数据。如果value为对象或者普通字符串,则不用循环输出,直接获取以value为参数渲染出的html,如果value为方法,则执行该方法,并且将返回值作为结果叠加到buffer中。如果是^,则当value不存在或者value是数组且数组为空的时候,才获取渲染数据,其他判断都是差不多。
通过这堆判断以及递归调用,就可以把数据完成渲染出来了。
至此,Mustache的源码也就解读完了,Mustache的核心就是一个解析器加一个渲染器,以非常简洁的代码实现了一个强大的模板引擎。
Mustache.js前端模板引擎源码解读【二】