Vue源码:mustache模板解析

文章目录

  • 什么是模板引擎
    • 数据变为视图的方法
  • 手写mustache
    • 运行流程

什么是模板引擎

模板引擎是将数据变为视图最优雅的解决方案,如下可以通过左侧数据最终在网页上渲染成右侧dom视图

Vue源码:mustache模板解析_第1张图片

数据变为视图的方法

历史上出现的数据变为视图的方法有四种:纯DOM法、数组join法、ES6反引号法、mustache模板引擎法,可以说越来越优雅。

如下数据采用各种方法渲染

    <div>
        <ul class="list">

        ul>
    div>

    <script>
        let students = [{
                name: '小明',
                age: 18
            },
            {
                name: '小红',
                age: 19
            },
            {
                name: '小华',
                age: 20
            },
        ];
    script>

1.纯DOM法
纯DOM法就是通过对DOM的原生操作(获取节点、创建节点、添加节点)来将数据拼接添加

<script>
    for (let i = 0; i < students.length; i++) {
        let list = document.querySelector('.list');
        let li = document.createElement('li');
        let p1 = document.createElement('p');
        p1.innerText = '我是' + students[i].name;
        let p2 = document.createElement('p');
        p2.innerText = '我今年' + students[i].age + '岁';
        list.appendChild(li);
        li.appendChild(p1);
        li.appendChild(p2);
    }
</script>

如上可以看出像渲染一个简单的students数据却非常繁琐,通过获取节点,然后创建节点,添加节点。

2.数组join法
曾经非常流行,通过数组join的方法,将模板字符串通过数组分段join连接起来,从而可以达到一种看似换行的效果

   <script>
        let list = document.querySelector('.list');
        for (let i = 0; i < students.length; i++) {
            list.innerHTML += [
                '            
  • ', '

    我是' + students[i].name + '

    '
    , '

    我今年' + students[i].age + '岁了

    '
    , '
  • '
    ].join('') } </script>

    3.ES6反引号法
    在ES6中新增了``反引号,可以直接换行输入字符串。

    let list = document.querySelector('.list');
    for (let i = 0; i < students.length; i++) {
        list.innerHTML += `
            
  • 我是${students[i].name}

    我今年${students[i].age}岁了

  • `
    }

    4.mustache渲染引擎
    通过mustache渲染引擎可以将{{}}内属性进行解析渲染
    这里通过bootcdn复制mustache.js,

    import Mustache from "../libjs/mustache.js"
    let templateStr = `
            {{#students}}
            
  • 我是{{name}}

    我今年{{age}}岁了

  • {{/students}}
    `
    ; let list = document.querySelector('.list'); let domStr = Mustache.render(templateStr, data); list.innerHTML = domStr;

    如上通过mustache内的render进行渲染,传入模板字符串与数据,根据识别#号可以进行循环,不需要for进行循环


    手写mustache

    这里通过配置webpack、webpack-cli、webpack-dev-server进行模块化开发

    结构如下:
    Vue源码:mustache模板解析_第2张图片

    运行流程

    1.index.js中声明全局引擎TemplateEngine对象,其中添加render函数
    2.在index.html中调用TemplateStr.render,传入模板字符串templateStr和渲染数据data
    3.render内部调用parseTemplateToTokens
    4.parseTemplateToTokens内部调用scanner类方法(
        scanUntil:读取非{{}}内容
        scan:跳过{{}}字符串
        utail:更新tail
        exo:是否到达终点
    )得到读取后的所有字符串并分类型(text,name,#,/)加入tokens数组
    5.调用nestTokens方法传入tokens将tokens转为嵌套数组
    6.调用renderTemplate传入tokens,data渲染数据,lookup(
        传入数据,字符串属性,可判断.类型的字段
        返回对应的值
    )返回domStr
    7.将最终得到domStr添加到innerHtml
    

    主文件index.js
    声明全局引擎TemplateEngine对象,内部包含render方法,传入参数templateStr模板字符串与数据data。

    import parseTemplateToTokens from "./parseTemplateToTokens"
    import renderTemplate from "./renderTemplate"
    //全局提供TemplateEngine
    window.TemplateEngine = {
        render(tempateStr, data) {
            //调用parseTemplateToTokens方法,将模板字符串转为Tokens
            let tokens = parseTemplateToTokens(tempateStr);
            console.log(tokens);
            //调用renderTemplate方法,将tokens转为domStr
            let domStr = renderTemplate(tokens, data);
            return domStr;
        }
    }
    

    parseTemplateToTokens.js

    import Scanenr from "./Scanner";
    import nestTokens from "./nestTokens";
    export default function parseTemplateToTokens(templateStr) {
        //实例化一个扫描器,构造提供一个模板字符串参数
        let scanner = new Scanenr(templateStr);
        let tokens = [];
        let words;
        while (!scanner.exo()) {
            words = scanner.scanUntil('{{')
            //去掉空格,但是不能去掉标签内的空格
            let isInLable = false; //是否在标签内
            let _words = '';
            for (let i = 0; i < words.length; i++) {
                let w = words[i];
                if (w === '<') isInLable = true;
                if (w === '>') isInLable = false;
                if (!/\s/g.test(w)) {
                    _words += w
                } else {
                    if (isInLable) {
                        _words += w
                    }
                }
            }
            tokens.push(['text', _words])
            scanner.scan('{{')
            words = scanner.scanUntil('}}')
            if (words.length > 0) {
                let word0 = words[0];
                if (word0 === '#') {
                    tokens.push(['#', words.substring(1)])
                } else if (word0 === '/') {
                    tokens.push(['/', words.substring(1)])
                } else {
                    tokens.push(['name', words])
                }
            }
            scanner.scan('}}')
        }
        return nestTokens(tokens);
    }
    

    内部先调用scanner类读取模板字符串
    scanner.js

    export default class Scanenr {
        constructor(templateStr) {
            this.templateStr = templateStr;
            this.pos = 0;
            this.tail = this.utail()
        }
        //过渡{{}}
        scan(etag) {
            this.pos += etag.length;
            this.tail = this.utail()
        }
        //过渡返回非{{}}字符串
        scanUntil(etag) {
            const pre_pos = this.pos;
            while (!this.exo() && this.tail.indexOf(etag) != 0) {
                this.pos++;
                this.tail = this.utail()
            }
            return this.templateStr.substring(pre_pos, this.pos)
        }
        //更新tail
        utail() {
            return this.templateStr.substring(this.pos)
        }
        //判断是否到达终点
        exo() {
            return this.pos >= this.templateStr.length
        }
    }
    

    将Tokens变为可循环嵌套的Tokens
    nestTokens.js

    //将tokens变为嵌套tokens
    export default function nestTokens(tokens) {
        //返回结果的数组
        let nestedTokens = [];
        //栈结构 用来存放迭代数,栈顶的token表示当前操作token
        let sections = [];
        //收集者 用来指向当前添加数据的数组
        let collector = nestedTokens;
        for (let i = 0; i < tokens.length; i++) {
            let token = tokens[i];
            switch (token[0]) {
                case '#':
                    collector.push(token);
                    sections.push(token);
                    collector = token[2] = [];
                    break;
                case '/':
                    sections.pop();
                    //如果栈中还有则将collector指向上一级循环的添加数组
                    collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
                    break;
                default:
                    collector.push(token);
                    break;
            }
        }
        return nestedTokens
    }
    

    进行数据渲染
    renderTemplate.js

    import lookup from "./lookup";
    
    export default function renderTemplate(tokens, data) {
        let resultStr = '';
        for (let i = 0; i < tokens.length; i++) {
            let token = tokens[i];
            switch (token[0]) {
                case 'text':
                    resultStr += token[1]
                    break;
                case 'name':
                    resultStr += lookup(data, token[1])
                    break;
                case '#':
                    let key = lookup(data, token[1]);
                    for (let j = 0; j < key.length; j++) {
                        resultStr += renderTemplate(token[2], key[j])
                    }
            }
        }
        return resultStr
    }
    

    根据对象,字符串查找数据
    lookup.js

    /*
        功能:将含有a.b.c类型的数据进行查找
        如{
            a:{
                b:{
                    c:3
                }
            }
        }
        则a.b.c=3
    */
    export default function lookup(data, valueStr) {
        if (valueStr === '.') return data
        if (valueStr.indexOf('.') != -1) {
            let values = valueStr.split('.');
            let start = 0;
            if (values[0] === 'item') start = 1;
            let temp = data;
            for (let i = start; i < values.length; i++) {
                temp = temp[values[i]]
            }
            return temp
        }
        return data[valueStr];
    }
    

    最后renderTempate返回值即可得到最终的domStr,再将domStr通过innerHtml解析即可

    你可能感兴趣的:(#,vue源码,javascript,vue.js)