最简单的JavaScript模板引擎

 在小公司待久了感觉自己的知识面很小,最近逛博客园和一些技术网站看大家在说JavaScript模版引擎的事儿,完全没有概念,网上一搜这是08年开始流行起来的。。。本来以为这是很高深的知识,后来在网上看到jQuery作者John Resig,研究了一下,算是明白了最简单的javaScript模版引擎的原理,并没有想象的那么高大上,写篇博客推导一下John Resig写法的过程,写出一个最简单的JavaScript模版引擎。

什么是JavaScript引擎

 其实在网站开发中模板还是很常见的一种技术,比如PHP的Smarty、ASP.NET的Master Page等,但这些模板都是基于服务器的,JavaScript模板引擎是为了解决我们在前端写出形如这样的拼html的语句

var html='<ul>';
for(var i=0;i<users.length;i++){
  html+='<li><a href=">'+users[i].url+'">'+users[i].name+'</a>';
}
html+='</ul>';
document.getElementById('results').innerHTML=html;

 上面的代码我们一看就知道是在拼html,但具体拼的什么很难说清,需要逐句去读代码,如果我们有这样一个模板

<ul>
    <% for ( var i = 0; i < users.length; i++ ) { %>
         <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
    <% } %>
</ul>

 看了很容易就明白开发者希望得到的是这样的html

<ul>
  <li><a href="XXX">OOO</a></li>
  <li><a href="XXX">OOO</a></li>
  <li><a href="XXX">OOO</a></li>
</ul>

 JavaScript模板引擎就是帮我们把带有JavaScript代码的伪html语句翻译为html的东东

John Resig的实现方式

先看看John Resig是怎么实现最简单的一个JavaScript模板引擎的

1 // Simple JavaScript Templating
 2 // John Resig - http://ejohn.org/ - MIT Licensed
 3 (function(){
 4   var cache = {};
 5  
 6   this.tmpl = function tmpl(str, data){
 7     // Figure out if we're getting a template, or if we need to
 8     // load the template - and be sure to cache the result.
 9     var fn = !/\W/.test(str) ?
10       cache[str] = cache[str] ||
11         tmpl(document.getElementById(str).innerHTML) :
12      
13       // Generate a reusable function that will serve as a template
14       // generator (and which will be cached).
15       new Function("obj",
16         "var p=[],print=function(){p.push.apply(p,arguments);};" +
17        
18         // Introduce the data as local variables using with(){}
19         "with(obj){p.push('" +
20        
21         // Convert the template into pure JavaScript
22         str
23           .replace(/[\r\t\n]/g, " ")
24           .split("<%").join("\t")
25           .replace(/((^|%>)[^\t]*)'/g, "$1\r")
26           .replace(/\t=(.*?)%>/g, "',$1,'")
27           .split("\t").join("');")
28           .split("%>").join("p.push('")
29           .split("\r").join("\\'")
30       + "');}return p.join('');");
31    
32     // Provide some basic currying to the user
33     return data ? fn( data ) : fn;
34   };
35 })();

 看完上面代码就明白的同学就不用看下面内容了,没太明白的同学可以和我一块儿看看着三十多句代码为什么能够实现一个JavaScript引擎吧。

 模板的语法

模板的语法很简单,有三条基本规则

  1. 用正常的方式书写html
  2. 用<% %>嵌套JavaScript语句
  3. 用<%= %>嵌套JavaScript 变量值

模板转换为html字符串原理 

我们的JavaScript引擎正式设计为识别这种类型的模板的,拿上面的做例子,这样的一个模版

<ul>
    <% for ( var i = 0; i < users.length; i++ ) { %>
         <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
    <% } %>
</ul>

想得到预期html字符串,我们必须设法让模板内部的javascript变量置换、javaScript语句执行,也就是把JavaScript代码剥离出来执行,把其它html语句拼接为一个字符串

var p=[];
p.push('<ul>');
for(var i=0;i<users.length;i++){ //javascript语句执行
  p.push('<li><a href="'); //html语句拼接
  p.push(users[i].url); //javascript变量置换后拼接
  p.push('">');
  p.push(users[i].name);
  p.push('</a></li>');
}
p.push('</ul>');

 最后得到的数组join一下就是我们希望得到的字符串了,首先需要取到模板内的字符串,这个简单按照John的做法我们可以把模板放到一个script标签里(防止在页面显示出来),换成我们特定的类型

<script type="text/html" id="user_tmpl">
        <ul>
          <% for ( var i = 0; i < users.length; i++ ) { %>
            <li>
                <a href="<%=users[i].url%>">
                    <%=users[i].name%>
                </a>
            </li>
          <% } %>
        </ul>
    </script>

这样就可以通过 document.getElementById(str).innerHTML 来获取模版内字符串了,然后我们应用一些简单的法则处理一下模板内字符串

<%=xxx%>           =>     ');p.push(xxx);p.push('
<%                 =>     ');
%>                 =>     p.push('

这样我们就可以得到这样的结构,看起来就已经很接近结果了

p.push('<ul>');
for(var i=0;i<users.length;i++){
  p.push('<li><a href="'); 
  p.push(users[i].url); 
  p.push('">');
  p.push(users[i].name);
  p.push('</a></li>');
}
p.push('</ul>');

简单的字符串置换

现在我们根据上面规则做替换了,这里得使用一些正则表达式和replace函数的知识,不太熟悉的同学可能需要看看  JavaScript 正则表达式上——基本语法 JavaScript正则表达式下——相关方法

1.把<%=xxx%> 替换为 ');p.push(xxx);p.push('

html=html.replace(/<%=(.*?)%>/g,"');p.push(xxx);p.push('");

2.把<%替换为 ');

html=html.replace(/<%/g,"');");

3.把%> 替换为 p.push('

html=html.replace(/%>/g,"p.push('");

我们再把结果用p.push(' 和 '); 包裹起来就可以看到初步效果了

function tmpl(id,data){
        var html=document.getElementById(id).innerHTML;
        var result="var p=[]; p.push('"
            +html.replace(/<%=(.*?)%>/g,"');p.push(xxx);p.push('")
            .replace(/<%/g,"');")
            .replace(/%>/g,"p.push('")
            +" ');";
    }

 这样我们就把html模版内容替换成了这样的一个字符串

var result="
var p=[];
p.push('<ul>');
for(var i=0;i<users.length;i++){ 
  p.push('<li><a href="');
  p.push(users[i].url); 
  p.push('">');
  p.push(users[i].name);
  p.push('</a></li>');
}
p.push('</ul>');"

貌似得到结果了,但我们得到的是字符串,我们预期的是这个字符串执行的结果,很多同学会想到使用eval就可以让字符串变成JavaScript语句执行,但是Jonh使用了另外一种方式——创建function,我们知道除了常用使用function关键字创建一个function

function fn(data){
    console.log(data);
}

 还可以使用Function构造函数来创建一个function

var fn = new Function(arg1, arg2, ..., argN, function_body)

在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码),使用这种方式可以动态(方法体是动态生成的,提前不知道,当然这样做会有效率问题)创建一个方法,也就是说我们还可以使用刚才拼出来的javascript字符串动态创建一个函数

function tmpl(id,data){
        var html=document.getElementById(id).innerHTML;
        var result="var p=[];p.push('"
            +html.replace(/[\r\n\t]/g," ")
            .replace(/<%=(.*?)%>/g,"');p.push($1);p.push('")
            .replace(/<%/g,"');")
            .replace(/%>/g,"p.push('")
            +"');return p.join('');";
        var fn=new Function(data,result);
        return fn(data);
    }

 这样看起来很科学了,但是我们执行一下会报错,原因很简单就是参数的作用域不对,我们需要改变一下动态构造的方法的作用域,这个有很多方式比如apply函数啊什么的,我们暂且采用John的方式——使用with关键字改变作用域

function tmpl(id,data){
        var html=document.getElementById(id).innerHTML;
        var result="var p=[];with(obj){p.push('"
            +html.replace(/[\r\n\t]/g," ")
            .replace(/<%=(.*?)%>/g,"');p.push($1);p.push('")
            .replace(/<%/g,"');")
            .replace(/%>/g,"p.push('")
            +"');}return p.join('');";
        var fn=new Function("obj",result);
        return fn(data);
    }

虽然看起来和John的方法还有很大区别,不过我们已经偷师到了其精髓,实现了一个最简单JavaScript模版引擎,你是不是也明白了JavaScript模版引擎是什么了呢?就是简单的字符串替换,剥离出JavaScript语句,然后利用新的字符串构造函数,返回结果。

看个例子

<!DOCTYPE html>
<html>
<head>
    <title>Template</title>
</head>
<body>
    <div id="results"></div>
    <script type="text/html" id="user_tmpl">
        <ul>
            <% for ( var i = 0; i < users.length; i++ ) { %>
            <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
            <% } %>
        </ul>
      </script>
    <script type="text/javascript">
    var results = document.getElementById("results");
    var users=[
        {"name":"Byron", "url":"http://localhost"},
        {"name":"Casper", "url":"http://localhost"},
        {"name":"Frank", "url":"http://localhost"}
    ];
    function tmpl(id,data){
        var html=document.getElementById(id).innerHTML;
        var result="var p=[];with(obj){p.push('"
            +html.replace(/[\r\n\t]/g," ")
            .replace(/<%=(.*?)%>/g,"');p.push($1);p.push('")
            .replace(/<%/g,"');")
            .replace(/%>/g,"p.push('")
            +"');}return p.join('');";
        var fn=new Function("obj",result);
        return fn(data);
    }
    results.innerHTML = tmpl("user_tmpl", users);
</script>
</body>
</html>

 应用了简单的JavaScript模版引擎,我们可以很方便的拼出一些html了

参考

John Resig JavaScript Micro-Templating 汤姆大叔 大叔手记(7):构建自己的JavaScript模板小引擎 BarretLee JavaScript模板引擎原理,几行代码的事儿 PS. 目前写的JavaScript模版引擎还有很多性能、特殊情况处理问题,下篇博客(简单JavaScript模版引擎优化)会继续进行一些优化工作

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏肖蕾的博客

图文并茂解释Kotlin == 和 === 之间的差异

1003
来自专栏菩提树下的杨过

linq to sql生成not in语句的小技巧

以前一直觉得linq to sql生成类似where id not in (1,3,5)或where id not in (select id from ......

1945
来自专栏我的小碗汤

为什么在Go语言中要慎用interface{}

在掘金上看到一篇从java转Go思想上的变化以及对go语言思考的文章,写的很透彻,我也推敲了一遍。这里也分享给大家,或许对将要或者已经学习golang的同学有所...

1442
来自专栏小文博客

小文’s blog — 奇妙的数字 –《蓝桥杯代码笔记2》

1325
来自专栏IMWeb前端团队

创建script、link标签,预加载js/css方法createStaticResourceElement(CSRE)

# 代码 create-static-resource.js /** * Created by Capricorncd 2017-09-25 * https...

3486
来自专栏腾讯Bugly的专栏

如何定位Obj-C野指针随机Crash(三):如何让Crash自报家门

本文主要介绍如何利用OC Runtime的特性,让OC野指针对象主动抛出自己的信息,秒杀某些全系统栈Crash。 ? 陈其锋,腾讯SNG即通产品部音视频技术中心...

7964
来自专栏Java爬坑系列

【JAVA零基础入门系列】Day14 Java对象的克隆

  今天要介绍一个概念,对象的克隆。本篇有一定难度,请先做好心理准备。看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充。   克隆...

2106
来自专栏菩提树下的杨过

Silverlight之ListBox/Style学习笔记--ListBox版的图片轮换广告

ListBox是一个很有用的控件,其功能直逼Asp.Net中的Repeater,它能实现自定义数据项模板,纵向/横向排列Item(如果扩展一下实现自行折行,几乎...

2275
来自专栏编程

Shell 中的命令替换及参数扩展

今天和大家分享的主要是 “命令替换” 以及 “参数扩展” 。 -- 季文康 本文导航 ◈ 前言 00% ◈ 什么是命令替换 14% ◈ 1、一层 SHELL 嵌...

2049
来自专栏较真的前端

认识Flow

Flow 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 做了静态类型检查,所以了解 Flow 有助...

811

扫码关注云+社区

领取腾讯云代金券