【vue源码解析之底层模板引擎】mustache模板引擎

什么是模板引擎?

模板引擎是将数据变为视图的最优雅的解决办法。目前将数据处理为视图的方法,从时间线排列有:

  • 纯DOM法 document.createElement()

  • 数组 join,借助 HTMLElement.prototype.innerHTML将字符串解析为HTML

  • es6 模板字符串

    ${data}

    替代 join 函数

  • 模板引擎,vue中的

  • 就是一种模板引擎。此处解析的mustache是最早的模板引擎,因它的嵌入标记{{}}像胡子而命名

例如:将下图数据转为视图

// 数据
const data = {
    title: '信息',
    arr: [
        { name: 'Jay', age: 18},
        { name: 'Bin', age: 20}
    ],
}

<p>{{title}}p>
<ul>
    {{#arr}}
    <li>
        <p>{{name}}p>
        <p>{{age}}p>
    li>
    {{/arr}}
ul>

<p>信息p>
<ul>
    <li>
        <p>Jayp>
        <p>18p>
    li>
    <li>
        <p>Binp>
        <p>20p>
    li>
ul>

mustache 基本用法

引入mustache.4.1.0.js


<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Documenttitle>
head>

<body>
  <div id="box">div>

  <script src="./mustache4_1_0.js">script>
    
  
  <script type="text/template" id="myTemplate">
    <p>{{title}}</p>
    <ul>
      {{#arr}}
        <li>
            <p>{{name}}</p>
            <p>{{age}}</p>
        </li>
      {{/arr}}
    </ul>
  script>
    
  <script>
    // 数据
    const data = {
      title: '信息',
      arr: [
        { name: 'Jay', age: 18 },
        { name: 'Bin', age: 20 }
      ]
    }
    
    const templateStr = document.getElementById('myTemplate').innerHTML;
    const domStr = Mustache.render(templateStr, data)
    const box = document.getElementById('box')
    box.innerHTML = domStr

  script>
body>

html>

【vue源码解析之底层模板引擎】mustache模板引擎_第1张图片

mustache 原理

{{title}}

转为

信息

很简单

templateStr.replace(/\{\{(\w+)\}\}/, function (findStr, $1) {
    return data[$1];
})

但遇到复杂嵌套的数据怎么解决呢?mustache 引入了 token 的概念。在 mustache.js 中打断点输出 tokens,可以看到模板被解析为下图所示 tokens

【vue源码解析之底层模板引擎】mustache模板引擎_第2张图片

它用 text 标记普通文本,用 name 标记嵌入数据名称,并形成和模板一样的嵌套结构

【vue源码解析之底层模板引擎】mustache模板引擎_第3张图片

根据 tokens 和 data 形成最后的字符串,下面自己实现一下。

【vue源码解析之底层模板引擎】mustache模板引擎_第4张图片

自定义mustache

目标实现双重嵌套

let templateStr = `
    

{{title}}

    {{#arr}}
  • {{name}}

    {{age}}

    {{#num}} {{.}} {{/num}}
  • {{/arr}}
`
; const data = { title: '信息', arr: [ {name: 'Jay', age: 18, num: [1, 2, 3]}, {name: 'Bin', age: 20, num: [1, 2, 3]} ], }

1. parseTemplateStrToTokens 将模板转化为 tokens

第一步不是直接实现嵌套结构的 tokens,先将模板转为扁平的一维 tokens.

原理:先检索{{,将之前的数据用 text 标记,再在剩余数据检索}},中间数据用 name 标记,循环往复,直至结束。此处未考虑{{}}不对称及其他情况,只是简单实现。

export default function ParseTemplateStrToTokens(templateStr) {
    let stopScanUtilTag = '{{';
    let stopScanTag = '}}';

    let tokens = [];
    scanUtil(tokens, templateStr);
    return tokens;
    
    /* 收集 name 即{{}}中间的东西 */
    function scan(tokens, tail) {
        let res;
        let pos = tail.search(stopScanTag);
        if (pos > 0) {
            res = tail.substring(0, pos);
            tail = tail.substring(pos + stopScanTag.length);
        } else {
            res = tail;
        }
        res = res.trim();
        if (res.length > 0) {
            // 用 # / 标记嵌套开始和结束
            if (res[0] === '#' || res[0] === '/') {
                tokens.push([res[0], res.substring(1)])
            } else tokens.push(['name', res]);
        }
        if (pos > 0 && tail.length > 0) {
            scanUtil(tokens, tail);
        }
    }

    /* 收集 text */
    function scanUtil(tokens, tail) {
        let res;
        let pos = tail.search(stopScanUtilTag);
        if (pos > 0) {
            res = tail.substring(0, pos);
            tail = tail.substring(pos + stopScanUtilTag.length);
        } else {
            res = tail;
        }
        res = res.trim();
        if (res.length > 0) tokens.push(['text', res]);
        if (pos > 0 && tail.length > 0) {
            scan(tokens, tail);
        }
    }
}

输出 tokens

【vue源码解析之底层模板引擎】mustache模板引擎_第5张图片

下面将一维的 tokens 转为嵌套结构。

思想:这里用 nestTokens 存储嵌套的 tokens,并用到数据结构栈 stack。

将 tokens 分为三类: #、/、default,进行遍历

  • # : 压栈
  • /: 退栈,直至遇到 # 且属性相同的 token,用 section 存储退栈的数据并存在当前 # token中,若此时栈为空,则 # token 压入 nestTokens,否则依旧压入栈 stack
  • default: 若栈为空,压入 nestTokens,否则压入 stack
function nestTokens() {
    let flag = 0;   // 记录栈是否为空
    let stack = [];
    let nestTokens = [];    // 最终嵌套tokens
    tokens.forEach(token => {
        switch (token[0]) {
            case '#':
                flag++;
                stack.push(token)
                break;
            case '/':
                flag--;
                let section = [];
                let temp;
                while (true) {
                    temp = stack.pop();
                    if (temp[0] === '#' && temp[1] === token[1]) break;
                    else section.unshift(temp);
                }
                temp[2] = section;
                if (flag === 0) nestTokens.push(temp);
                else stack.push(temp)
                break
            default:
                if (flag === 0) nestTokens.push(token);
                else stack.push(token)
        }
    })
    return nestTokens;
}

输出 nestTokens

【vue源码解析之底层模板引擎】mustache模板引擎_第6张图片

2. renderTemplate 生成dom字符串

思想: 对 tokens 遍历,遇到嵌套结构,再针对数据遍历。递归调用。

const data = {
    title: '信息',
    arr: [
        {name: 'Jay', age: 18, num: [1, 2, 3]},
        {name: 'Bin', age: 20, num: [1, 2, 3]}
    ],
}

export default function renderTemplate(tokens, data) {
    let domStr = '';
    tokens.forEach(token => {
        if (token[0] === 'text') domStr += token[1];
        else if (token[0] === 'name') {
            if (token[1] !== '.') domStr += data[token[1]];
            else domStr += data;
        } else if (token[0] === '#') {
            data[token[1]].forEach(data => {
                domStr += renderTemplate(token[2], data);
            })
        }
    })
    return domStr;
}

输出 domStr(未改变格式)

<p>信息p>
    <ul><li>
            <p>Jayp>
            <p>18p><span>1span><span>2span><span>3span>li><li>
            <p>Binp>
            <p>20p><span>1span><span>2span><span>3span>li>ul>

3. index.js 整合挂载到全局

import ParseTemplateStrToTokens from "./parseTemplateStrToTokens"
import RenderTemplate from "./renderTemplate";

window.templateEngine = {
    render(templateStr, data) {
        let tokens = ParseTemplateStrToTokens(templateStr);
        let domStr = RenderTemplate(tokens, data)
        return domStr;
    }
}

使用

<body>
<div id="app">div>

<script>
    let templateStr = `
    

{{title}}

    {{#arr}}
  • {{name}}

    {{age}}

    {{#num}} {{.}} {{/num}}
  • {{/arr}}
`
; const data = { title: '信息', arr: [ {name: 'Jay', age: 18, num: [1, 2, 3]}, {name: 'Bin', age: 20, num: [1, 2, 3]} ], } let domStr = templateEngine.render(templateStr, data); document.getElementById('app').innerHTML = domStr;
script> body>

【vue源码解析之底层模板引擎】mustache模板引擎_第7张图片

至此,自定义实现了 mustache 简易功能,了解了vue底层模板引擎实现的思路。

你可能感兴趣的:(vue.js,javascript,前端)