>웹 프론트엔드 >JS 튜토리얼 >Javascript 템플릿 엔진 mustache.js_javascript 기술에 대한 자세한 설명

Javascript 템플릿 엔진 mustache.js_javascript 기술에 대한 자세한 설명

WBOY
WBOY원래의
2016-05-16 15:19:081095검색

이 글은 사용 방법과 일부 사용 경험을 요약한 것입니다. 내용은 그다지 고급이 아니며 순전히 소개 내용이므로 살펴보시기 바랍니다. 그러나 이러한 종류의 JavaScript 엔진 라이브러리를 사용해 본 적이 없더라도 이 기사는 여전히 읽을 가치가 있습니다. 강력한 기능과 간단한 사용법을 이해한 후에는 작업에 사용하고 싶어질 것입니다.

1. 간단하고 실제적인 필요부터 시작하겠습니다
현재 회사에서는 MVC 인터페이스와 데이터 추가, 삭제, 수정, 쿼리를 위한 인터페이스를 통합 개발 플랫폼으로 구축했습니다. CAS를 기반으로 통합된 권한 관리 시스템을 최초로 구축했습니다. 이 시스템은 모든 하위 시스템의 메뉴와 권한을 관리 및 구성하는 데 사용됩니다. 이후 우리는 비즈니스 시스템 A와 비즈니스 시스템 B를 연속적으로 개발했습니다. 이 세 개의 하위 시스템은 세 개의 Java 프로젝트에 해당하므로 최종 배포 중에 세 개의 애플리케이션이 Tomcat에 배포되었습니다.

  • 1) 각 시스템에 로그인한 후 시스템 이름을 클릭하여 승인된 모든 하위 시스템을 표시하는 드롭다운 메뉴를 확장합니다.
  • 2) 그런 다음 사용자는 다른 하위 시스템을 클릭하여 선택한 시스템으로 전환할 수 있습니다. 이 드롭다운 메뉴가 생성되므로 해당 시스템에서 다시 전환할 수도 있습니다.
  • 3) 사용자에게 시스템 권한이 하나만 있는 경우 드롭다운 메뉴가 표시되지 않습니다.

요구 사항은 실제로 매우 간단합니다. 프로토타입은 다음과 같습니다.

이 기능은 각 하위 시스템이 로그인된 후 시스템 목록을 얻기 위해 인터페이스를 호출하고 js를 사용하여 드롭다운 메뉴를 렌더링함으로써 구현됩니다.

data: [
 {
  "sortOrder": 1,
  "isCurrent": true, 
"systemHttpUrl": "http://xxxx:8080/permission",
  "systemName": "统一权限管理系统"
 },
 {
  "sortOrder": 2,
  "isCurrent": false, 
  "systemHttpUrl": "http://xxxx:8080/systemA",
  "systemName": "业务系统A"
 },
 {
  "sortOrder": 3,
  "isCurrent": false, 
  "systemHttpUrl": "http://xxxx:8080/systemB",
  "systemName": "业务系统B"
 }
]

템플릿 엔진을 사용하지 않는 경우 이 데이터를 구문 분석하고 HTML 문자열로 변환하는 전통적인 방법은 일반적으로 다음과 같습니다.

function data2Html(data) {
 data = data || [];
 var html = ['<ul class="nav navbar-nav navbar-left nav-system">',
   '  <li class="dropdown">',
   '  <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown" title="切换系统">'],
  l = data.length;

 if(l < 2) {
  l == 1 && html.push(data[0].systemName || '');
  html.push('</a></li></ul>');
  return html.join('');
 }

 var curSysAry = data.filter(function(s){ return s.isCurrent; });
 html.push(curSysAry[0].systemName + ' <i class="fa fa-caret-down"></i></a><ul class="dropdown-menu">');
 data.sort(function(a, b){ return a.sortOrder - b.sortOrder;});
 for(var i = 0; i < l; i++) {
  i && html.push('<li role="separator" class="divider"></li>');
  html.push('<li><a href="' + data[i].systemHttpUrl + '" target="_self">' +
   data[i].systemName + '</a></li>');
 }
 html.push('</ul></li></ul>');
 return html.join('');
}

이 문자열 연결 방법에는 많은 단점이 있습니다.

  • 1) 특히 접합 논리가 복잡하고 접합 문자열이 매우 긴 경우 문제가 발생합니다.
  • 2) 관리가 쉽지 않습니다. 주의하지 않으면 잘못된 라벨 대응을 받게 됩니다.
  • 3) 구조가 명확하지 않습니다.

이 시나리오를 단순화할 수 있는 도구는 템플릿 엔진입니다. 템플릿 엔진의 기술적 배경이 먼저입니다. jsp를 사용해 본 적이 있다면 jsp가 데이터를 구문 분석하고 표시하는 데 사용되는 템플릿이라는 점을 알아야 합니다. 템플릿 엔진에는 속도(Velocity), 프리마커(Freemarker) 등이 있습니다. 많은 프런트엔드 템플릿 엔진이 있는데, mustache.js는 git에서 8,000개 이상의 좋아요를 받았습니다. 이 문제를 해결하기 위해 mustache.js를 사용하면 다음과 같습니다.

//通过一些根据属性名称对应的标记定义模板
var _template = [
  '<ul class="nav navbar-nav navbar-left nav-system">',
  ' <li class="dropdown">',
  ' <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown" title="切换系统">',
  '  {{curSystemName}} {{#multiple}}<i class="fa fa-caret-down"></i>{{/multiple}}',
  ' </a>',
  ' {{#multiple}}<ul class="dropdown-menu">',
  '  {{#systems}}',
  '   {{^first}}<li role="separator" class="divider"></li>{{/first}}',
  '   <li>',
  '    <a href="{{{systemHttpUrl}}}" target="_self">{{systemName}}</a>',
  '   </li>',
  '  {{/systems}}',
  ' </ul>{{/multiple}}',
  ' </li>',
  '</ul>'
 ].join('');

//初始化这个模板
Mustache.parse(_template);
function data2Html(data) {
 data = data || [];
 var curSysAry = data.filter(function(s){ return s.isCurrent; });
 data.sort(function(a, b){ return a.sortOrder - b.sortOrder;});
 data = data.map(function(s, i){s.first = i == 0; return s});

 //模板渲染成字符串
 return Mustache.render(_template, {
  curSystemName: curSysAry.length &#63; curSysAry[0].systemName : '',
  multiple: !!data.length,
  systems: data
 });
}

두 코드를 비교해 보면 후자의 코드가 이전 코드에 비해 다음과 같은 장점이 있다는 것을 알 수 있습니다.

  • 1) 구조가 명확하고 렌더링할 모든 HTML이 한 위치에 정의되어 있으며 연결이 없습니다.
  • 2) 논리는 명확합니다. 템플릿의 태그는 실제로 템플릿이 렌더링될 때 전달된 개체의 속성 이름과 일치합니다.
  • 3) 관리가 용이합니다. 태그를 추가하거나 삭제하려면 템플릿에 해당하는 배열만 조정하면 됩니다.
  • 이 예를 통해 템플릿 엔진에 대한 일반적인 이해를 얻을 수 있습니다. 이러한 유형의 도구는 프런트엔드 개발, 특히 프런트엔드와 백엔드를 분리하는 애플리케이션에서 점점 더 보편화되고 있습니다. 이는 이미 그러한 애플리케이션의 기초입니다. mustache.js는 매우 간단하고 사용하기 쉬운 엔진 구현입니다. 다음 콘텐츠에서는 이 도구의 일반적으로 사용되는 템플릿 구성을 실용적인 예와 함께 하나씩 소개합니다.)

2.콧수염 활용법

콧수염의 사용은 매우 간단합니다. 먼저 스크립트 태그를 통해 js 파일을 소개한 후 아래 단계를 따르세요.
1) 템플릿 문자열 정의
템플릿을 정의하는 방법에는 두 가지가 있습니다. 첫 번째 방법은 이전 부분에서 본 것과 같습니다. 두 번째 방법은 [...].join('')을 사용하여 js 코드에서 직접 정의합니다. js 코드에서 템플릿을 직접 정의하는 것입니다.
스크립트를 사용하여 내용을 html로 정의합니다.

그런 다음 템플릿을 컴파일하기 전에 tpl의 innerHTML을 가져와 원본 템플릿 문자열을 정의합니다.
<script id="tpl" type="text/html">
 Hello {{name}}!
</script>

具体要用哪种方式来定义模板,可以参考下面的建议:
如果这个模板要用于多个页面,推荐把模板定义在js代码中;如果这个模板只用于当前页面,推荐直接定义到script标签中,管理更方便。
2)预编译模板
假设原始模板串已经定义好,并用tpl变量来引用,就可以通过下面的代码来预编译模板:

Mustache.parse(tpl);

要注意的是,经过预编译之后的tpl已经不再是原来的模板串了,连数据类型都变成数组类型了,这都是预编译的结果。
3)渲染模板
渲染方式很简单:

var htmlAfterRendered = Mustache.render(tpl1, obj);

obj引用的是一个数据源对象,mustache会把模板中那些属性标签,根据约定的规则,替换成对象的内容。htmlAfterRendered就是替换之后的字符串,你可以用它完成你需要的DOM操作。

3. mustache的思想
mustache的核心是标签和logic-less。从前面的代码中可以看到定义模板时,使用了{{name}}这样的标记,还有{{#systems}}{{/systems}},这就是mustache的标签,只不过它用{{}}替代了a8093152e673feb7aba1828c43532094,以免跟html标签的a8093152e673feb7aba1828c43532094混淆。logic-less,可以翻译为轻逻辑,因为在定义模板的时候不会用到if-else,不会有循环式的编码,一切都用标签来解决,它的标签非常简单,但是能应付所有场景,阅读完本文之后,你会惊讶地发现,只要用以下几个标签几乎就能解决所有的问题:
{{prop}}
{{{prop}}}
{{#prop}}{{/prop}}
{{^prop}}{{/prop}}

4. {{prop}}标签
这个标签是mustache模板里用的最多的,可以将数据源对象上prop属性对应的值,转换成字符串进行输出,以下是同一个属性,对应不同类型的值,在经过mustache渲染之后输出结果的测试(前后那根短横线的作用是为了让这个标签的渲染结果看起来更清楚):

<script id="tpl1" type="text/html">
 -{{prop}}-
</script>
<script>
 var tpl1 = document.getElementById('tpl1').innerHTML.trim();
 Mustache.parse(tpl1);
 //测试falsy值
 console.log(Mustache.render(tpl1, {prop: ''}));//--
 console.log(Mustache.render(tpl1, {prop: 0}));//-0-
 console.log(Mustache.render(tpl1, {prop: null}));//--
 console.log(Mustache.render(tpl1, {prop: undefined}));//--
 console.log(Mustache.render(tpl1, {prop: false}));//-false-
 console.log(Mustache.render(tpl1, {prop: NaN}));//-NaN-

 //测试简单对象
 console.log(Mustache.render(tpl1, {prop: {name: 'jason'}}));//-[object Object]-
 //测试数组
 console.log(Mustache.render(tpl1, {prop: [{name: 'jason'}, {name: 'frank'}]}));//-[object Object],[object Object]-
 //测试日期对象
 console.log(Mustache.render(tpl1, {prop: new Date()}));//-Mon Jan 18 2016 15:38:46 GMT+0800 (中国标准时间)-
 //测试自定义toString的简单对象
 var obj1 = {name: 'jason'};
 obj1.toString = function () {
  return this.name;
 };
 console.log(Mustache.render(tpl1, {prop: obj1}));//-jason-

 //测试boolean number string
 console.log(Mustache.render(tpl1, {prop: true}));//-true-
 console.log(Mustache.render(tpl1, {prop: 1.2}));//-1.2-
 console.log(Mustache.render(tpl1, {prop: 'yes'}));//-yes-

 //测试function
 console.log(Mustache.render(tpl1, {
  prop: function () {
  }
 }));//--
 console.log(Mustache.render(tpl1, {
  prop: function () {
   return 'it\'s a fun'
  }
 }));//-it&#39;s a fun-
 console.log(Mustache.render(tpl1, {
  prop: function () {
   return false;
  }
 }));//-false-
 console.log(Mustache.render(tpl1, {
  prop: function(){
   return function (text, render) {
    return "<b>" + render(text) + "</b>"
   };
  }
 }));
 //-function (text, render) {
 // return "<b>" + render(text) + "<&#x2F;b>"
 //}-

</script>

mustache渲染{{prop}}标签的逻辑是:

  • 1)如果prop引用的值是null或undefined,则渲染成空串;
  • 2)如果prop引用的是一个函数,则在渲染时自动执行这个函数,并把这个函数的返回值作为渲染结果,假如这个返回值为null或者undefined,那么渲染结果仍然为空串,否则把返回值转成字符串作为渲染结果(注意最后一个用例,直接把函数代码渲染出来了);
  • 3)其它场景,直接把prop引用的值转成字符串作为渲染结果。

由于默认情况下,mustache在渲染prop时,都是对prop的原始值进行url编码或者html编码之后再输出的,所以有一个用例的渲染结果,把英文的单引号,转成了html实体符:

console.log(Mustache.render(tpl1, {
  prop: function () {
   return 'it\'s a fun'
  }
}));//-it&#39;s a fun-

如果要阻止这种编码行为,只要把标签形式改成{{{prop}}}就可以了:

<script id="tpl1" type="text/html">
 -{{{prop}}}-
</script>
console.log(Mustache.render(tpl1, {
 prop: function () {
  return 'it\'s a fun'
 }
}));//-it's a fun-

5. {{#prop}}{{/prop}}标签
这对标签的作用非常强大,可以同时完成if-else和for-each以及动态渲染的模板功能。在这对标签之间,可以定义其它模板内容,嵌套所有标签。接下来看看mustache如何利用这个对标签完成这三个模板功能。

1) if-else渲染

只有prop属性在数据源对象上存在,并且不为falsy值(javascript 6个falsy值:null,undefined,NaN,0,false,空字符串),并且不为空数组的情况下,标签之间的内容才会被渲染,否则都不会被渲染:

<script id="tpl2" type="text/html">
 -{{#prop}}content{{/prop}}-
</script>
<script>
 var tpl2 = document.getElementById('tpl2').innerHTML.trim();
 Mustache.parse(tpl2);
 //测试falsy值
 console.log(Mustache.render(tpl2, {prop: ''}));//--
 console.log(Mustache.render(tpl2, {prop: 0}));//--
 console.log(Mustache.render(tpl2, {prop: null}));//--
 console.log(Mustache.render(tpl2, {prop: undefined}));//--
 console.log(Mustache.render(tpl2, {prop: false}));//--
 console.log(Mustache.render(tpl2, {prop: NaN}));//--
 //测试空数组
 console.log(Mustache.render(tpl2, {prop: []}));//--
 //测试不存在的属性
 console.log(Mustache.render(tpl2, {prop2: 
true
}));//--
 //测试function
 console.log(Mustache.render(tpl2, {
  prop: function () {
  }
 }));//--
 console.log(Mustache.render(tpl2, {
  prop: function () {
   return false;
  }
 }));//--
 console.log(Mustache.render(tpl2, {
  prop: function() {
   return [];
  }
 }));//--
 
 //测试简单对象
 console.log(Mustache.render(tpl2, {prop: {name: 'jason'}}));//-content-
 //测试日期对象
 console.log(Mustache.render(tpl2, {prop: new Date()}));//-content-
 //测试boolean number string
 console.log(Mustache.render(tpl2, {prop: true}));//-content-
 console.log(Mustache.render(tpl2, {prop: 1.2}));//-content-
 console.log(Mustache.render(tpl2, {prop: 'yes'}));//-content-
 //测试返回非falsy,非空数组的function
 console.log(Mustache.render(tpl2, {
  prop: function () {
   return 'it\'s a fun'
  }
 }));//-content-
</script>

以上用例中特殊点的就是prop属性引用的是一个函数的时候,{{#prop}}会自动调用这个函数,并把函数的返回值作为if-else渲染逻辑的判断依据,也就是说如果这个函数返回的是falsy值或者是空数组的时候,那么这对标签之间的内容还是不会显示。

2)for-each渲染

当prop属性所引用的是一个非空数组时,这对标签之间的内容将会根据数组大小进行迭代,并且当数组元素为对象时,还会把该对象作为每一次迭代的上下文,以便迭代时的标签可以直接引用数组元素上的属性:

<script id="tpl2" type="text/html">
 -{{#prop}}{{name}},{{/prop}}-
</script>
<script>
  var tpl2 = document.getElementById('tpl2').innerHTML.trim();
  Mustache.parse(tpl2);
  console.log(Mustache.render(tpl2, {prop: [{name: 'jason'}, {name: 'frank'}]}));//-jason,frank,-
</script>

从这个测试结果中可以看到,{{#prop}}{{/prop}}之间的模板内容根据prop所引用的数组迭代了两次,并且在这对标签内部直接通过{{name}}标签,输出了数组元素对象上的name属性对应的值。

如果prop属性所引用的是一个函数,但是这个函数返回值是一个数组类型,那么仍然会进行for-each渲染:

<script id="tpl2" type="text/html">
 -{{#prop}}{{name}},{{/prop}}-
</script>
<script>
  var tpl2 = document.getElementById('tpl2').innerHTML.trim();
  Mustache.parse(tpl2);
  console.log(Mustache.render(tpl2, {
   prop: function(){
    return [{name: 'jason'}, {name: 'frank'}];
   }
  }));//-jason,frank,-
</script>

3) 动态渲染

当prop属性所引用的是一个函数,并且这个函数的返回值还是一个函数的话,mustache会再次调用这个返回的函数,并给它传递2个参数:text表示原来的模板内容,render表示mustache内部的执行渲染的对象,以便在这个函数内部可以通过这render对象,结合原来的模板内容,自定义渲染的逻辑,并把函数的返回值作为渲染结果(这个返回值渲染的逻辑跟{{prop}}标签完全一样):

<script id="tpl2" type="text/html">
 -{{#prop}}content{{/prop}}-
</script>
<script>
  var tpl2 = document.getElementById('tpl2').innerHTML.trim();
  Mustache.parse(tpl2);
  console.log(Mustache.render(tpl2, {
  prop: function(){
   return function (text, render) {
    return "<b>" + render(text) + "</b>"
   };
  }
 }));//-<b>content</b>-
</script>

6. {{^prop}}{{/prop}}标签
这对标签,与{{#prop}}{{/prop}}的if-else渲染执行相反逻辑,即只有在prop属性不存在,或者引用的是一个falsy值,或者是一个空数组的时候才会显示标签之间的内容,否则不会显示:

<script id="tpl2" type="text/html">
 -{{^prop}}content{{/prop}}-
</script>
<script>
 var tpl2 = document.getElementById('tpl2').innerHTML.trim();
 Mustache.parse(tpl2);
 //测试falsy值
 console.log(Mustache.render(tpl2, {prop: ''}));//-content-
 console.log(Mustache.render(tpl2, {prop: 0}));//-content-
 console.log(Mustache.render(tpl2, {prop: null}));//-content-
 console.log(Mustache.render(tpl2, {prop: undefined}));//-content-
 console.log(Mustache.render(tpl2, {prop: false}));//-content-
 console.log(Mustache.render(tpl2, {prop: NaN}));//-content-
 // 测试空数组
 console.log(Mustache.render(tpl2, {prop: []}));//-content-
 // 测试不存在的属性
 console.log(Mustache.render(tpl2, {prop2: true}));//-content-
 //测试function
 console.log(Mustache.render(tpl2, {
  prop: function () {
  }
 }));//-content-
 console.log(Mustache.render(tpl2, {
  prop: function () {
   return false;
  }
 }));//-content-
 console.log(Mustache.render(tpl2, {
  prop: function () {
   return [];
  }
 }));//-content-


 //测试简单对象
 console.log(Mustache.render(tpl2, {prop: {name: 'jason'}}));//--
 //测试日期对象
 console.log(Mustache.render(tpl2, {prop: new Date()}));//--
 // 测试非空数组
 console.log(Mustache.render(tpl2, {prop: [{name: 'jason'},{name: 'tom'}]}));//--

 //测试boolean number string
 console.log(Mustache.render(tpl2, {prop: true}));//--
 console.log(Mustache.render(tpl2, {prop: 1.2}));//--
 console.log(Mustache.render(tpl2, {prop: 'yes'}));//--
 
 //测试返回非falsy,非空数组的function
 console.log(Mustache.render(tpl2, {
  prop: function () {
   return 'it\'s a fun'
  }
 }));//--

 //测试返回function的function
 console.log(Mustache.render(tpl2, {
  prop: function () {
   return function(text,render){
    return '<b>' + render(text) +'</b>'
   }
  }
 }));//--
</script>

7. 渲染上下文
mustache有一个渲染上下文栈的概念,在模板渲染的开始的时候,把数据源对象作为当前的渲染上下文 ,并压入上下文栈。在遇到{{#prop}}标签的时候,如果prop引用的是一个对象或者是一个非空的对象数组,或者prop引用的是一个函数,并且这个函数返回的是一个对象或者是一个非空的对象数组,就会把这个对象或者数组的元素作为当前渲染上下文,并压入上下文栈,当这个标签渲染完毕的时候,才会把该上下文弹出,恢复上一层标签所使用的上下文。由于{{#prop}}标签可以多层嵌套,所以在有的模板渲染的时候,会有多层上下文存在。mustache在解析标签时,根据标签名称查找当前上下文对象是否存在该属性,如果不存在就会到上层上下文对象中查找,只要在某一层找到,就会用该层上下文对象的值来渲染。

<script id="tpl2" type="text/html">
 -{{#person}}{{#student}}{{#address}}address: {{home}},age: {{age}}{{/address}}{{/student}}{{/person}}-
</script>
<script>
 var tpl2 = document.getElementById('tpl2').innerHTML.trim();
 var obj2 = {
  age: 20,
  person: {
   student: {
    address: {
     home: 'xxxxx'
    }
   }
  }
 };
 console.log(Mustache.render(tpl2, obj2));//-address: xxxxx,age: 20-
</script>

上面这个例子中,在渲染{{#address}}{{/address}}时,上下文对象已经变成了obj2.person.student.address所引用的对象,所以{{home}}渲染时用到的就是obj2.person.student.address.home属性,而{{age}}渲染时,由于obj2.person.student.address不存在age属性,所以就会到上层上下文中查找,一直到obj2对象才找到,于是就把obj2.age当成了渲染结果。

还有一种方法,不用通过{{#prop}}创建新的上下文,也可以做到递归渲染属性的属性:

<script id="tpl2" type="text/html">
 -address: {{person.student.address.home}},age: {{age}}-
</script>
<script>
 var tpl2 = document.getElementById('tpl2').innerHTML.trim();
 var obj2 = {
  age: 20,
  person: {
   student: {
    address: {
     home: 'xxxxx'
    }
   }
  }
 };
 console.log(Mustache.render(tpl2, obj2));//-address: xxxxx,age: 20-
</script>

这种方法其实很好理解,只要知道当前的上下文对象,再根据属性操作串person.student.address.home,当然就能找到需要的值了。

本文介绍了一个非常好用的前端模板引擎,涵盖的内容包含了在日常工作肯定会用到的知识点,希望大家喜欢。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.