详解nodejs模板引擎制作


Posted in NodeJs onJune 14, 2017

关于模板,我倒是用过了不少。最开始要数Java的JSP了,然后接触了PHP的smarty,再就是Python的jinja2, Django内置模板,现在刚开始看Nodejs,也发现了不少类似的模板引擎,ejs, jade等等吧。

模板带来的最直接的好处就是加速开发,前后端分离。除此之外,对于字符串的格式化同样是个比较好的应用。习惯了python中

string = "hello {}".format("郭璞") # hello 郭璞
string = "hello {username}".format(username="郭璞") # hello 郭璞

这样简便的用法,突然来到nodejs中,没有了这类特性的原生支持,写起来打印语句就老是觉得很别扭,一点都不优雅。然后我就想自己做一个实现上述功能的工具函数,方便自己的使用。然后就想到了模板这一个方向,虽然想法还不够成熟,甚至是有点拙略,但是“灵(瞎)感(闹)”还是得记录一下不是。

Function对象

JavaScript中有这么一个神奇的对象,那就是Function。如果函数体符合语法要求,那么你就可以动态创建出一个自己的函数出来。下面来个简单的小例子。

无参模式

function create_function(){
  var func_body = "var time = new Date(); console.log('创建时间:'+time);";
  var func = new Function('', func_body);
  func();
}
create_function();

运行结果如下:

E:\Code\Nodejs\learn\my-work\string>node one.js
创建时间:Tue Jun 13 2017 15:40:15 GMT+0800 (中国标准时间)

E:\Code\Nodejs\learn\my-work\string>

有参模式

刚才演示了一个无参数的情况,那么有参数的情况如何呢?

function create_function_with_parameters() {
  var param1 = "郭璞";
  var param2 = "辽宁大连";
  var func_body = "console.log('Hello '+param1+', welcome to '+param2+'!' );";
  var func = new Function('param1', 'param2', func_body);
  func(param1, param2);
}
create_function_with_parameters();

同样的运行结果如下:

E:\Code\Nodejs\learn\my-work\string>node one.js
Hello 郭璞, welcome to 辽宁大连!

E:\Code\Nodejs\learn\my-work\string>

到这里,关于Function的内容就算是铺垫完成了。只需要了解这

正则

探究模板的真实原理,有些语言中是编译型的,有些是替换型的。但是不管是哪种类型,都离不开扣出变量关键字这个步骤。而这个过程用正则表达式基本上是最好的方法了。所以需要掌握一点相关的技巧。

如何表达?

在Nodejs中,使用正则表达式有两种形式:

  1. 字面量: /pattern/flags
  2. RegExp: new RegExp(pattern, flags)

关于正则表达式的具体的规则,鉴于篇幅很长,这里就不再赘述了。有兴趣的可以浏览下面的这篇文章。
https://3water.com/article/39623.htm?source=1

需求获取

根据一开始的设想,目标是获取{{}} 和{%%} 这种语法下的变量名称,然后替换成对应的变量值。 因此可以写出如下的正则表达式:

var pattern1 = /{{([\s\S]+?)}}/gi;
// 或者
var pattern2 = /{%([\s\S]+?)%}/gi;

默认规则如下:

  1. 在{{}} 中直接替换为变量名对应的值。
  2. 在{%%} 中的则是可以添加到函数体的代码块,要保留起来。

简易实现

下面简单的对照着实现一下。

直接变量形式

function test1(){
  var tpl = "Hello {{visitorname}}, Welcome to {{worldname}}!";
  var data = {
    visitorname: "游客",
    worldname: "冰雹工作室"
  };
  var pattern = /{{([\s\S]+?)}}/gi;
  var result = tpl.replace(pattern, (match, tuple)=>{
    return data[tuple];
  });

  console.log("渲染后的数据为:\n", result);
}

实现结果:

E:\Code\Nodejs\learn\my-work\string>node one.js
渲染后的数据为:
 Hello 游客, Welcome to 冰雹工作室!

E:\Code\Nodejs\learn\my-work\string>

对象形式

function test2(){
  var tpl = "I'm {{user.name}}, and I come from {{user.address}}";
  var user = {name: "郭璞", address: "辽宁大连"};
  console.log(user.name);
  var pattern = /{{([\s\S]+?)}}/gi;
  var result = tpl.replace(pattern, function(match, tuple, offset){
    return eval(''+tuple);
  });
  console.log(result);

}

运行效果:

E:\Code\Nodejs\learn\my-work\string>node one.js
郭璞
I'm 郭璞, and I come from 辽宁大连

E:\Code\Nodejs\learn\my-work\string>

混杂多参数实现

刚才实现了只有关键字的和有对象性质的参数的例子,但是实际中情况可能比这要复杂的多,比如混杂模式。接下来着手实现一下混杂模式下的替换策略。

function test3(){
  var tpl = "I am {} of {} years old, and I come from {user.address}.";
  var name = '郭璞';
  var index = 0;
  var paramindex = 0;
  // var parameters = [{name: '郭璞'}, {'age': 22}, {address: '辽宁大连'}];
  var parameters = ['郭璞', 22, {user: {address: '辽宁大连'}}];
  console.log(parameters[2]);
  var result = tpl.replace(/{([\s\S])*?}/gi, function(match, tuple, offset){
    console.log('match:', match);
    console.log('tuple: ', tuple);
    tpl = tpl.slice(index, offset);
    index = offset + match.length;
    paramindex += 1;

    var temp = parameters[paramindex-1];
    if(match.length > 2){
      // 使用tuple不能正确获取到标记中相关的变量名,故用match来代替.
      match = match.slice(1, match.length-1);
      return eval('parameters[paramindex-1].'+match);
    }else{
      return temp;
    }
    // return parameters[paramindex-1];
  });
  console.log(result);
}

运行结果如下:

E:\Code\Nodejs\learn\my-work\string>node one.js
{ user: { address: '辽宁大连' } }
match: {}
tuple: undefined
match: {}
tuple: undefined
match: {user.address}
tuple: s
******* s
I am 郭璞 of 22 years old, and I come from 辽宁大连.

E:\Code\Nodejs\learn\my-work\string>

关于正则这块,大致的内容就是这样了。如果要想更简单的调用,只需要封装起来,用外部参数代替就好了。

当然,注意变量名的命名风格。

实战

废话连篇说了两个小节,还没到正式的模板制作。下面就整合一下刚才例子。模拟着实现一下好了。

(!完整)代码

来个不完整的代码,示意一下算了。

/**
 * 通过正则表达式和Function语法创建一个简单的模板引擎。
 */

const pattern = /{{([\s\S]+?)}}|{%([\s\S]+?)%}|$/img;

function template(text, params, name) {
  // 声明最终要返回的解析好的文本串,也就是构造Function所需的函数体部分。
  var func_body = '';
  // 函数体里面最终效果是返回一个代表了解析完成的字符串的变量,因此要声明一个出来
  func_body += 'var parsedstr="";';
  func_body += 'parsedstr+="';
  // 设置一个定位器,每次更新偏移量,进行全文替换工作
  var index = 0;
  // 开始正则匹配,根据捕获到的元组进行剖析
  text.replace(pattern, function (matchedtext, interpolate, evaluate, offset) {
    // 匹配到正常的HTML文本,则直接添加到func_body中即可
    func_body += text.slice(index, offset);

    // 如果是evaluate类型的文本,则作为代码进行拼接
    if (evaluate) {
      func_body += '";' + evaluate + 'parsedstr+="';
    }

    // 匹配到interpolate类型的文本,则作为变量值进行替换
    if (interpolate) {
      func_body += '"+' + interpolate + '+"';
    }

    // 更新偏移量index,让程序向后移动
    index = offset + matchedtext.length;
    // 貌似返回值没什么用吧
    return matchedtext;
  });

  // 完成函数体的构建之后就可以调用Function的语法实现渲染函数的构建了
  func_body += '"; return parsedstr;';

  return new Function('obj', 'name', func_body)(params, name);
}

function test() {
  var obj = [
    { text: '张三' },
    { text: '李四' },
    { text: '王五' },
    { text: '赵六' },
    { text: '韩七' },
    { text: '王八' }
  ];
  var name = '郭璞';


  var fs = require('fs');
  // var rawtext = fs.readFileSync('index.html').toString('utf8');
  var rawtext = '<ul>{%for(var i in obj){%}<li>{{ obj[i].text }}</li><br>{%}%}</ul>';
  console.log("源文件:", rawtext);
  var result = template(rawtext, obj);
  console.log("渲染后文件:", result, name);
  fs.writeFileSync('rendered.html', result);
  console.log('渲染完毕,请查看rendered.html文件')
}

test();

同级目录下生成的文件内容为:

<ul>
  <li>张三</li><br>
  <li>李四</li><br>
  <li>王五</li><br>
  <li>赵六</li><br>
  <li>韩七</li><br>
  <li>王八</li><br></ul>

感觉效果还行,但是这里面参数太固定化了,实际封装的时候还需要酌情指定,不然这东西也就没什么卵用。

总结

要是论实用性价值的话,这个不成熟的模板实现思路毫无价值。但是对于我而言,用来格式化字符串倒是个不错的选择,估计我会把这个小思路封装成一个小小的模块,详情https://github.com/guoruibiao/have-fun-in-node

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
nodejs中操作mysql数据库示例
Dec 20 NodeJs
nodejs开发微博实例
Mar 25 NodeJs
使用DNode实现php和nodejs之间通信的简单实例
Jul 06 NodeJs
用NodeJS实现批量查询地理位置的经纬度接口
Aug 16 NodeJs
windows 下安装nodejs 环境变量设置
Feb 02 NodeJs
nodejs中模块定义实例详解
Mar 18 NodeJs
使用 NodeJS+Express 开发服务端的简单介绍
Apr 07 NodeJs
nodejs简单实现TCP服务器端和客户端的聊天功能示例
Jan 04 NodeJs
Nodejs实现用户注册功能
Apr 14 NodeJs
通过Nodejs搭建网站简单实现注册登录流程
Jun 14 NodeJs
Nodejs环境实现socket通信过程解析
Jul 03 NodeJs
Nodejs 微信小程序消息推送的实现
Jan 20 NodeJs
Nodejs回调加超时限制两种实现方法
Jun 09 #NodeJs
nodeJS实现路由功能实例代码
Jun 08 #NodeJs
nodeJS实现简单网页爬虫功能的实例(分享)
Jun 08 #NodeJs
详解nodejs异步I/O和事件循环
Jun 07 #NodeJs
浅析 NodeJs 的几种文件路径
Jun 07 #NodeJs
nodejs mysql 实现分页的方法
Jun 06 #NodeJs
nodejs利用ajax实现网页无刷新上传图片实例代码
Jun 06 #NodeJs
You might like
PHP查询MySQL大量数据的时候内存占用分析
2011/07/22 PHP
php实现事件监听与触发的方法
2014/11/21 PHP
PHP中常见的缓存技术实例分析
2015/09/23 PHP
CI框架(CodeIgniter)实现的导入、导出数据操作示例
2018/05/24 PHP
JS操作XML中DTD介绍及使用方法分析
2019/07/04 PHP
Javascript的IE和Firefox兼容性汇编
2006/07/01 Javascript
asp 取文本框名称代码
2008/12/02 Javascript
Jquery如何实现点击时高亮显示代码
2014/01/22 Javascript
html的DOM中document对象anchors集合用法实例
2015/01/21 Javascript
高性能JavaScript DOM编程(1)
2015/08/11 Javascript
JS实现弹性菜单效果代码
2015/09/07 Javascript
node.js入门实例helloworld详解
2015/12/23 Javascript
JS小数运算出现多为小数问题的解决方法
2016/06/02 Javascript
JavaScript中双向数据绑定详解
2017/05/03 Javascript
node.js使用express框架进行文件上传详解
2019/03/03 Javascript
常见的浏览器存储方式(cookie、localStorage、sessionStorage)
2019/05/07 Javascript
通过JQuery,JQueryUI和Jsplumb实现拖拽模块
2019/06/18 jQuery
layui table设置某一行的字体颜色方法
2019/09/05 Javascript
Node.js中console.log()输出彩色字体的方法示例
2019/12/01 Javascript
iSlider手机端图片滑动切换插件使用详解
2019/12/24 Javascript
uni-app微信小程序登录授权的实现
2020/05/22 Javascript
[01:20:47]DOTA2-DPC中国联赛 正赛 Ehome vs Magma BO3 第一场 1月19日
2021/03/11 DOTA
python正则表达式判断字符串是否是全部小写示例
2013/12/25 Python
python文件写入write()的操作
2019/05/14 Python
利用python开发app实战的方法
2019/07/09 Python
Python Opencv提取图片中某种颜色组成的图形的方法
2019/09/19 Python
解决pyCharm中 module 调用失败的问题
2020/02/12 Python
利用CSS3实现炫酷的飞机起飞动画
2016/09/17 HTML / CSS
交通事故赔偿协议书
2014/04/15 职场文书
高中生班主任评语
2014/04/25 职场文书
德能勤绩廉个人总结
2015/02/14 职场文书
2015年教研员工作总结
2015/05/26 职场文书
入党积极分子党支部意见
2015/06/02 职场文书
24句精辟的现实社会语录,句句扎心,道尽人性
2019/08/29 职场文书
HTML+CSS 实现顶部导航栏菜单制作
2021/06/03 HTML / CSS
Mysql将字符串按照指定字符分割的正确方法
2022/05/30 MySQL