Vue源码解析之Mustache

本篇笔记来自于尚硅谷——Vue源码解析系列课程

Mustache

Vue中的Mustache就是Vue中的模板引擎,另外v-for指令也是一种模板引擎的应用。

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

数据:

[
   {"name":"小明", "age":12, "sex":"男"},
   {"name":"小红", "age":11, "sex":"女"},
   {"name":"小强", "age":13, "sex":"男"}
]
复制代码

视图:

<ul>
   <li>
   	<div class="hd">小明的基本信息</div>
      <div class="bd">
      	<p>姓名:小明</p>
         <p>年龄:12</p>
         <p>性别:男</p>
      </div>
   </li>
   <li>...</li>
</ul>
复制代码

历史上的方案

  • 纯DOM法:非常笨拙,没有实战价值

  • 数组join法:曾几何时非常流行,是曾经的前端必备知识

  • ES6的反引号法:ES6中新增的'${a}'语法糖(反引号显示失败,这里用单引号替代),很好用

  • 模板引擎:数据变为视图的最优雅的解决方案

纯DOM法

<body>
 <ul id="list"></ul>
 <script>
    let data = [
       {"name":"小明", "age":12, "sex":"男"},
       {"name":"小红", "age":11, "sex":"女"},
       {"name":"小强", "age":13, "sex":"男"}
    ]
    let list  = document.querySelector('#list')
    //每次循环生成一次DOM,这里只是简单生成,若实现上面的视图,还要创建更多的DOM,很麻烦
    for(let obj of data){
       for(let item in obj){
          let tmp = document.createElement('li')
          tmp.innerText=obj[item]
          list.appendChild(tmp)
       }
       //分割线为了美观
       list.appendChild(document.createElement('hr'))
    }
 </script>
</body>
复制代码

image.png

数组join法

JS创建DOM的方法还可以使用html字符串,但是在JS中的传统字符串是不能换行的,如下:

let str = 'asdasd
asdasd
asdasd'
复制代码

这样就导致了,在代码中无法清晰看出层级关系,可读性很差。

ES6中的出现使用反引号的模板字符串在曾经还未出现,但是数据中的元素在代码中是可以换行的,所以使用的是数组join方法来实现类似的效果:

let arr = ['A',
           'B',
           'C',
           'D']
let str = arr.join('')
consloe.log(str)//ABCD
复制代码

实现

<body>
  <ul id="list"></ul>
  <script>
    let data = [
      {"name":"小明", "age":12, "sex":"男"},
      {"name":"小红", "age":11, "sex":"女"},
      {"name":"小强", "age":13, "sex":"男"}
    ]
    
    let list  = document.querySelector('#list')
    ////将数据与字符串拼接
    for(let obj of data){
      //代码层级非常清晰,获得这样的代码可以先在html中编辑好,再利用编辑器的多行编辑
      list.innerHTML+=[
      '<li>',
      '  <div class="hd">'+obj.name+'的基本信息</div>',
      '    <div class="bd">',
      '      <p>姓名:'+obj.name+'</p>',
      '      <p>年龄:'+obj.age+'</p>',
      '      <p>性别:'+obj.sex+'</p>',
      '    </div>',
      '</li>',
      ].join('')
      //分割线为了美观
      list.appendChild(document.createElement('hr'))
    }
  </script>
</body>
复制代码

image.png

ES6反引号法

<body>
  <ul id="list"></ul>
  <script>
    let data = [
      {"name":"小明", "age":12, "sex":"男"},
      {"name":"小红", "age":11, "sex":"女"},
      {"name":"小强", "age":13, "sex":"男"}
    ]
    
    let list  = document.querySelector('#list')
    
    for(let obj of data){
      //ES6语法中的模板字符串可以直接换行,并且可以用${}拼接
      list.innerHTML+=`
        <li>
          <div class="hd">${obj.name}的基本信息</div>
            <div class="bd">
              <p>姓名:${obj.name}</p>
              <p>年龄:${obj.age}</p>
              <p>性别:${obj.sex}</p>
            </div>
        </li>`
      //分割线为了美观
      list.appendChild(document.createElement('hr'))
    }
  </script>
</body>
复制代码

效果与上面相同。

基本使用

  • Mustache官方git:github.com/janl/mustac…
  • Mustache,胡子,因为它的嵌入标记{{}}非常像胡子
  • {{}}语法也被Vue沿用
  • Mustache是最早的模板引擎库,比Vue诞生的还要早得多,它的底层实现机理在当时是非常有创造性的,轰动性的,为后续的模板引擎发展提供了崭新的思路

这里只讲基本使用,不使用npm和node进行安装和使用,直接使用引用编译好的库或者CDN链接即可。

这里使用的是Mustache4.0.1

<script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.0.1/mustache.js"></script>
复制代码

引用后,这个库会提供一个名为Mustache的全局变量。

最重要的方法Mustache.render(templateStr,data),第一个参数放模板字符串,第二个参数为渲染用的数据。

  1. 普通对象

    <body>
      <script src="./mustache-4.0.1.js"></script>
      <div id="box"></div>
      <script>
        let data={
          OS:'鸿蒙',
          mood:'好'
        }
        let html = '今天手机安装了{{OS}}系统,心情变得真{{mood}}'
        let box  = document.querySelector('#box')
        box.innerHTML=Mustache.render(html,data)
      </script>
    </body>
    复制代码
  2. 循环数组

      <script src="./mustache-4.0.1.js"></script>
      <ul id="list"></ul>
      <script>
        let data = {
          arr:[
            {"name":"小明", "age":12, "sex":"男","hobbies":["篮球","编程"]},
            {"name":"小红", "age":11, "sex":"女","hobbies":["唱歌"]},
            {"name":"小强", "age":13, "sex":"男","hobbies":[]}
          ]
        }
        
        let list  = document.querySelector('#list')
        //{{#arr}}{{/arr}}中的内容会被模板引擎循环生成,arr其中的对象属性直接用即可
        //传统数组直接用{{.}}即可表示数组元素
        //循环可以嵌套
        let html = `
            {{#arr}}
              <li>
                <div class="hd">{{name}}的基本信息</div>
                  <div class="bd">
                    <p>姓名:{{name}}</p>
                    <p>年龄:{{age}}</p>
                    <p>性别:{{sex}}</p>
                    <p>性别:爱好:</p>
                    <ol> 
                      {{#hobbies}}
                      <p><li>{{.}}</li></p>
                      {{/hobbies}}
                    </ol>
                  </div>
              </li>
            {{/arr}}`
        //data必须是一个包含arr数组的对象,而不是arr数组对象本身
        list.innerHTML=Mustache.render(html,data)
      </script>
    </body>
    复制代码

image.png

  1. 布尔值

    <body>
      <script src="./mustache-4.0.1.js"></script>
      <div id="box"></div>
      <script>
        let data={
          show : true
        }
        //使用布尔值表示是否显示包含的元素,false不显示,反而反之。
        let html= `
          {{#show}}
          <h1>猜猜我会不会出现?</h1>
          {{/show}}
        `
        let box  = document.querySelector('#box')
        //data必须是一个包含arr数组的对象,而不是arr数组对象本身
        box.innerHTML=Mustache.render(html,data)
      </script>
    </body>
    复制代码
  2. script标签内容作为模板

    <body>
      <script src="./mustache-4.0.1.js"></script>
      <!-- type只要不被浏览器认识即可,这样用既可以看清元素层级,还可以继续使用编辑器的Emmet功能快速编辑  --> 
      <script type="text/template" id="myTemplate">
       {{#arr}}
         <li>
           <div class="hd">{{name}}的基本信息</div>
             <div class="bd">
               <p>姓名:{{name}}</p>
               <p>年龄:{{age}}</p>
               <p>性别:{{sex}}</p>
               <p>性别:爱好:</p>
               <ol> 
                 {{#hobbies}}
                 <p><li>{{.}}</li></p>
                 {{/hobbies}}
               </ol>
             </div>
         </li>
       {{/arr}}
      </script>
      <ul id="list"></ul>
      <script>
        let data = {
          arr:[
            {"name":"小明", "age":12, "sex":"男","hobbies":["篮球","编程"]},
            {"name":"小红", "age":11, "sex":"女","hobbies":["唱歌"]},
            {"name":"小强", "age":13, "sex":"男","hobbies":[]}
          ]
        }
        let list  = document.querySelector('#list')
        let html = document.querySelector('#myTemplate').innerHTML
        list.innerHTML=Mustache.render(html,data)
      </script>
    </body>
    复制代码

Mustache不支持任何运算和表达式如{{1+1}}等

底层核心机理

一张图可以概括Mustache的机理

image.png

Mustache底层只做两件事情

  1. 将模板字符串编译为tokens形式
  2. tokens结合数据,解析为DOM字符串

简单正则表达式

Mustache库并非是用正则表达式实现的,但是简单的Mustache功能可以通过正则表达式实现。

<body>
  <div id="box"></div>
  <script>
    let html  = '我今天学习了{{knowledge}}的底层原理,虽然有点{{level}},但是很{{mood}}'
    let data = {
      knowledge: 'Mustache',
      level: '难',
      mood: '开心'
    }
    function render(templateStr,data){
      //str.replace(正则表达式,替换字符,可以是函数的返回值)
      // (\w+)的括号用于捕获内容
      // /g全局模式,不然只会替换匹配的第一个
      // 回调函数,findStr:匹配到的字符串如{{knowledge}},$1:捕获到的字符串如knowledge,$2和$3(这里没用上)分别为匹配到的下标和整个templateStr字符串本身
      return templateStr.replace(/\{\{(\w+)\}\}/g,function(findStr,$1){
        return data[$1]
      })
    }
    document.querySelector('#box').innerText = render(html,data)//我今天学习了Mustache的底层原理,虽然有点难,但是很开心
  </script>
</body>
复制代码

Token思想

简单正则表达式的实现算一个餐前小菜,前面概括图中的模板字符串,已经不陌生了,但是编译过后的tokens是什么呢?

tokens是一个JS数组,说白了,就是模板字符串的JS表示,它是抽象语法树(AST)虚拟DOM等等的开山鼻祖。

在Mustache中,模板字符串将会被进行以下抽象:

image.png

这里转换出来的tokens是一个二维数组,通过观察不难发现,模板字符串以{{}}为界限被分割成不同的部分。{{}}外的字符串被命名为text{{}}内的字符串被命名为name。它们的值都被放在数组的第二位。并且每一段都会作为一个数组对象,这个数组对象称为token,包含这些数组对象的数组称为tokens

当有循环情况下的模板字符串,会被转换成嵌套更深tokens

image.png

循环会变成一个新的类型#,数组第二位为循环数组的变量名,第三位又是一个tokens

双重循环下模板字符串,转换后的tokens,嵌套更深:

image.png

其中的null为匹配位置的开始索引到结束索引,不做校验,没有任何用处。

暴露源码tokens

因为Mustache库直接做好了编译tokens和数据结合,并没有直接将tokens直接展示。所以需要改动一下源代码,使我们能够看到真正tokens是什么样的。

这是这里使用的版本:cdn.bootcdn.net/ajax/libs/m… ,打开后浏览器可以直接看,但是浏览器观看可读性较差,建议另存为或复制到本地,在编辑器中查看。

需要修改的源码位置在parseTemplate方法的结尾处。

  function parseTemplate (template, tags) {
     ...
     return nestTokens(squashTokens(tokens));
  }
复制代码

改为:

function parseTemplate (template, tags) {
    var tokens = nestTokens(squashTokens(tokens));
    console.log(tokens);
    return tokens;
}
复制代码

这样每当使用Mustache时,可以自动在控制台输出tokens

再次运行前面基本使用的例子1和4,查看控制台:

image.png

其中的数字为匹配位置的开始索引到结束索引,不做校验,没有任何用处。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享