Juicer – 一个 JavaScript 模板引擎的实现和优化

article/2025/11/10 7:51:04

说明

本文转载自:淘系前端团队:Juicer – 一个 JavaScript 模板引擎的实现和优化

介绍

让我们从一段代码说起,假设有一段这样的 JSON 数据:

var json = {name: '流火',blog: 'ued.taobao.org'
};

我们需要根据这段 JSON 生成这样的 HTML 代码:

流火 (blog: ued.taobao.org)

传统的 JavaScript 代码一定是这个样子:

var html;
html = '' + json.name + ' (blog: ' + json.blog + ')';

不言而喻,这样的代码混杂了 HTML 结构和代码逻辑,而且代码不具可读性,不便于后期维护,于是便有了这样一个函数:

// " g "是全局即全部字符串,而如果想要多行匹配(有多行效果)就要再加" m ",如果想要忽略大小写再加” i “。/igm
function sub(str, data) {return str.replace(/{(.*?)}/igm, function($, $1) {return data[$1] ? data[$1] : $;});
}

有了这个函数,我们拼接字符串的工作就可以简化为:

var tpl = '{name} (blog: {blog})';
var html = sub(tpl, json);

在这里插入图片描述

看到这里,不用我多说,我想通过这个例子直观的展现出前端模板引擎的好处所在,这么做能够完全剥离 HTML 和代码逻辑,便于多人协作和后期的代码维护。当然,当我们的业务逻辑需要对数据源进行循环遍历,if 判断等的时候,这个简明的函数很显然并不能满足我们的需求,于是便有了如今这市面上众多的模板引擎,诸如 Mustache,jQuery tmpl,Kissy template,ejs,doT,nTenjin 等等。

“如无必要,勿增实体。”

这是著名的奥卡姆剃须刀法则,简单的说就是避免重复造轮子。那么就会有童鞋质疑,既然已然有这么多现成的东西可用,为什么还要重新打造一个呢?

我个人认为一个完善的模板引擎应该兼顾这几点:

  • 语法简明
  • 执行效率高
  • 安全性
  • 错误处理机制
  • 多语言通用性

在这里插入图片描述

而市面上现有的模板引擎没有做到兼顾以上几点,比如 Mustache 支持多种语言,通用性不错,不过性能稍差,而且语法不支持高级特性,例如遍历的时候无法做 if 判断,也无法获得 index 索引值,jQuery tmpl 依赖 jQuery,缺乏可移植性,Kissy template 虽然依赖 Kissy, 不过性能和语法都值得推荐,doT/nTenjin 性能和灵活性都很不错,但是语法需要用原生的js来写,写好的模板代码可读性稍差。

鱼和熊掌不可兼得,语法的处理,安全性的输出过滤和错误处理机制的引入在一定程度上都会或多或少降低模板引擎的性能,因此就需要我们权衡。Juicer 在实现上首先将性能看做第一个重要的指标,毕竟性能好坏直接影响用户的感知,同时兼顾了安全性和错误处理机制(即便这样会导致性能的略微下降)。

首先来看下 jsperf上同几个主流模板引擎的性能对比。

在这里插入图片描述

在这里插入图片描述

可以看到,性能上比传统模板引擎均有提升,下边的介绍主要从语法、安全性和错误处理,以及如何使用这几个方面介绍下 Juicer.

语法

  • 循环 {@each}…{@/each}
  • 判断 {@if}…{@else if}…{@else}…{@/if}
  • 变量(支持函数)${varname|function}
  • 注释 {% raw %} {# comment here}{% endraw %}

详细的语法请参考 Juicer Docs.

安全性

安全性,简单地说就是对输出数据在输出前进行一次转义过滤,避免 XSS 这样的脚本注入攻击,简单扫下盲,举个 XSS 的例子。

var json = {output: 'alert("XSS");'
};

如果 JSON 数据是第三方接口返回或者含有用户输入(像 BBS、评价)的内容,我们如果赤裸裸的将 output 写到页面上就会执行恶意的js代码,所以 Juicer 默认是对数据输出做了安全转义的,当然如果不想被转义,可以使用 $${varname}

juicer.to_html('${output}',json); 
//输出:<script>alert("XSS");</script>juicer.to_html('$${output}',json); 
//输出:<script>alert("XSS");</script>

错误处理

如果没有错误处理,当模板引擎编译(Compile)或者渲染(Render)出错时候就会引起后续js代码停止执行,可想而知,如果因为一个逗号或者 JSON 数据的偶发错误导致整个页面挂掉,是我们不能接受的。但是 Juicer 在遇到这些错误的时候不会影响后续代码的执行,只会在控制台打出一句警告(Warn)告知开发者模板解析出现错误。

juicer.to_html('${varname,,,,,,,}', json);
alert('hello, juicer!');

执行上边的代码就会看到控制台打出的 Juicer Compile Exception: Unexpected token ,,但是不会因为错误导致后续的 alert 被阻塞掉。

实现原理

在这里插入图片描述

Juicer 对一个模板的编译和渲染的过程主要有以下几个步骤:

  • 对模板代码进行语法分析
  • 分析后生成原生的 JavaScript 代码字符串
  • 将生成的代码转为可重用的 Function(Compiled Template)
var json = {list: [{ name: 'benben' },{ name: 'liuhuo' }]
};juicer.set('errorhandling', false); // pre-set optionvar tpl = '{@each list as value,key}$${value.name}{@/each}';
var compiled_tpl = juicer(tpl);

我们通过 compiled_tpl.render.toString() 看下编译后的代码:

function anonymous(data) {var data = data || {};var out = '';out += '';for (var i0 = 0, l = data.list.length; i0 < l; i0++) {var value = data.list[i0];var key = i0;out += '';out += ((value.name));out += '';}out += '';return out;
}

是不是已经明白了 Juicer 的原理?这个编译后的函数就会每次帮我们完成从数据到 HTML 代码的拼装操作。

这里有几点优化的地方值得分享下:

  • using += instead of array.push
  • avoid using with {}
  • cache the compiled template (function)

这几点优化在大数据量循环渲染时候性能提升显著,不过正因为放弃了 with{} 语句,所以 Juicer 会在编译函数之前对模板进行词法分析,将用到的变量实现声明,这样就能避免 JSON 数据外层必须指定 data. 前缀,如果你觉得这点性能的提升不重要,也可以在 options 中指定 loose: false(禁用松散模式),这样就可以不省去 data. 前缀,这样做的好处就是性能会更好一些。

最后介绍下 Options 配置项,左侧为参数默认值。

{cache: true/false,loose: true/false,strip: true/false,errorhandling: true/false
}

cache 默认为 true,即同一个模板编译后是否被 Juicer 缓存,也就是说如果缓存开启的情况下,同一个模板第一次编译后,为了缩短耗时提升性能,后续不会再次执行编译的操作而是直接从缓存中去取编译好的模板函数。

Juicer 的 API

Juicer 有两种使用方法,一种是通过

juicer(tpl, data);
// 或者
juicer.to_html(tpl, data);

直接根据提供的数据将模板转为 HTML 代码,另一种是通过 compile 方法先将模板编译好,在需要的时候再对模板进行数据的 Render 操作:

var compiled_tpl=juicer(tpl);compiled_tpl.render(data);
// 或者
var compiled_tpl=juicer.compile(tpl);
compiled_tpl.render(data);

最后附上 Juicer 的项目地址,上边有详细的文档和 Demo 代码。

地址:Juicer


http://chatgpt.dhexx.cn/article/NXVB6IEC.shtml

相关文章

模板引擎?看这一篇就懂了

概念 模板引擎可以让&#xff08;网站&#xff09;程序实现界面与数据分离&#xff0c;业务代码与逻辑代码的分离&#xff0c;这就大大提升了开发效率&#xff0c;良好的设计也使得代码重用变得更加容易。 光看生硬的概念没有用&#xff0c;接下来我来给大家详细解释&#xff0…

前端模板引擎 artTemplate

artTemplate是新一代 javascript 模板引擎 特性&#xff1a;性能卓越&#xff0c;执行速度通常是 Mustache 与 tmpl 的 20 多倍&#xff08;性能测试&#xff09; 支持运行时调试&#xff0c;可精确定位异常模板所在语句&#xff08;演示&#xff09; 对 NodeJS Express 友好…

js模板引擎渲染html,JavaScript模板引擎 art-template.js 的使用

一、基本使用 1、引入模板引擎(版本为4.13.2) 2、编写模板和渲染的容器 {{title}} 3、获取模板并往模板里插入数据 var data { title: "hello world", desc: " 这是一段描述" }; var html template("test-template",data); 4、把模板渲染到页面…

前端开发--art-template模板引擎及实现原理

#博学谷IT学习技术支持# 目录 简介 安装 使用步骤 ​编辑 art-template标准语法 值输出 原文输出 条件输出 循环输出 过滤器 模板引擎的实现原理 1.基本语法 2.分组 3.字符串的replace函数 4.多次replace 5.使用while循环replace 6.replace替换为真值 实现简…

template.js——前端模板引擎

1. 什么情况下推荐使用template.js (1)在你的页面布局中存在一样的模块,可以提出公共的模块,便于维护 注:此处用到循环来减少工作量 对应代

jquery template.js前端模板引擎

1、加载模板引擎js&#xff08;jquery.tmpl.js&#xff09; 可下载链接&#xff1a;https://github.com/BorisMoore/jquery-tmpl 2、直接上代码 <script type"text/javascript" src"${ctxStatic}/web/js/jquery.tmpl.js"></script> <!--…

java 前端模板_前端模板引擎入门

模板引擎 模板引擎 起到 数据和视图分离的作用&#xff0c; 模板对应视图&#xff0c; 关注如何展示数据&#xff0c; 在模板外头准备的数据&#xff0c; 关注那些数据可以被展示。 后端模板引擎 freemarker 如下介绍&#xff0c; java后台的模板引擎&#xff0c; freemark介绍…

art-template前端模板引擎

目录 内容介绍一、使用方法二、主要API1、原文输出2、逻辑判断3、循环语句 三、代码四、页面显示五、其他1、pre标签2、code标签 内容介绍 今天我们要了解的是一款高性能的 art-template 前端模板引擎 —— template-web.js。 一、使用方法 定义容器元素通过type"text/…

前端模板引擎——handlebars

目录 一、代码二、页面显示 一、代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>handlebars 前…

ESLINT错误提示 no-unused-vars

ESLINT错误提示 no-unused-vars 使用ESLINT时遇到如下错误的提示 查阅资料得知&#xff0c;是需要使用插件 https://github.com/typescript-eslint/typescript-eslint/issues/941 全局按照 eslint-plugin-react npm install eslint-plugin-react -g 修改eslint的配置项 "e…

Jmeter书中不会教你的(2)——vars.get和${}引用区别

在上一篇中&#xff0c;我们了解了在beanshell中可以利用vars获取和设置新的变量&#xff0c;也知道了如何用log去查看日志&#xff0c;以及两种获取变量值的方法。没看过的可以点这里Jmeter书本中不会教你的&#xff08;1&#xff09;——beanshell用来获取变量和设置变量的va…

【Python报错-01】解决matplotlib在Pycharm中运行报错:vars()参数必须有__dict__属性

1 运行错误 &#xff08;1&#xff09;程序的原代码如下图所示&#xff1a; import matplotlib.pyplot as plt # 是可视化绘图工具 …… #省略了计算SSE的代码 plt.plot(range(1, 11), SSE) plt.xlabel("聚类数k", fontsize12) plt.ylabel("误差平方和SSE&…

Jmeter书中不会教你的(1)——beanshell用来获取变量和设置变量的vars

这几年Jmeter用的相对比较多&#xff0c;自己每次在做项目时也常会去翻看以前遇到的问题&#xff0c;记录的一些技巧&#xff0c;解决方案&#xff0c;也一直考虑把它整理出来&#xff0c;一来方便自己查找&#xff0c;二来也希望同行可以互相切磋。我写的这些文章不是针对Jmet…

eslint 配置no-unused-vars规则,TypeScript接口抛出“ no-unused-vars”报错

1.项目使用了typescript&#xff0c;并在.eslintrc.js中使用该配置&#xff0c;type类型接口形参检测报错 no-unused-vars: 检测未使用的变量&#xff0c;函数和函数的参数 type类型接口如下&#xff1a; 报错如下&#xff1a; 2.解决 将 “no-unused-vars” 替换成 “typesc…

mmdetection报错 TypeError: vars() argument must have __dict__ attribute

下载官方代码&#xff0c;安装好环境后第一个demo就报错 报错1、error: the following arguments are required: img, config, checkpoint 添加-- 报错2、manager_pyplot_show vars(manager_class).get("pyplot_show")TypeError: vars() argument must have __dict_…

Pycharm使用matplotlib报错:TypeError: vars() argument must have __dict__ attribute 解决方法

Pycharm使用matplotlib绘图时报错 问题描述 TypeError: vars() argument must have __dict__ attribute源程序&#xff1a; # -*- encoding: utf-8 -*-File : MaLearnTest01_1.py Time : 2023/03/03 09:39:05 Author : seveN1foR Version : 1.0 Contact : s…

JMeter常用内置对象:vars、ctx、prev

在前文 Beanshell Sampler 与 Beanshell 断言 中&#xff0c;初步阐述了JMeter beanshell的使用&#xff0c;接下来归集整理了JMeter beanshell 中常用的内置对象及其使用。 注&#xff1a;示例使用JMeter版本为5.1 1 vars 如 API 文档 所言&#xff0c;这是定义变量的类&am…

jmeter内置变量 vars 和props使用详解

vars和props都是jmeter的内置变量&#xff0c;且本质都是Map类型 用python语言来理解的话&#xff0c;就是字典格式 最主要两点的区别&#xff1a; vars 只能在当前线程组内使用&#xff0c;props 可以跨线程组使用 vars 只能保持String 或者Objec&#xff0c; props 是 Hash…

Ansible中vars(变量)定义

1、变量使用的原因 playbook的编写是使用yml的语法&#xff0c;虽然该语法规则较为简单&#xff0c;但是&#xff0c;同其他语法相同该语法也有变量、循环等机制的使用。变量的使用就是为了提高我们所写剧本的复用性&#xff08;当某个参数更改时&#xff0c;直接更改变量的赋…

vue3解决no-unused-vars报错

vue3解决no-unused-vars 前言添加配置 前言 在上一节&#xff0c;针对 vue3配置了ESLint&#xff0c;此时&#xff0c;App.vue 和 HelloWorld.vue 报了一个 no-unused-vars 的错误。 添加配置 vue3 是尤大写的&#xff0c;参考下尤大的配置&#xff1a;.eslintrc.js {no-u…