You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functioncomputedExpression(obj,expression){constmethodBody=`return (${expression})`;constfuncString=obj ? `with(_$o){ ${methodBody} }` : methodBody;constfunc=newFunction('_$o','_$f',funcString);try{constresult=func(obj,processFilter);return(result===undefined||result===null) ? '' : result;}catch(e){// only catch the not defined errorif(e.message.indexOf('is not defined')>=0){return'';}else{throwe;}}}
但是其实创建方法实例,是完全可以在构建 ast 阶段就准备好的,而渲染阶段,就只需要执行已经准备好的 render function 即可。
前言
在上一篇文章中讲了怎么来实现一个模板引擎,而写完上一篇文章的时候,我的模板引擎也确实是造出来了,不过起初的实现还是比较简陋,就想着做一下性能优化,让自己的轮子,真正能成为可用的组件,而不仅仅是一个 demo。于是就有了这篇文章。
工具
在做组件优化的时候,总不可能自己觉得那样写会提升性能就那样写了,很多时候,瞎尝试可能会带来反效果,所以我们需要一个工具来验证自己的优化是否有效,业界最常用的就是 benchmark.js 了,因此,我也是用 benchmark 来做验证。
除了 benchmark 之外,我们最好还需要一个用来对比的东西,才知道要优化到什么程度才可以。而我的组件的语法是参考 nunjucks 做的,因此我就理所当然的选择了 nunjucks 来做对比了。
优化之前
在做优化之前,我先写了几个 benchmark 来测一下。
简直全方面被吊打。简单说一下这几个 benchmark 的测试例子是怎样的:
renderExtend
是测试有 extend 其他模板文件的测试例子,renderNormal
是渲染一段比较多嵌套的模板,renderSimple
是渲染一段非常简单,只有变量的模板。具体可以看 https://github.com/whxaxes/mus/tree/master/benchmark
优化实现
1. 能在 ast 阶段做的事,尽量在 ast 阶段做好
做模板渲染之前,都会先生成 ast 并且缓存起来,从而将一切准备工作准备好,尽量减少渲染时候的计算量,从而提升性能。
在此前的实现中。如果有看过上一篇文章的人应该有印象,在进行变量渲染的时候,会把表达式用方法字符串包装起来,并且创建一个方法实例,但是这个行为是在渲染阶段做的。也就是以下这段:
但是其实创建方法实例,是完全可以在构建 ast 阶段就准备好的,而渲染阶段,就只需要执行已经准备好的 render function 即可。
上面贴的 benchmark 结果其实是已经做了这个优化的了,在做这个优化之前,
renderNormal
只有 7000ops/sec 而已。2. path.resolve
刚开始,上面的 benchmark 中有一点让我特别疑惑,就是
renderSimple
的差距,只是一个变量渲染而已,怎么会差那么多,经过排查,发现代码中在读取模板文件的时候,每次都会进行path.resolve
来获取文件的绝对路径。于是立马对该操作进行了缓存。跑分立马就上去了。3. for 循环的优化。
在此前的实现中是这样的:
注意到每个 for 循环中都会重新做一次对象的浅拷贝,而其实完全没必要,因为在每个 for 循环中需要的对象都是类似的,因此只需要做一个浅拷贝即可。就改成了:
4. 对表达式进行预处理
此前的实现中,无论什么样的表达式,都一股脑,直接拼成方法来处理,而且此前的都是用 with 来包裹的,而被 with 包裹的代码,在 js 引擎解析的时候是没法做优化的,执行效率特别慢。
因此可以在构建 AST 阶段,对表达式做预处理:
{{ test }}
或者{{ test.value }}
之类的,就不需要用 with 包裹。直接拼成{{ _$o.test }}
,然后再创建 function。5. filter 的优化
在第4点中,我会对表达式做一个类型判断,但是还不够,按照此前实现的 filter 的逻辑,有 filter 的表达式,会被组装成
_$f('nl2br')(test)
的格式,一旦被组装后,到第四点中的表达式判断的时候,就会被认为是比较复杂的类型从而选择使用 with 来组合渲染方法。所以这个也是可以优化的点。然后就把 filter 的处理部分改成:把 filter 的 function string 分为左半边以及右半边来进行收集,在做完类型检查之后,再把 filter 组合起来。这样的话,filter 就不影响类型检查了。
除了以上几个,还有将所有的 for 循环改成了 while 循环,经过一系列优化后再次跑 benchmark:
在已有的测试例子中,分数都超过 nunjucks 。也算是优化成功了。
写本文更多是记录一下自己的优化过程。可能没啥干货,有兴趣的看看,没兴趣的也请勿喷。
最后再贴上项目地址:https://github.com/whxaxes/mus
The text was updated successfully, but these errors were encountered: