YayaTemplate
记得8个月前,我第一次在百度UED的网站上看到一篇关于前端模版的介绍。第一次并未一下就被吸引过去,而是辗转几日在项目前端复杂度逐渐增加的一个偶然机会,让我想起了一周前看到的“前端模版”这4个字眼。
那篇Blog上介绍的正是YayaTemplate这个由贵大师兄@ 所创作的优秀模版引擎,于是也会以它作为模版引擎的主要介绍对象。
yayaTemplate的github源码:
yaya等许多模版系统其核心思想即:先向引擎传入模版字符串,然后yaya通过String.prototype.replace,转换为javascript编译器可以识别代码,利用Function将字符串转换为function对象的能力,将生成的匿名函数作为该编译过程的返回值。
作为一个轻量级的前端模版系统,以其不亚于artTemplate的速度上yaya做得相当出色,并且其源代码也足够轻,添加了注释的源代码也才122行。
具体的代码可能要等到翻译部分完成后,才能向大家贡献了,不过我想在这里提出我对于模版系统的至今不解的疑惑,或许是我自身的思考过程:
几个疑惑
1、最大的疑惑来自于Function,Function/setTimeout/eval等都具有根据字符串来执行js代码的能力,不过也带来了相当层度的性能问题(调试上由@糖饼哥哥 的artTemplate已得到了解决方案)。我在想是否可以通过向浏览器中添加script标签的方式来动态执行代码呢?下面的代码是我从Closure Library中的base.js摘录的:
其实这段代码来自于KIQ.js,是我对Google Library做了些许的改动。
/* customized EVAL method[by Google Closure Library] */kiQ.globalEval = function (script) { if (kiQ.global.execScript) { kiQ.global.execScript(script); } else if (kiQ.global.eval) { if (kiQ.evalWorksForGlobals_ == null) { kiQ.global.eval("this.__test__=1;"); if (typeof kiQ.global["__test__"] !== "undefined") { delete kiQ.global["__test__"]; kiQ.evalWorksForGlobals_ = true; } else { kiQ.evalWorksForGlobals_ = false; } } if (kiQ.evalWorksForGlobals_) { kiQ.global.eval(script); } else { var doc = kiQ.global.document; var scriptElem = doc.createElement('script'); scriptElem.type = 'text/javascript'; scriptElem.defer = false; scriptElem.appendChild( doc.createTextNode(script) ); doc.body.appendChild(scriptElem); doc.body.removeChild(scriptElem); } } else { kiQ.error("KIQ.globalEval not available"); }}
不过大致思路没有多少出入,19-27行即我上面提到的通过通过嵌入script的方式来执行Javascript脚本,需要注意的是在IE/Opera浏览器下,如果把23-25行改成scriptElem.innerHTML=script,将会报错,因此closure巧妙地用TextNode的方式来向script中填充js代码。
不过即使是Google,也是将这种方法作为eval不被ES环境支持的情况下的备选方案,现在的我所能想到这么做的原因即是:对于作用域的控制,eval上提供了很明确的用法,不过使用后者,我也能通过将要执行的js代码包装在一个function表达式中,然后向function传递作用域。不过对于此,我准备在翻译完Closure对模版系统有更深了解之后,写一个性能比对的测试再下定论。
2、这应该不算疑问了,算是领悟,与上一个问题紧密度很高,在上一篇做动静模版性能比对时,我将动态模版的性能消耗算在了编译期,现在仍然是编译期,不过涵盖了Function/eval函数,这也使得第一个问题最后提到的性能比对更加重要。
3、关于缓存,上面所提到的两个模版系统都提供缓存,但他们的缓存都是基于一个返回字符串的函数,而非一个灵活的DocumentFragment对象。我想说的是,如果在预编译时就将DOM生成的DOM节点转换为一个DocumentFragment的话,不是可以省去很多模版么?比如有时候我们仅仅需要改写一个ul中每个li的一个小元素,要么是将这个需要修改的节点作为参数,写入,要么是新建第一个模版。
还有更好的办法么?答案是有的,将这个模版生成的字符串转换为DocumentFragment,存储在一个Js对象中,在需要时更改,然后插入就行了。
不过为何我们不直接将其直接加入到系统的缓存中呢,直接返回DocumentFragment,也许性能上没有那么大的提升,但DOM在操作XML时永远比String要直观,不是吗?
这三个问题所提供得优化方法,都将在翻译完Closure的相关文档后,加入到我KIQ.js的动态模版库中,也会做出一系列性能比对第一时间分享给大家。
后记:很多时候真的身不由己,上一篇后记说是要在这篇写一些源码分析,不过看完yayaTemplate和artTemplate源码后,感觉我还没有能力在一个时间内将别人优秀的作品做一个现场解剖,不过提提意见总是可以的吧,这不就是"开源"的思想吗!好了,问题已经抛出来了,下周开始正式的翻译工作,相对于原创,会是比较简单的工作了吧。亲们,下周三见咯!