详解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的10个性能优化技巧
Jul 15 NodeJs
Express与NodeJs创建服务器的两种方法
Feb 06 NodeJs
nodejs入门教程四:URL相关模块用法分析
Apr 24 NodeJs
windows系统下更新nodejs版本的方案
Nov 24 NodeJs
Nodejs连接mysql并实现增、删、改、查操作的方法详解
Jan 04 NodeJs
nodejs中Express与Koa2对比分析
Feb 06 NodeJs
详解nodejs解压版安装和配置(带有搭建前端项目脚手架)
Dec 06 NodeJs
PHPStorm中如何对nodejs项目进行单元测试详解
Feb 28 NodeJs
NodeJS读取分析Nginx错误日志的方法
May 14 NodeJs
Nodejs监听日志文件的变化的过程解析
Aug 04 NodeJs
浅谈使用nodejs搭建web服务器的过程
Jul 20 NodeJs
在nodejs中创建child process的方法
Jan 26 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 adodb连接不同数据库
2009/03/19 PHP
PHP中使用file_get_contents post数据代码例子
2015/02/13 PHP
关于laravel-admin ueditor 集成并解决刷新的问题
2019/10/21 PHP
JavaScript实现动态增加文件域表单
2009/02/12 Javascript
Javascript实现的鼠标经过时播放声音
2010/05/18 Javascript
基于jQuery的动态表格插件
2011/03/28 Javascript
Javascript 闭包引起的IE内存泄露分析
2012/05/23 Javascript
javascript中call,apply,bind的用法对比分析
2015/02/12 Javascript
JS实现表单中checkbox对勾选中增加边框显示效果
2015/08/21 Javascript
JavaScript操作select元素和option的实例代码
2016/01/29 Javascript
JavaScript实现相册弹窗功能(zepto.js)
2016/06/21 Javascript
ES6中Math对象的部分扩展
2017/02/20 Javascript
详解vue-resource promise兼容性问题
2017/06/20 Javascript
基于Vuejs和Element的注册插件的编写方法
2017/07/03 Javascript
JS字符串去除连续或全部重复字符的实例
2018/03/08 Javascript
Vue使用json-server进行后端数据模拟功能
2018/04/17 Javascript
JavaScript实现构造json数组的方法分析
2018/08/17 Javascript
Nodejs实现多文件夹文件同步
2018/10/17 NodeJs
vue store之状态管理模式的详细介绍
2019/06/13 Javascript
JS async 函数的含义和用法实例总结
2020/04/08 Javascript
vue实现淘宝购物车功能
2020/04/20 Javascript
jQuery实现简单日历效果
2020/07/05 jQuery
Openlayers实现图形绘制
2020/09/28 Javascript
python对文件目录的操作方法实例总结
2019/06/24 Python
Python基础之高级变量类型实例详解
2020/01/03 Python
python GUI库图形界面开发之PyQt5打印控件QPrinter详细使用方法与实例
2020/02/28 Python
scrapy与selenium结合爬取数据(爬取动态网站)的示例代码
2020/09/28 Python
HTML利用九宫格原理进行网页布局
2020/03/13 HTML / CSS
瑜伽服装品牌:露露柠檬(lululemon athletica)
2017/06/04 全球购物
英国高街电视:High Street TV
2018/05/22 全球购物
日本航空官方网站:JAL
2019/06/19 全球购物
测绘工程专业个人自我评价
2013/12/01 职场文书
教学改革实施方案
2014/03/31 职场文书
初任公务员培训心得体会
2016/01/08 职场文书
python简单验证码识别的实现过程
2021/06/20 Python
SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户 WIN-8IVSNAQS8T7\Administrator 的信息
2021/06/30 SQL Server