html模板引擎的作用,如何用javascript实现一个模板引擎

模板引擎简介

模板引擎是html渲染必不可少的工具,前端开发的同学经历了从最原始的字符串拼接、用数组push/join,发展到封装简单的string.format函数,再到功能更加强大的模板引擎,可以在模板中内嵌处理逻辑。

传统的页面开发语言技术asp.net,php,jsp都内置了模板引擎,javascrip常见的模板引擎有jquery的tmpl插件,underscore的template函数,ejs、jade、artTemplate等,以ejs为例,模板语法如下:

复制代码

模板中可以直接访问绑定的数据源变量,并且可以支持任意的javascript代码片段,如if,for循环等

原理

我们从需求出发,想一想把上面的模板字符串中的变量替换成数据源中的变量值,并支持嵌入的if等逻辑,需要怎样实现?能支持内嵌js代码执行,那肯定是要使用eval来编译的,只要把模板看成两部分,一部分是在百分号以内的,使用eval执行;另一部分是在百分号以外的,原样输出; 最后顺序拼接成一个完整的字符串就可以了。

正则表达式分割字符串

首先要把模板,按照%分隔进行拆分成多个代码片段,并按顺序存入到数组中

分割模板字符串可以用String对象的split方法,很多人不知道split方法是可以支持正则表达式的,因为我们的分隔符有两个,所以要用正则表达式,可以同时匹配这两个字符,代码就只有一行:

var arr=str.split(/(\)/gm);

复制代码

拆分后的代码片段生成一个数组,我们遍历一下这个数组,将代码片段分成3种类型

javascript代码

html片段(原样输出的字符)

用输出的变量值

只要按顺序拼接生成的字符串代码,最终编译执行字符串代码就可以了

编译字符串的方法

1. eval

js最常见的编译字符串的方法当然是eval,可以直接在当前函数作用域将一个字符串编译成可执行代码,有一条代码规范叫做don't be eval,这是因为使用eval是用代码生成代码,会让代码更加难以看懂,并且会有一些性能损耗。

2. new Function

与eval功能相似,不同的是他会将传入的字符串代码编辑后生成一个function,而且可以生成函数的参数,更加方便调用,封装性隔离性更好。调用形式:new Function(args,functionBody),为了更好的可读性,我们选择new Function

3. setTimeout/setInterval

setTimeout/setInterval 的第一个参数,可以传入一个函数,这是最常规的用法,也可以传入一个字符串,使用字符串的时候自动调用eval执行,这个用法不太常见。

作用域问题

模板中肯定需要一些动态变量,就是所谓的数据源,模板编译后是生成独立函数的,作用域是隔离的,而数据源是在业务代码中,有独立的作用域,不能直接访问,那么怎么能把两个不同的作用域连接起来呢,with,只有with能做到,将指定的对象插入到当前作用域链的顶端。

完整代码实现

function render(tpl,data){

tpl=tpl.replace(/(\r|\n)/ig,"");

var arr=tpl.split(/(\)/gm);//拆分模板

var funcBody=["with(this){\r\nvar result=[];"];

var item,codeType;

codeType=0;

for(var i=0;i

item=arr[i];

//将代码片段分为3类

if(item=="

codeType=1;

continue;

}else if(item=="

codeType=2;

continue;

}else if(item=="%>"){

codeType=0;

continue;

}

//为3类代码片段生成最终可被eval的函数体

if(codeType==0){ //字符

funcBody.push("result.push(\"");

funcBody.push(item);

funcBody.push("\");\r\n");

}else if(codeType==1){ //代码

funcBody.push(item);

funcBody.push("\r\n");

}else if (codeType==2){ //代码输出

funcBody.push("result.push(");

funcBody.push(item);

funcBody.push(");\r\n");

}

}

funcBody.push("return result.join('')\r\n}");

var template_func=new Function(["renderData"],funcBody.join(""));

return template_func.apply(data,[data]);

}

复制代码

测试一下

var html=render(""

+"

+" ",

{list:["javascript","css","node.js"]})

document.write(html)

复制代码

完美运行

7d48b2908e3e53eccad183f7f6c4b63e.png

我们来看一下最终编译后生成的函数是什么样子的,如下图:

647133252a80aaae19b39dba59be869c.png

由于生成的函数是用apply调用的,template_inner.apply(data,[data]),所以函数内部的this指向传入的数据源(data变量),因此可以在模板中直接使用传入的数据源对象

模板引擎与ES6模板字符串对比

ES6新增了模板字符串功能,不同于普通字符串的单引号和双引号,模板字符用`符号定义,在模板字符中直接可以通过${变量名}访问当前作用域内的变量并直接输出该变量的值,并且在js文件中定义大段的html字符串时,一般是把html片段粘帖进来,包含很多换行符,而模板字符串可以直接兼容换行符,使用起来非常方便。上文中的模板,用ES6的模板字符串来实现,代码也非常精简,如下:

var user={name:"windy"};

var str=`

${user.name}

`

复制代码

ES6模板字符串与普通的模板引擎相比,最大区别在于开发流程的不同,业务逻辑是在js中实现的,模板只实现纯净的变量替换功能,代码与逻辑分离,比较规范易用,可维护也较好,而普通的模板引擎不仅实现了变量替换,还可以内嵌js逻辑代码,更加灵活和强大。

最后,推荐一下个人的开源项目,内置了模板引擎的node.js web开发框架,webcontext:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个非侵入式、不会破坏原来静态页面结构、可被浏览器正确显示的、格式良好的前端HTML模板引擎。彻底实现前后端分离,让后端专注业务的处理。 传统MVC开发模式,V层使用服务器端渲染。美工设计好静态HTML文件,交给后端工程师,需要转换成Jsp、Freemarker、Velocity等动态模板文件。这种模式有几个缺点 1、动态模板文件不能被浏览器解释、必须要运行在服务器中才能显示出效果 2、动态效果和静态效果分别存在不同文件,美工和后端工程师需要分别维护各自页面文件,其中一方需要修改页面,都需要通知另一方进行修改 3、页面数据不能分块加载、获取跨域数据比较麻烦 domTemplate.js 模板引擎是通过在标签中添加自定义属性,实现动态模板功能,当没有引入domTemplate脚本, 则自定义标签属性不会被浏览器解析,不会破坏原有静态效果,当引入domTemplate脚本,模板引擎回去解析这些标签属性, 并加载数据进行动态渲染。 下图:对比服务器页面渲染和使用domTemplate前端引擎开发流程 服务器端模板解析 domTemplate前端解析 用法 导入jquery.js或者zepto.js和domTemplate.js $(function () {  $.domTemplate.init(options); //可以通过selector指定根节点,默认根节点是body,表示从body开始,渲染整个页面  }); 或者解析某一个html片段。 $('selector').domTemplate(options); //渲染数据是通过h-model 自动去获取数据,也可以通过data指定全局数据 if条件标签 <div> <p h-if="{user.id==50}" h-text="用户ID等于50">xxx</p> <p>其他内容</p> <div> switch条件标签 <p h-switch="{user.id}"> <input type="text" h-case="20" h-val="{user.email}"/> <input type="text" h-case="60" h-val="拉拉"/> <input type="text" h-case="*" h-val="丽丽"/>  </p> each遍历标签 <p>遍历List例子</p> <ul> <li h-each= "user,userStat : {users}" h-text="{userStat.index 1}-{user.email}"> 李小璐</li> </ul> 自定义标签 $.domTemplate.registerTag('tagName',function(ctx,name,exp){ }); //tagName 是自定义标签名称,用时要加上前缀,如定义'test'标签,用时h-test="" 标签:domTemplate 分享 window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "2", "bdMiniList": [], "bdPic": "", "bdStyle": "1", "bdSize": "24" }, "share": {} }; with (document)0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' ~(-new Date() / 36e5)];\r\n \r\n \r\n \r\n \r\n \u8f6f\u4ef6\u9996\u9875\r\n \u8f6f\u4ef6\u4e0b\u8f7d\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\nwindow.changyan.api.config({\r\nappid: 'cysXjLKDf', conf: 'prod_33c27aefa42004c9b2c12a759c851039' });

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值