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
我们将一个组件拆分为 .html、.js、.css 三种文件,例如 Panel 组件,包含 panel.html、panel.js、panel.css 三个文件,这样可以将视图、逻辑和样式拆解开来便于维护。为了提升组件灵活性,我们 Panel 中的标题,button 的文案,以及中间 item 的个数、内容等均由配置数据来控制,这样,我们就可以根据配置数据动态渲染组件。这个过程中,为了使数据、事件流向更为清晰,参考 Vue 的设计,我们引入了数据处理中心 data center 的概念,组件需要的数据统一存放在 data center 中。data center 数据改变会触发组件的更新,而这个更新的过程,就是根据不同的数据对视图进行重新渲染。
在模板字符串的 ${} 中可以写任意表达式,但是同样的,对 if / else 判断、循环语句无法处理。
简易模板引擎
很多情况下,我们渲染 HTML 模板时,尤其是渲染 ul 元素时, 一个 for 循环显得尤为必要。那么就需要在上面简单逻辑的基础上加入逻辑处理语句。
例如我们有如下一个模板:
vartemplate=('I hava some menu lists:'+'<% if (lists) { %>'+'<ul>'+'<% for (var index in lists) { %>'+'<li><% lists[i].text %></li>'+'<% } %>'+'</ul>'+'<% } else { %>'+'<p>list is empty</p>'+'<% } %>')
直观的想,我们希望模板能转化成下面的样子:
'I hava some menu lists:'if(lists){'<ul>'for(varindexinlists){'<li>'lists[i].text'</li>'}'</ul>'}else{'<p>list is empty</p>'}
为了得到最后的模板,我们将散在各处的 HTML 片段 push 到一个数组 html 中,最后通过 html.join('') 拼接成最终的模板。
consthtml=[]html.push('I hava some menu lists:')if(lists){html.push('<ul>')for(varindexinlists){html.push('<li>')html.push(lists[i].text)html.push('</li>')}html.push('</ul>')}else{html.push('<p>list is empty</p>')}returnhtml.join('')
作者:崔静
前言
虽然现在有各种前端框架来提高开发效率,但是在某些情况下,原生 JavaScript 实现的组件也是不可或缺的。例如在我们的项目中,需要给业务方提供一个通用的支付组件,但是业务方使用的技术栈可能是 Vue、React 等,甚至是原生的 JavaScript。那么为了实现通用性,同时保证组件的可维护性,实现一个原生 JavaScript 的组件也就显得很有必要了。
下面左图为我们的 Panel 组件的大概样子,右图则为我们项目的大概目录结构:
我们将一个组件拆分为
.html
、.js
、.css
三种文件,例如 Panel 组件,包含 panel.html、panel.js、panel.css 三个文件,这样可以将视图、逻辑和样式拆解开来便于维护。为了提升组件灵活性,我们 Panel 中的标题,button 的文案,以及中间 item 的个数、内容等均由配置数据来控制,这样,我们就可以根据配置数据动态渲染组件。这个过程中,为了使数据、事件流向更为清晰,参考 Vue 的设计,我们引入了数据处理中心 data center 的概念,组件需要的数据统一存放在 data center 中。data center 数据改变会触发组件的更新,而这个更新的过程,就是根据不同的数据对视图进行重新渲染。panel.html 就是我们常说的“字符串模板”,而对其进行解析变成可执行的 JavaScript 代码的过程则是“模板引擎”所做的事情。目前有很多的模板引擎供选择,且一般都提供了丰富的功能。但是在很多情况下,我们可能只是处理一个简单的模板,没有太复杂的逻辑,那么简单的字符串模板已足够我们使用。
几种字符串模板方式和简单原理
主要分为以下几类:
简单粗暴——正则替换
最简单粗暴的方式,直接使用字符串进行正则替换。但是无法处理循环语句和 if / else 判断这些。
a. 定义一个字符串变量的写法,比如用
<%%>
包裹b. 然后通过正则匹配,找出所有的
<%%>
, 对里面的变量进行替换简单优雅——ES6 的模板语法
使用 ES6 语法中的模板字符串,上面的通过正则表达式实现的全局替换,我们可以简单的写成
在模板字符串的
${}
中可以写任意表达式,但是同样的,对 if / else 判断、循环语句无法处理。简易模板引擎
很多情况下,我们渲染 HTML 模板时,尤其是渲染 ul 元素时, 一个 for 循环显得尤为必要。那么就需要在上面简单逻辑的基础上加入逻辑处理语句。
例如我们有如下一个模板:
直观的想,我们希望模板能转化成下面的样子:
为了得到最后的模板,我们将散在各处的 HTML 片段 push 到一个数组
html
中,最后通过html.join('')
拼接成最终的模板。如此,我们就得到了可以执行的 JavaScript 代码。对比一下,容易看出从模板到 JavaScript 代码,经历了几个转换:
<%%>
中如果是逻辑语句(if/else/for/switch/case/break),那么中间的内容直接转成 JavaScript 代码。通过正则表达式/(^( )?(var|if|for|else|switch|case|break|;))(.*)?/g
将要处理的逻辑表达式过滤出来。<% xxx %>
中如果是非逻辑语句,那么我们替换成html.push(xxx)
的语句<%%>
之外的内容,我们替换成html.push(字符串)
到此我们得到了“文本”版本的 JavaScript 代码,利用
new Function
可以将“文本”代码转化为真正的可执行代码。最后还剩一件事——传入参数,执行该函数。
方式一:可以把模板中所有的参数统一封装在一个对象 (data) 中,然后利用 apply 绑定函数的
this
到这个对象。这样在模板中,我们便可通过this.xx
获取到数据。方式二:总是写
this.
会感觉略麻烦。可以把函数包裹在with(obj)
中来运行,然后把模板用到的数据当做 obj 参数传入函数。这样一来,可以像前文例子中的模板写法一样,直接在模板中使用变量。但是需要注意,
with
语法本身是存在一些弊端的。到此我们就得到了一个简单的模板引擎。
在此基础上,可以进行一些包装,拓展一下功能。比如可以增加一个 i18n 多语言处理方法。这样可以把语言的文案从模板中单独抽离出来,在全局进行一次语言设置之后,在后期的渲染中,直接使用即可。
基本思路:对传入模板的数据进行包装,在其中增加一个 $i18n 函数。然后当我们在模板中写
<p><%$i18n("something")%></p>
时,将会被解析为push($i18n("something"))
具体代码如下:
通过
setGlobalContent
方法,设置全局的文案。然后在模板中可以通过<%$i18n("contentKey")%>
来直接使用在我们介绍的方法中使用 '<%%>' 的来包裹逻辑语块和变量,此外还有一种更为常见的方式——使用双大括号
{{}}
,也叫 mustache 标记。在 Vue, Angular 以及微信小程序的模板语法中都使用了这种标记,一般也叫做插值表达式。下面我们来看一个简单的 mustache 语法模板引擎的实现。模板引擎 mustache.js 的原理
有了方法3的基础,我们理解其他的模板引擎原理就稍微容易点了。我们来看一个使用广泛的轻量级模板 mustache 的原理。
简单的例子如下:
模板解析
模板引擎首先要对模板进行解析。mustache 的模板解析大概流程如下:
然后通过遍历
tokens
,将连续的text
类型的数组合并。遍历
tokens
,处理section
类型(即模板中的{{#tag}}{{/tag}}
,{{^tag}}{{/tag}}
)。section
在模板中是成对儿出现的,需要根据section
进行嵌套,最后和我们的模板嵌套类型达到一致。渲染
解析完模板之后,就是进行渲染了:根据传入的数据,得到最终的 HTML 字符串。渲染的大致过程如下:
首先将渲染模板的数据存入一个变量
context
中。由于在模板中,变量是字符串形式表示的,如'name.first'
。在获取的时候首先通过.
来分割得到'name'
和'first'
然后通过trueValue = context['name']['first']
设值。为了提高性能,可以增加一个cache
将该次获取到的结果保存起来,cache['name.first'] = trueValue
以便于下次使用。渲染的核心过程就是遍历
tokens
,获取到类型,和变量 (value
) 的正真的值,然后根据类型、值进行渲染,最后将得到的结果拼接起来,即得到了最终的结果。找到适合的模板引擎
众多模板引擎中,如何锁定哪个是我们所需的呢?下面提供几个可以考虑的方向,希望可以帮助大家来选择:
功能
选择一个工具,最主要的是看它能否满足我们所需。比如,是否支持变量、逻辑表达式,是否支持子模板,是否会对 HTML 标签进行转义等。下面表格仅仅做几个模板引擎的简单对比。
不同模板引擎除了基本功能外,还提供了自己的特有的功能,比如 artTemplate 支持在模板文件上打断点,使用时方便调试,还有一些辅助方法;handlesbars 还提供一个 runtime 的版本,可以对模板进行预编译;ejs 逻辑表达式写法和 JavaScript 相同;等等在此就不一一例举了。
大小
对于一个轻量级组件来说,我们会格外在意组件最终的大小。功能丰富的模板引擎便会意味着体积较大,所以在功能和大小上我们需要进行一定的衡量。artTemplate 和 doT 较小,压缩后仅几 KB,而 handlebars 就较大,4.0.11 版本压缩后依然有 70+KB。
(注:上图部分数据来源于 https://cdnjs.com/ 上 min.js 的大小,部分来源于 git 上大小。大小为非 gzip 的大小)
性能
如果有非常多的频繁 DOM 更新或者需要渲染的 DOM 数量很多,渲染时,我们就需要关注一下模板引擎的性能了。
最后,以我们的项目为例子,我们要实现的组件是一个轻量级的组件(主要为一个浮层界面,两个页面级的全覆盖界面)同时用户的交互也很简单,组件不会进行频繁重新渲染。但是对组件的整体大小会很在意,而且还有一点特殊的是,在组件的文案我们需要支持多语言。所以最终我们选定了上文介绍的第三种方案。
参考文档
The text was updated successfully, but these errors were encountered: