mustache-1.0源码注解

源码地址:https://cdn.bootcdn.net/ajax/libs/mustache.js/1.0.0/mustache.js

大概的方法

{{name}} 会把数据中 name 转换为具体值
{{#list}} {{/list}} 会把list中的数据选项展示
{{^list}}{{/list}} 数据中 list不存在或者未空数组才展示
{{!注释}} 注释
{{{}}} 里面的内容不会转义
{{>name}} 会从附属数据中获取数据

{{=<% %>=}}
修改 <% %> 为新切割符号

html测试



  
    
    
    
    Document
  
  
    
image.png

原理

1.0是 大概原理是 把template模板 通过 对应的分隔符号 比如 {{ }} 转为为tokens;

["text", "

↵", 181, 188]
["^", "list", 194, 203, Array(5), 260]
["text", " 姓名:", 204, 221]
["name", "name", 221, 229]
["text", "年龄:", 229, 235]
["name", "age", 235, 242]
["text", "
↵", 242, 254]
["/", "list", 260, 269]
["#", "arr", 276, 284, Array(3), 321]

[type, value, start, scanner.pos]

  • 其中type 是这个 类型 text 为文本, name需要取数据中的键 ,# 是循环
  • value 数据中的键或者字符串
  • start 这个是当前数组所在字符串的位置
  • scanner.pos 扫描器的位置或者未结束位置

然后把tokens处理为 dom识别的字符串;

源码里面有三个类:

  • Scanner;
  • Context;
  • Writer;

Scanner 扫描器

我的理解 Scanner 是 当 匹配符号比如 {{ 和 }} 匹配一段文本是分别 获取 匹配符号之外的文本。(其实跟以前版本正则类似)
比例: 我是{{name}},我爱我的{{contry}}。也爱你
通过Scanner类可以获取到 五段内容:

  1. 我是
  2. name
  3. ,我爱我的
  4. contry
  5. 。也爱你

源码


    /**
     * 模板解析器用于在模板字符串中查找令牌的简单字符串扫描仪
     * 获取字符串,并设置尾部,尾巴会向后减少。pos是当前位置
     */
    function Scanner(string) {
        this.string = string;
        this.tail = string;
        this.pos = 0;
    }
    /**
     * 判断是否到尾部
     */
    Scanner.prototype.eos = function () {
        return this.tail === "";
    };
    /**
     * 为了跳过匹配符号
     * 返回匹配的匹配的符号,如果没有返回空字符串
     * 比如 re为 {{   找到的话就返回 {{  并且尾巴向后移动两位,pos位置也加2
     */
    Scanner.prototype.scan = function (re) {
        var match = this.tail.match(re);
        // 没有匹配直接返回空字符串
        if (!match || match.index !== 0)
            return '';
        var string = match[0];
        // 尾巴 向后移动 匹配符号的长度
        this.tail = this.tail.substring(string.length);
        // 位置加 上匹配符号的长度
        this.pos += string.length;
        return string;
    };
    /**
     * 为了找到 匹配符号前的文本
     * 如果有匹配的符号,返回匹配符号之前的文本,否则就是没有匹配到,返回剩下的尾巴
     * 比如 这里是{{name}}的家  re为 {{  会返回 这里是
     */
    Scanner.prototype.scanUntil = function (re) {
        // 得到匹配符号在尾巴中的位置(尾巴会变化,所有位置也会在变化)
        var index = this.tail.search(re),
            match;
        switch (index) {
            // -1说明没有匹配到特殊符号,说明已经扫描完了。直接返回剩下的文本。比把尾巴置空
            case -1:
                match = this.tail;
                this.tail = "";
                break;
            // 特殊符号刚好在开始地方,那匹配符号前 就没有文本。匹配到的就是空字符串
            case 0:
                match = "";
                break;
            // 其他情况 直接获取匹配符号前的文本,并把尾巴向后移动到匹配的地方(这个时候后面会用scan函数跳过匹配符号)
            default:
                match = this.tail.substring(0, index);
                this.tail = this.tail.substring(index);
        }
        // 位置 需要加上匹配的长度 ,并返回匹配的文本 
        this.pos += match.length;
        return match;
    };

html



  
    
    
    
    Document
  
  
    

由于这个类向外暴露了。我们可以直接测试


image.png

这里我只看一下效果,后面代码里面会循环调用;

Context 上下文

作用是用来处理data数据,便于查找对应的数据

/**
     * 
     * view 类 数据 比如 {{name: 'zs', age: 14}}
     * 把数据作为参数 实例化Context 。便于 创建新的数据 并且根据树结构查找数据
     */
    function Context(view, parentContext) {
        // view 如果不存在,默认设置为空对象
        this.view = view == null ? {} : view;
        // 缓存  对象  .
        this.cache = {
            '.': this.view
        };
        // 设置父元素为 传入的第二个参数
        this.parent = parentContext;
    }
    /**
     * 使用给定视图创建一个新上下文,并将此上下文作为父级。
     */
    Context.prototype.push = function (view) {
        return new Context(view, this);
    };
    /**
     * 返回此上下文中给定名称的值,如果此上下文视图中缺少该值,则遍历上下文层次结构。
     */
    Context.prototype.lookup = function (name) {
        // 首先拿到缓存
        var cache = this.cache;
        // 定义查找的内容
        var value;
        // 如果要查找的 内容在缓存中  赋值为value
        // 如果是循环 的情况,获取 .  这个符号,会直接返回 上面 有缓存对象
        if (name in cache) {
            value = cache[name];
        } else {
            // context 初始定义为 当前对象,下面可能会改变
            var context = this,
                names, index;
            // 循环遍历 context ,知道context不存在为止
            while (context) {
                // 如果要查找的是 a.b.c 类似 的数据
                if (name.indexOf('.') > 0) {
                    // 首先把 数据赋值为value  比如 { a: { b: c: 'haha' } }
                    value = context.view;
                    // names 把 a.b.c 拆分为 [a.b.c]
                    names = name.split('.');
                    // index 初始为0
                    index = 0;
                    //  循环 names 数组 ,并分别从value中查找到值,并重新赋值为value
                    //  比如上面最后会得到 value 为 haha
                    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 = context.parent;
            }
            cache[name] = value;
        }

        // 如果查找到的内容是 函数,然后执行到,赋值给value
        if (isFunction(value))
            value = value.call(this.view);

        // 返回找到到的内容
        return value;
    };

Writer

提供render方法。 把模板转换为tokens。然后处理只有的tokens 通过数据转换为浏览器识别的字符串



    /**
     * 提供解析模板为tokens 然后把tokens 转为 dom字符串
     * 根据tokens 转换为 字符串,并且缓存它
     */
    function Writer() {
        this.cache = {};
    }
    /**
     * 清空缓存
     */
    Writer.prototype.clearCache = function () {
        this.cache = {};
    };
    /**
     * 解析和缓存给定的“模板”,并返回从解析生成的令牌数组。
     */
    Writer.prototype.parse = function (template, tags) {
        // 拿到缓存
        var cache = this.cache;
        // 如果缓存中有,直接返回对应的tokens
        var tokens = cache[template];
        // 若果缓存中没有。那么调用 parseTemplate返回获取tokens并缓存起来
        if (tokens == null)
            tokens = cache[template] = parseTemplate(template, tags);
           
        return tokens;
    };
    /**
     * 渲染函数 
     * template 为模板
     * view 为数据
     * partials 为补充模板 可以为对象也可以为函数
     */
    Writer.prototype.render = function (template, view, partials) {
        // 把模板 转化 tokens
        var tokens = this.parse(template);
        // 数据是不是 是不是 Context的示例,不是的话,用 Context实例化处理数据
        var context = (view instanceof Context) ? view : new Context(view);
        
        return this.renderTokens(tokens, context, partials, template);
    };
    /**
     * 递归函数  用于处理tokens  处理为dom字符串
     *     
     *  */
    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]) {
                // 如果是# 那这个token 就是循环的token 
                // 比如 ["#", "list", 194, 203, Array(5), 260]
                case '#':
                    // 数据中查找 list 对应的 数据
                    value = context.lookup(token[1]);
                    console.log('value', value);
                    // 如果数据红没有则跳过
                    if (!value)
                        continue;
                    // 如果是 数组
                    // 比如  {list: [{name:'zs'},{name: 'ls'}]}
                    // value 为[{name:'zs'},{name: 'ls'}]
                    if (isArray(value)) {
                        for (var j = 0, valueLength = value.length; j < valueLength; ++j) {

                            // 递归 把 Array(5) 作为token, 每一项 {name: 'xx'} 作为数据
                            buffer += this.renderTokens(token[4], context.push(value[j]), partials,
                                originalTemplate);
                        }
                        // 如果 找到的 value 为对象或字符串
                        // {list: {name:'zs', age:14}} 或者 {list: 'abcd'}
                        // value 为 {name:'zs', age:14} 或 'abcd'
                    } else if (typeof value === 'object' || typeof value === 'string') {
                        // 也是递归 把 Array(5)作为token, 直接把当前value作为数据
                        buffer += this.renderTokens(token[4], context.push(value), partials,
                            originalTemplate);
                        // 如果找到的函数
                        /**
                         *  arr: ()=> (template, fn)=>{
                         *     return fn(template)
                         *   }
                         * 
                         *  template  == originalTemplate.slice(token[3], token[5]) 为循环内的位解析字符串
                         * fn == subRender 用根 数据渲染当前模板
                         */
                    } else if (isFunction(value)) {
                        // 如果 原始模板不是字符串 则报错
                        if (typeof originalTemplate !== 'string')
                            throw new Error(
                                'Cannot use higher-order sections without the original template');
                        // 返回一个自定义函数, 原函数匹配的字符串为第一个参数 ,subRender为第二个参数(以根数据来渲染当前模板)
                        value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
                        
                        if (value != null)
                            buffer += value;
                    } else {
                        // 其他情况 直接调用 递归处理 并且不用 context.push反正创建子context
                        buffer += this.renderTokens(token[4], context, partials, originalTemplate);
                    }
                    break;
                case '^':
                    // 查找到 数据中的对应值 (lookup方法会找到最上层)
                    value = context.lookup(token[1]);
                    // 如果没有找到值 或者 是一个空数组  调用 renderTokens 方法查找对应数据
                    // 
                    if (!value || (isArray(value) && value.length === 0))
                        buffer += this.renderTokens(token[4], context, partials, originalTemplate);
                    break;
                case '>':
                    // > 如果没有 分部 partials 跳过
                    if (!partials)
                        continue;
                        // 判断partials 是不是函数,函数执行 否则世界查找partials的 值
                    value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
                    if (value != null)
                         // 有值的话继续递归
                        buffer += this.renderTokens(this.parse(value), context, partials, value);
                    break;
                case '&':
                    // 如果是& 说明是{{{}}} 中的数据, 去context 中查找到对应的数据 后直接拼接
                    value = context.lookup(token[1]);
                    if (value != null)
                        buffer += value;
                    break;
                case 'name':
                    // name  去context 中查找到对应的数据
                    value = context.lookup(token[1]);
                    // 如果不为空,转义之后拼接
                    if (value != null)
                        buffer += mustache.escape(value);
                    break;
                case 'text':
                    // text 是文本 ,直接拼接
                    buffer += token[1];
                    break;
            }
        }
        return buffer;
    };

下面是整个文件的注解:

/*!
 * mustache.js - Logic-less {{mustache}} templates with JavaScript
 * http://github.com/janl/mustache.js
 */
/*global define: false*/
// umd 写法: 兼容AMD和commonJS规范的同时,还兼容全局引用的方式
(function (global, factory) {
    if (typeof exports === "object" && exports) {
        factory(exports); // CommonJS
    } else if (typeof define === "function" && define.amd) {
        define(['exports'], factory); // AMD
    } else {
        factory(global.Mustache = {}); // 
                    
                    

你可能感兴趣的:(mustache-1.0源码注解)