精华 全球最快的JS模板引擎
发布于 9 年前 作者 yangjiePro 32771 次浏览 最后一次编辑是 8 年前 来自 分享

废话不多说,先上测试:

all.jpg

亲测请访问:【在线测试地址】单次结果不一定准确,请多测几次。

tppl 的编译渲染速度是著名的 jQuery 作者 John Resig 开发的 tmpl43 倍!与第二名 artTemplate 也有一倍的差距。

似乎每一个大公司都选择自己开发模板引擎并将其开源,结果就是社区充斥着数不清的引擎,让人眼花缭乱无从选择。随着时间的流逝,越来越多的功能被添加进去,最终让一个强悍的发动机变成了一台臃肿复杂零件生锈的拖拉机。天呐,我就想网页面里插一段 html,你居然要我往每个js文件里再塞进500行代码!

不,事情原本应该更简单。保持代码的简洁高效也意味着让生活更加健康愉悦。

好吧,满脑子装着“封装”或者“模块化”的读者估计有不同的看法。

下面我们来谈谈如何让引擎更加强劲高效。

模板引擎分为两大主要阵营:

  1. 原生语法
    
  2. 自定义模板语法
    

此两种方案各有各的好,自定义语法相比前者的优点在于,看起来和写起来更加规范简洁,更“像”是一种模板。也能更好的配合编译,并且可以避免用户写出“性能不佳”的代码。部分人认为自定义语法对页面设计人员来说更为友好,这就见仁见智了。而缺点就是通常只能自定义 if else 和 for 循环等简单而有限的逻辑结构,不够强大和灵活。

自定义语法的优化方法有随着语法的不同而不同,但通常最终都是将其转换为原生的语言逻辑结构。这里主要讨论原生语法模板引擎的优化。

对于追求性能的模板引擎来说,有两个显而易见的方向:

  1. 编译结果缓存
    
  2. 编译结果静态化
    

缓存很好理解,一次编译多次渲染。通常是保存初步正则替换后的字符串中间值,重复渲染时直接拿来使用。

静态指的是,编译模板字符串生成一个字符串拼接的函数,而不是每次创建函数。渲染操作就相当于一次函数调用,代入变量完成字符串拼接并返回。一般引擎优化到这一步时,渲染方面已经没有太大的差距和进步的空间。大家都成了字符串拼接函数,只能在微小的语法层面做优化。这里说一下,相信从事过前端开发的朋友,都“听说”过一个字符串拼接的“快速方法”:将字符串片段 push 进一个数组,最后再 join 返回,性能比直接采用 + 或者 += 字符串要好。注意,这种方法过时了!在现代浏览器以及Node.js中已经不再成立。通过数组连接字符串只是一个“临时解决方案”,随着各大js编译器的优化和进步,直接采用 + 字符串操作,给了一个编译器在语言底层做出优化的机会,大家还是着眼于未来吧。

渲染差距不大时,编译则还存在很多“水分”可以挤出来。当然,一部分模板引擎集成了诸如文件加载、Ajax等高级功能,对其性能方面有要求太高似乎也不太合理。

一般原生语法模板引擎,都采用类似下面的字符串表示:

		<h1> <%=title %> </h1>
		<% for(var i in content){ %>
			<p>第<%=i%>段:<%=content[i]%></p>
		<%}%>

而编译操作就是将这一段字符串转换成类似下面的函数:

		function(data){
		  var str = "<h1>  "+data.title+"  </h1>";
		  for(var i in data.content){
			str += "<p>第"+i+"段:"+data.content[i]+"</p>";
		  }
		  return str;
		}

通常情况都是改变字符串结构,去掉模板标签,再使用 new Function() 创建函数。

传统传递参数的实现通过遍历数据对象,把对象的名值分离,然后分别把对象成员名称作为new Function的参数名(即变量名),然后使用函数的appley调用方式传给那些参数。

tmpl 则使用了javascript不常用的 with 语句实现。 实现方式很简洁,省去了var这个关键字。tmpl 性能问题就出在 with 上面。javascript 提供的 with 语句,本意是想用来更快捷的访问对象的属性。不幸的是,with语句在语言中的存在,就严重影响了 javascript 引擎的速度,因为它阻止了变量名的词法作用域绑定。

而转换的方法有很多种,一部分采用 split() 截断分析,一部分采用全正则替换,更有强悍的引擎使用词法分析,总之各显神通。

全正则替换的方案只是一长串的 .replace() 链式调用,看起来代码更加美妙,但由于存在中间过渡状态和方法而导致性能不佳。词法分析更不必说,大量的正则拖慢编译速度。编译优化的重点就在,尽量减少中间态,并减少复杂正则表达式的使用。经过实测,split() 截断分析能减少一部分正则,性能更好。

tppl 一开始使用“模板尾标签分割”,即:str.split("%>") 的方式,这与 tmpl 的实现方式不谋而合,上面的字符串被分割为6段,然后为每一段使用一次正则替换:

		var tpls = ["<h1> <%=title", " </h1>","<% for(var i in content){ ", "<p>第<%=i", "段:<%=content[i]", "</p><%}"];

在后来的性能测试中,发现这种实现方式相比其它引擎,并没有太大的提升。看来只能另辟蹊径了。

经过长时间的冥思苦想,终于发现采用“模板头标签分割”的方式,能大大减少分割结果的数量,但是需要修改模板标签:

		<h1> [=:title:] </h1>
		[: for(var i in content){ :]
		  <p>第[=:i:]段:[=:content[i]:]</p>
		[:}:]

方括号 [ 与冒号 : 组成的模板标签相比 <% 能更加清晰的区分html代码与js逻辑代码。通过 .split("[:") 将模板分割为3段:

		var tpls = ["<h1> [=:title:] </h1>", " for(var i in content){ :]<p>第[=:i:]段:[=:content[i]:]</p>", "}:]"];

如此一来正则替换从6次下降到3次,性能提升将近一倍!而且随着代码逻辑结构的不同,性能提升将会更大。

关键的正则表达式:

		var line = "'"+"<p>第[=:i:]段:[=:content[i]:]</p>".replace(/\[\=\:(.*?)\:\]/g, "'+$1+'")+"'";
		// '<p>第'+i+'段:'+content[i]+'</p>'

tppl 的源码托管在 Github 上,地址:https://github.com/jojoin/tppl

如果你还有更好编译优化方法,欢迎讨论!

35 回复

支持一下,库很小,在浏览器端可以用上。

语法略奇葩,另外不知道对浏览器的支持如何,我也不要多的,支持IE9+就行了。

使用compile()函数编译下再测试看看

听上去很美~

  • 一般情况下对模板的编译速度不会有太高要求,楼主是以牺牲模板语法的易用性来提升那么一点编译速度,我觉得没多大必要
  • 一款优秀的模板引擎不仅仅是比拼渲染速度
  • 楼主有考虑过编译时模板语法不正确,渲染过程中出错时如何定位到具体位置么
  • 模板引擎渲染过程中最快的方法就是用加号拼接字符串,实际上诸如artTemplate和juicer这些模板引擎已经几乎做到极限了,我不认为再这么高调地”比谁快“有任何意义
  • 折腾精神可嘉,切勿好大喜功

QQ20141217-1.png QQ20141217-2.png QQ20141217-3.png QQ20141217-4.png QQ20141217-5.png

@leizongmin 我在文章里面已经说过了:“渲染方面已经没有太大的差距和进步的空间,大家都成了字符串拼接函数”。 tppl 的性能优势在编译上,但你贴出来的图只有1次编译。 我不想做一个“大而全”的模板引擎,它的定位只是“在页面里插一段html”,简洁而高效。非常感谢你的关注!

@yangjiePro 再看看我更新的图片,编译速度甩tppl几条街的也不少 ------ 我没别的意思,只是看了标题忍不住进来吐槽几句而已 ^_^

@leizongmin kissy juicer Mustache 这几个有编译缓存: D{48)59HSKX36%7L4J2X.jpg 相同的 TPL 字符串不会被重复编译,导致 Mustache 编译一千次只需要 12 毫秒,因为根本就只编译了一次!

编译快点没啥好处,我可以在系统启动时把所有模板预先编译缓存了……关键还是得看开发和维护效率,不能太奇葩了

总有重复造轮子的,是否能坚持维护,是否能提供良好的API,体积和速度可以根据需求均衡选择类似的库。

帮LZ说两句吧,刚看了看代码,非常小纯代码未压缩还不到1K,兼容性也不错,我觉得这才是最大的卖点。在Node服务器端的模板引擎如果没有streaming都差不多,但在浏览器端也还是需要这样的短小精悍的模板引擎的,手头刚好有项目用的上,自己写总觉的写不好,后端用了jade,前端不想用angular之类的,用这个+Ajax也能搞定数据渲染了。

用doT的飘过

赞一个. 从 React 的角度上来说, 浏览器端替换 HTML 挺快的, 但是 DOM 更新引起的 reflow 会非常消耗计算量. 然后 Famo.us 提出来说, 浏览器原生的 reflow 的很慢的, 如果避免 reflow 全部激活 GPU 可以飞快… 同意老雷说的, 其实模版渲染的速度不是最重要的.

楼主还是值得赞的,细究之下必出奇效。看了文章还了解了不少模板引擎的原理还渲染效率的差异点。

十行代码实现的极简高性能模板引擎 wu.tmpl.js https://github.com/wusfen/wu.tmpl.js test.png 其实速度并不是最重要的,关键是顺手 还要考虑易用,上手快,出错较容易定位。。

关于模板文件的存储 1 用script type=“tpl/text” 写到html文件中,还是 2 用ajax从服务器获取
这两种方式哪种好?

@leizongmin 傲娇

来自酷炫的 CNodeMD

ejs 没有排名??

@yakczh 一般模板都是小片段,放到当前用到的页面下即可,一般没必要增加一次请求

一直在用art-template,语法什么的感觉还不错,性能也很好,LZ那个[:的语法,暂时看着有点怪:)支持一下

同样的字符串,开不开fast,结果不一致,这算不算有问题?

	// 定义了三个参数 tppl(tpl, data, fast)
	var str = '<div>[=: name :]</div>';
	tppl(str, {name: 'cnodejs'})
	// 输出=> "<div>cnodejs</div>"
	tppl(str, {name: 'cnodejs'}, true)
	// 输出=> "<div></div>"

必须用把name改为this.name才是一致结果,这样的设计,反人类啊!

@leizongmin 这脸打的,啪啪响。。

就冲折腾精神顶楼主

一般来说,如果追求执行速度的话,我们都不会直接在浏览器编译模版,而会把要用的模版先编译成js模块。我用underscore或者用其它模版引擎一样可以轻松获得超过楼主模版引擎的执行时性能: leungwensen/template2module

回到顶部