mustache模板引擎

模板引擎是将数据要变为视图最优雅的解决方案

数据

 视图

vue的解决方法

这实际上就是一种模板引擎

历史上曾经出现将数据变为视图的方法

 原生dom法

        var arr = [
            { "name": "小明", "age": 12, "sex": "男" },
            { "name": "小红", "age": 11, "sex": "女" },
            { "name": "小强", "age": 13, "sex": "男" }
        ];

        var list = document.getElementById('list');

        for (var i = 0; i < arr.length; i++) {
            // 每遍历一项,都要用DOM方法去创建li标签
            let oLi = document.createElement('li');
            // 创建hd这个div
            let hdDiv = document.createElement('div');
            hdDiv.className = 'hd';
            hdDiv.innerText = arr[i].name + '的基本信息';
            // 创建bd这个div
            let bdDiv = document.createElement('div');
            bdDiv.className = 'bd';
            // 创建三个p
            let p1 = document.createElement('p');
            p1.innerText = '姓名:' + arr[i].name;
            bdDiv.appendChild(p1);
            let p2 = document.createElement('p');
            p2.innerText = '年龄:' + arr[i].age;
            bdDiv.appendChild(p2);
            let p3 = document.createElement('p');
            p3.innerText = '性别:' + arr[i].sex;
            bdDiv.appendChild(p3);

            // 创建的节点是孤儿节点,所以必须要上树才能被用户看见
            oLi.appendChild(hdDiv);
            // 创建的节点是孤儿节点,所以必须要上树才能被用户看见
            oLi.appendChild(bdDiv);
            // 创建的节点是孤儿节点,所以必须要上树才能被用户看见
            list.appendChild(oLi);
        }

数据join法

    <ul id="list"></ul>

    <script>
        var arr = [
            { "name": "小明", "age": 12, "sex": "男" },
            { "name": "小红", "age": 11, "sex": "女" },
            { "name": "小强", "age": 13, "sex": "男" }
        ];

        var list = document.getElementById('list');

        // 遍历arr数组,每遍历一项,就以字符串的视角将HTML字符串添加到list中
        //传统字符串不能换行
        for (let i = 0; i < arr.length; i++) {
            list.innerHTML += [
                '<li>',
                '    <div class="hd">' + arr[i].name + '的信息</div>',
                '    <div class="bd">',
                '        <p>姓名:' + arr[i].name + '</p>',
                '        <p>年龄:' + arr[i].age + '</p>',
                '        <p>性别:' + arr[i].sex + '</p>',
                '    </div>',
                '</li>'
            ].join('')
        }

    </script>

es6反引号法

        var arr = [
            { "name": "小明", "age": 12, "sex": "男" },
            { "name": "小红", "age": 11, "sex": "女" },
            { "name": "小强", "age": 13, "sex": "男" }
        ];

        var list = document.getElementById('list');

        // 遍历arr数组,每遍历一项,就以字符串的视角将HTML字符串添加到list中
        for (let i = 0; i < arr.length; i++) {
            list.innerHTML += `
                <li>
                    <div class="hd">${arr[i].name}的基本信息</div>    
                    <div class="bd">
                        <p>姓名:${arr[i].name}</p>    
                        <p>性别:${arr[i].sex}</p>    
                        <p>年龄:${arr[i].age}</p>    
                    </div>    
                </li>
            `;
        }

mustache库简介

 mustache官方git:mustache
• mustache是“胡子”的意思,因为它的嵌入标记{{ }}非常像胡子
• 没错,{{ }}的语法也被Vue沿用,这就是我们学习mustache的原因
• mustache是最早的模板引擎库,比Vue诞生的早多了,它的底层实现机理在当
时是非常有创造性的、轰动性的,为后续模板引擎的发展提供了崭新的思路

mustache库基本使用

• 必须要引入mustache库,可以在bootcdn.com上找到它
• mustache的模板语法非常简单,比如前述案例的模板语法如下:

不循环

 

    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script>
        var templateStr = `
            <h1>我买了一个{{thing}},好{{mood}}啊</h1>
        `;

        var data = {
            thing: '华为手机',
            mood: '开心'
        };

        var domStr = Mustache.render(templateStr, data);
        
        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>

 循环最简单的数组

    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script>
        var templateStr = `
            <ul>
                {{#arr}}
                    <li>{{.}}</li>    
                {{/arr}}
            </ul>
        `;

        var data = {
            arr: ['A', 'B', 'C']
        };

        var domStr = Mustache.render(templateStr, data);
        
        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>

循环对象数组 类似 v-for

    <div id="container"></div>

    <!-- 模板  解决es6之前没有反引号字符串不能换行的问题-->
    <script type="text/template" id="mytemplate">
        <ul>
            {{#arr}}
                <li>
                    <div class="hd">{{name}}的基本信息</div>    
                    <div class="bd">
                        <p>姓名:{{name}}</p>    
                        <p>性别:{{sex}}</p>    
                        <p>年龄:{{age}}</p>    
                    </div>
                </li>
            {{/arr}}
        </ul>
    </script>

    <script src="jslib/mustache.js"></script>
    <script>
        var templateStr = document.getElementById('mytemplate').innerHTML;

        var data = {
            arr: [
                { "name": "小明", "age": 12, "sex": "男" },
                { "name": "小红", "age": 11, "sex": "女" },
                { "name": "小强", "age": 13, "sex": "男" }
            ]
        };

        var domStr = Mustache.render(templateStr, data);
        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>

 循环嵌套数组

 

    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script>
        var templateStr = `
            <ul>
                {{#arr}}
                    <li>
                        {{name}}的爱好是:
                        <ol>
                            {{#hobbies}} 
                                <li>{{.}}</li>
                            {{/hobbies}} 
                        </ol>
                    </li>    
                {{/arr}}
            </ul>
        `;

        var data = {
            arr: [
                {'name': '小明', 'age': 12, 'hobbies': ['游泳', '羽毛球']},
                {'name': '小红', 'age': 11, 'hobbies': ['编程', '写作文', '看报纸']},
                {'name': '小强', 'age': 13, 'hobbies': ['打台球']},
            ]
        };

        var domStr = Mustache.render(templateStr, data);
        
        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>

布尔值 类似 v-if

        // v-if
        var templateStr = `
            {{#m}}
                <h1>你好</h1>
            {{/m}}
        `;

        var data = {
            m: false
        };

        var domStr = Mustache.render(templateStr, data);
        
        var container = document.getElementById('container');
        container.innerHTML = domStr;

正则表达式

mustache库不能用简单的正则表达式思路实现

在较为简单的示例情况下,可以用正则表达式实现

 

但是当情况复杂时,正则表达式的思路肯定就不行了。比如这样的模板字符串,
是不能用正则表达式的思路实现的

正则捕获

/\}\}{(\w+)\}\}/

表示捕获{{}}中间的多个文字或数字
    <script>
        var templateStr = '<h1>我买了一个{{thing}},花了{{money}}元,好{{mood}}</h1>';

        var data = {
            thing: '白菜',
            money: 5,
            mood: '激动'
        };

        // 最简单的模板引擎的实现机理,利用的是正则表达式中的replace()方法。
        // replace()的第二个参数可以是一个函数,这个函数提供捕获的东西的参数,就是$1
        // 结合data对象,即可进行智能的替换
        //  function中的参数分别是:1:findStr匹配到的部分{{thing}} 2:捕获到的thing 3:匹配项在字                            
        //  符串中的位置 4:原串
        function render(templateStr, data) {
            return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
                return data[$1];
            });
        }

        var result = render(templateStr, data);
        console.log(result);
    </script>

 replace

 mustache 的实现原理

 什么是tokens

• tokens是一个JS的嵌套数组,说白了,就是模板字符串的JS表示
• 它是“抽象语法树”、“虚拟节点”等等的开山鼻祖

最简单的形式

模板字符串

<h1>我买了一个{{thing}},好{{mood}}啊</h1>

tokens

[
  ["text", "<h1>我买了一个"],
  ["name", "thing"],
  ["text", "好"],
  ["name", "mood"],
  ["text", "啊</h1>"]
]

循环数组情况下的 tokens

 

双重循环情况下的tokens

 mustache机理

 

将模板字符串变成tokens

实现 Scanner 扫描器类

src/Scanner.js

 substring

/* 
    扫描器类
*/
export default class Scanner {
    constructor(templateStr) {
        // 将模板字符串写到实例身上
        this.templateStr = templateStr;
        // 指针
        this.pos = 0;
        // 尾巴,一开始就是模板字符串原文
        this.tail = templateStr;
    }

    // 功能弱,就是走过指定内容,没有返回值
    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            // tag有多长,比如{{长度是2,就让指针后移多少位
            this.pos += tag.length;
            // 尾巴也要变,改变尾巴为从当前指针这个字符开始,到最后的全部字符
            this.tail = this.templateStr.substring(this.pos);
        }
    }

    // 让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
    scanUtil(stopTag) {
        // 记录一下执行本方法的时候pos的值
        const pos_backup = this.pos;
        // 当尾巴的开头不是stopTag的时候,就说明还没有扫描到stopTag
        // 写&&很有必要,因为防止找不到,那么寻找到最后也要停止下来
        while (!this.eos() && this.tail.indexOf(stopTag) != 0) {
            this.pos++;
            // 改变尾巴为从当前指针这个字符开始,到最后的全部字符
            this.tail = this.templateStr.substring(this.pos);
        }

        return this.templateStr.substring(pos_backup, this.pos);
    }

    // 指针是否已经到头,返回布尔值。end of string
    eos() {
        return this.pos >= this.templateStr.length;
    }
};

生产tokens数组

src/parseTemplateToTokens

import Scanner from './Scanner'
import nextTokens from './nextTokens';

/* 
    将模板字符串变为tokens数组
*/
export default function parseTemplateToTokens (templateStr) {
  var tokens = [];
  var words;
  //scanner创建扫描器
  var scanner = new Scanner(templateStr)
  //让扫描器工作
  while (!scanner.eos()) {
    //收集开始标记之前的文字
    words = scanner.scanUntil('{{')
    if (words != '') {
      //收集起来
      tokens.push(['text', words])
    }
    // words&&tokens.push(['text', words])

    //过双大括号
    scanner.scan('{{')

    //收集开始标记之前的文字
    words = scanner.scanUntil('}}')
    if (words != '') {
      //这个words就是{{}}中间的东西 判断一下首字符
      if (words[0] === '#') {
        //存起来,从下标为1开始存,因为下标为0的项是#
        tokens.push(['#', words.substring(1)])
      } else if (words[0] === '/') {
        //存起来,从下标为1开始存,因为下标为0的项是/
        tokens.push(['/', words.substring(1)])
      } else {
        //收集起来
        tokens.push(['name', words])
      }

    }
    // words&&tokens.push(['name', words])

    //过反大括号
    scanner.scan('}}')

  }
  //返回折叠收集的tokens
  return nextTokens(tokens)

}

将零散的tokens嵌套起来

来解决
遇见#就进栈,遇见/就出栈

 src/nestTokens.js

/* 
    函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens (tokens) {
    // 结果数组
    var nestedTokens = [];
    // 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
    var sections = [];
    // 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
    // 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
    var collector = nestedTokens;

    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];

        switch (token[0]) {
            case '#':
                // 收集器中放入这个token
                collector.push(token);
                // 入栈
                sections.push(token);
                // 收集器要换人。给token添加下标为2的项,并且让收集器指向它
                /*数组对象是地址引用传递的,所以在修改这个对象的属性后,所有存放这个对象引用的地方都会受到影响。*/
                collector = token[2] = [];


                break;
            case '/':
                // 出栈。pop()会返回刚刚弹出的项
                sections.pop();
                // 改变收集器为栈结构队尾(队尾是栈顶)那项的下标为2的数组
                collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
                break;
            default:
                // 甭管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为2的数组,甭管是谁,推入collctor即可。
                collector.push(token);
        }
    }

    return nestedTokens;
};

将tokens解析为DOM字符串

src/index.js

import parseTemplateToTokens from "./parseTemplateToTokens";
import renderTemplate from "./renderTemplate";

// 全局提供YK_TemplateEngine对象
window.f_TemplateEngine = {
  // 渲染方法
  render(tempalteStr, data) {
    // 调用parseTemplateToTokens,可以让模板字符串变为tokens数组
    var tokens = parseTemplateToTokens(tempalteStr);
    var domStr = renderTemplate(tokens, data);
    return domStr
  },
};

src/lookup.js 可以在对象中,寻找连续点符号的属性

/* 
    功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
    比如,dataObj是
    {
        a: {
            b: {
                c: 100
            }
        }
    }
    那么lookup(dataObj, 'a.b.c')结果就是100
    不忽悠大家,这个函数是某个大厂的面试题
*/
export default function lookup(dataObj, keyName) {
    // 看看keyName中有没有点符号,但是不能是.本身
    if (keyName.indexOf('.') != -1 && keyName != '.') {
        // 如果有点符号,那么拆开
        var keys = keyName.split('.');
        // 设置一个临时变量,这个临时变量用于周转,一层一层找下去。
        var temp = dataObj;
        // 每找一层,就把它设置为新的临时变量
        for (let i = 0; i < keys.length; i++) {
            temp = temp[keys[i]];
        }
        return temp;
    }
    // 如果这里面没有点符号
    return dataObj[keyName];
};

src/renderTemplate.js

import lookup from './lookup.js';
import parseArray from './parseArray.js';
/* 
    函数的功能是让tokens数组变为dom字符串
*/
export default function renderTemplate(tokens, data) {
    // 结果字符串
    var resultStr = '';
    // 遍历tokens
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        // 看类型
        if (token[0] == 'text') {
            // 拼起来
            resultStr += token[1];
        } else if (token[0] == 'name') {
            // 如果是name类型,那么就直接使用它的值,当然要用lookup
            // 因为防止这里是“a.b.c”有逗号的形式
            resultStr += lookup(data, token[1]);
        } else if (token[0] == '#') {
            resultStr += parseArray(token, data);
        }
    }

    return resultStr;
}

src/parseArray.js 递归调用 renderTemplate

import lookup from './lookup.js';
import renderTemplate from './renderTemplate.js';

/* 
    处理数组,结合renderTemplate实现递归
    注意,这个函数收的参数是token!而不是tokens!
    token是什么,就是一个简单的['#', 'students', [

    ]]
    
    这个函数要递归调用renderTemplate函数,调用多少次???
    千万别蒙圈!调用的次数由data决定
    比如data的形式是这样的:
    {
        students: [
            { 'name': '小明', 'hobbies': ['游泳', '健身'] },
            { 'name': '小红', 'hobbies': ['足球', '蓝球', '羽毛球'] },
            { 'name': '小强', 'hobbies': ['吃饭', '睡觉'] },
        ]
    };
    那么parseArray()函数就要递归调用renderTemplate函数3次,因为数组长度是3
*/

export default function parseArray(token, data) {
    // 得到整体数据data中这个数组要使用的部分
    var v = lookup(data, token[1]);
    // 结果字符串
    var resultStr = '';
    // 遍历v数组,v一定是数组
    // 注意,下面这个循环可能是整个包中最难思考的一个循环
    // 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
    for(let i = 0 ; i < v.length; i++) {
        // 这里要补一个“.”属性
        // 拼接
        resultStr += renderTemplate(token[2], {
            ...v[i],
            '.': v[i]
        });
    }
    return resultStr;
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值