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 === ‘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的核心就是一个解析器加一个渲染器,以非常简洁的代码实现了一个强大的模板引擎。

时间: 2024-08-27 22:15:07

Mustache.js前端模板引擎源码解读【二】的相关文章

Mustache.js前端模板引擎初识

目前在看一个项目,在一遍遍撸代码的时候,发现代码里调用了一个Mustache.render()方法. 经过百度后,发现Mustache.js是一套轻量级模板引擎. 所以现在就要仔细学习一下,它是如何工作的. 一般来说mustache在js中的使用方法都是如下: var template = $('#template').html(); Mustache.parse(template);   // optional, speeds up future uses var rendered = Mus

(转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

转自:http://www.baiyuxiong.com/?p=886 ----------------------------------------------------------------------- 上一篇go语言nsq源码解读-基本介绍  介绍了最基本的nsq环境搭建及使用.在最后使用时,我们用到了几个命令:nsqlookupd.nsqd.nsqadmin.curl及 nsq_to_file,并看到用curl命令写入的几个”hello world”被nsq_to_file命令保

artTemplate模板引擎源码拜读

最初接触的模板引擎还是基于node的ejs,当时觉得很神奇原来还可以这么玩,后来随着学习的深入,使用过jade,doT等,当然还有一些比较火的诸如juicer.underscore还没有深入接触,直到今年上半年由于项目需要就想着要不试试腾讯的artTemplate,感觉牛逼也吹的挺响的.开始了解后,觉得它比我之前使用过的jade.doT都好用,调试神马的也方便很多,采用预编译的方式也让性能非常优越. 其实看了源码后简单的总结出来就是这么一句话:就是先获取html中对应的id下得innerHTML

九千套前端模板及源码

以前会接到不少给小型企业建站的SH,然后花了不少时间和精力收集.购买了数千套前端源码.下面我将这些模板开源以回馈大家对我的支持.库里有关于旅游.美食.汽车等各种行业网站的适用模板,我之前做过一套二手房交易市场商城,使用tornado写的全栈项目,代码注释非常完整,感兴趣的可以去github仓库看看!以后也可以带大家做一套像下面这样的电子商城 行了,剩下的你们自己下载看看吧! 以后除了分享实用工具外,也会陆陆续续的领着大家开发全栈项目,已经设计好了几个实用工具.一起加油吧! 关注公众号并回复:前端

tars framework 源码解读(二) libservant部分源码的简介

还是直接用官方原图解说 服务端:可以理解成对外公开的接口 被调用时候响应流程 的底层封装 (响应端) NetThread: 收发包,连接管理,多线程(可配置),采用epoll ET触发实现,支持tcp/udp: BindAdapter: 绑定端口类,用于管理Servant对应的绑定端口的信息操作: ServantHandle:业务线程类,根据对象名分派Servant的对象和接口调用: AdminServant: 管理端口的对象: ServantImp: 继承Servant的业务处理基类(Serv

Mustache.js前端渲染模板

引自:http://blog.csdn.net/xuemoyao/article/details/17896203 Mustache 使用心得总结 前言: 之前的一个项目里面就有用到这个前台的渲染模版,当时挺忙的也没时间抽空总结一下,刚好上周项目里又用到这个轻量型的渲染模版,真心感觉很好用,因此就总结一下使用心得,算是一个入门级别的指引吧. 1.  Mustache 概述 Mustache是基于JavaScript实现的模版引擎,类似于JQuery Template,但是这个模版更加的轻量级,语

fastclick.js源码解读分析

阅读优秀的js插件和库源码,可以加深我们对web开发的理解和提高js能力,本人能力有限,只能粗略读懂一些小型插件,这里带来对fastclick源码的解读,望各位大神不吝指教~! fastclick诞生背景与使用 在解读源码前,还是简单介绍下fastclick: 诞生背景 我们都知道,在移动端页面开发上,会出现一个问题,click事件会有300ms的延迟,这让用户感觉很不爽,感觉像是网页卡顿了一样,实际上,这是浏览器为了更好的判断用户的双击行为,移动浏览器都支持双击缩放或双击滚动的操作,比如一个链

分享:json2.js源码解读笔记

1. 如何理解"json" 首先应该意识到,json是一种数据转换格式,既然是个"格式",就是个抽象的东西.它不是js对象,也不是字符串,它只是一种格式,一种规定而已. 这个格式规定了如何将js对象转换成字符串.以及转换成怎样的字符串--序列化 -- JSON.stringify 接口: 以及如何将一个有效字符串转换成js对象--反序列化-- JSON.parse 接口: 2. 关于作者 json作者是 道格拉斯.克劳福德 ,是一位js大牛,写过一本<java

js便签笔记(10) - 分享:json2.js源码解读笔记

1. 如何理解“json” 首先应该意识到,json是一种数据转换格式,既然是个“格式”,就是个抽象的东西.它不是js对象,也不是字符串,它只是一种格式,一种规定而已. 这个格式规定了如何将js对象转换成字符串.以及转换成怎样的字符串——序列化 —— JSON.stringify 接口: 以及如何将一个有效字符串转换成js对象——反序列化—— JSON.parse 接口: 2. 关于作者 json作者是 道格拉斯.克劳福德 ,是一位js大牛,写过一本<javascript语言精粹>,相信不少朋