详解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 url验证(url-valid)的使用方法
Nov 18 NodeJs
跟我学Nodejs(三)--- Node.js模块
May 25 NodeJs
nodejs npm包管理的配置方法及常用命令介绍
Jun 05 NodeJs
Nodejs学习笔记之测试驱动
Apr 16 NodeJs
Nodejs多站点切换Htpps协议详解及简单实例
Feb 23 NodeJs
nodejs个人博客开发第六步 数据分页
Apr 12 NodeJs
浅谈NodeJs之数据库异常处理
Oct 25 NodeJs
nodejs 最新版安装npm 的使用详解
Jan 18 NodeJs
NodeJs实现简单的爬虫功能案例分析
Dec 05 NodeJs
详解NodeJS Https HSM双向认证实现
Mar 12 NodeJs
nodejs 递归拷贝、读取目录下所有文件和目录
Jul 18 NodeJs
nodejs实现聊天机器人功能
Sep 19 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
一些操作和快捷键的理解和讨论
2020/03/04 星际争霸
PHP中通过加号合并数组的一个简单方法分享
2011/01/27 PHP
php单例模式的简单实现方法
2016/06/10 PHP
Zend Framework入门教程之Zend_Session会话操作详解
2016/12/08 PHP
Yii2中datetime类的使用
2016/12/17 PHP
PHP获取远程http或ftp文件的md5值的方法
2019/04/15 PHP
Jquery下EasyUI组件中的DataGrid结果集清空方法
2014/01/06 Javascript
Jquery 获取对象的几种方式介绍
2014/01/17 Javascript
javascript实现单击和双击并存的方法
2014/12/13 Javascript
基于JQuery制作可编辑的表格特效
2014/12/23 Javascript
基于jquery实现发送文章到手机的代码
2014/12/26 Javascript
jQuery中:input选择器用法实例
2015/01/03 Javascript
JS显示下拉列表框内全部元素的方法
2015/03/31 Javascript
探寻JavaScript中this指针指向
2016/04/23 Javascript
浅谈JS正则表达式的RegExp对象和括号的使用
2016/07/28 Javascript
BootStrap table删除指定行的注意事项(笔记整理)
2017/02/05 Javascript
微信小程序page的生命周期和音频播放及监听实例详解
2017/04/07 Javascript
详解ES6中的代理模式——Proxy
2018/01/08 Javascript
解决vue中无法动态修改jqgrid组件 url地址的问题
2018/03/01 Javascript
VUE重点问题总结
2018/03/19 Javascript
通过npm或yarn自动生成vue组件的方法示例
2019/02/12 Javascript
[48:21]林俊杰圣堂刺客超神杀戮秀
2014/10/29 DOTA
[40:03]Liquid vs Optic 2018国际邀请赛淘汰赛BO3 第一场 8.21
2018/08/22 DOTA
python 3.7.0 安装配置方法图文教程
2018/08/27 Python
python进程和线程用法知识点总结
2019/05/28 Python
windows下Python安装、使用教程和Notepad++的使用教程
2019/10/06 Python
Python partial函数原理及用法解析
2019/12/11 Python
Python3爬虫中识别图形验证码的实例讲解
2020/07/30 Python
Python+kivy BoxLayout布局示例代码详解
2020/12/28 Python
澳大利亚UGG工厂直销:Australian Ugg Boots
2017/10/14 全球购物
大学生的四年学习自我评价
2013/12/13 职场文书
一岗双责责任书
2014/04/15 职场文书
投标诚信承诺书
2014/05/26 职场文书
报到证办理个人委托书
2014/10/06 职场文书
立项申请报告范本
2015/05/15 职场文书
梳理总结Python开发中需要摒弃的18个坏习惯
2022/01/22 Python