码迷,mamicode.com
首页 > Web开发 > 详细

Mustache.js前端模板引擎源码解读【二】

时间:2014-12-30 11:26:24      阅读:272      评论:0      收藏:0      [点我收藏+]

标签:

  上一篇解读完解析器的代码,这一篇就来解读一下渲染器。

  在之前的解读中,解读了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 === ‘object‘ || typeof value === ‘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 = 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 = 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 = 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 = context.lookup(token[1]);

                    if (value != null)
                        buffer += value;

                    break;
                case ‘name‘:
                    //如果为name说明为属性值,不作为html显示,通过mustache.escape即escapeHtml方法将value中的html关键词转码
                    value = 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前端模板引擎源码解读【二】

标签:

原文地址:http://www.cnblogs.com/axes/p/4192860.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!