首页 > 代码库 > 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前端模板引擎源码解读【二】