实现模板引擎
2018-02-19 本文已影响0人
阿鲁提尔
字符串替换
Template(模板)
目录
- 字符串拼接
- string format(字符串格式化)
- 模板替换
- 自制模板引擎
- 常见模板引擎介绍
一个简单的需求
用 JS 『渲染』一个歌曲列表
- 数据来自一个数组 songs
- 不能写死在页面里
songs === [
{name: '绅士', url: 'http://music.163.com/xxx', singer: '薛之谦'},
{name: '刚刚好', url: 'http://music.163.com/yyy', singer: '薛之谦'},
...
]
最傻的办法 - 遍历
你必须要能想到一个最傻的办法
- 方案一:拼 HTML 字符串
var songs = [
{name:'刚刚好 ', singer:'薛之谦', url: 'http://music.163.com/xxx'},
{name:'最佳歌手 ', singer:'许嵩', url: 'http://music.163.com/xxx'},
{name:'初学者 ', singer:'薛之谦', url: 'http://music.163.com/xxx'},
{name:'绅士 ', singer:'薛之谦', url: 'http://music.163.com/xxx'},
{name:'我门 ', singer:'陈伟霆', url: 'http://music.163.com/xxx'},
{name:'画风 ', singer:'后弦', url: 'http://music.163.com/xxx'},
{name:'We Are One ', singer:'郁可唯', url: 'http://music.163.com/xxx'}
]
var html = ''
html += '<div class=song-list>'
html += ' <h1>热歌榜</h1>'
html += ' <ol>'
for(var i=0; i<songs.length; i++){
html += '<li>'+ songs[i].name + ' - ' + songs[i].singer +'</li>'
}
html += ' </ol>'
html += '</div>'
document.body.innerHTML = html
缺点:如果在songs数据中添加了js代码,也会执行。存在注入风险。
- 方案二:构造 DOM 对象(也可以用 jQuery)
var songs = [
{name:'刚刚好 ', singer:'薛之谦', url: 'http://music.163.com/xxx'},
{name:'最佳歌手 ', singer:'许嵩', url: 'http://music.163.com/xxx'},
{name:'初学者 ', singer:'薛之谦', url: 'http://music.163.com/xxx'},
{name:'绅士 ', singer:'薛之谦', url: 'http://music.163.com/xxx'},
{name:'我门 ', singer:'陈伟霆', url: 'http://music.163.com/xxx'},
{name:'画风 ', singer:'后弦', url: 'http://music.163.com/xxx'},
{name:'We Are One ', singer:'郁可唯', url: 'http://music.163.com/xxx'}
]
var fragment = document.createDocumentFragment()
//createDocumentFragment翻译是'HTML片段',内存中的DOM(节点),不会放到页面上。
var elDiv = document.createElement('div')
elDiv.className = 'song-list'
//element 就用el开头
//div和elDiv区别:
//div = '<div></div>'
//elDiv = document.createElement('div')
var elH1 = document.createElement('h1')
elH1.appendChild(document.createTextNode('热歌榜'))
var elList = document.createElement('ol')
for(var i=0; i<songs.length; i++){
var li = document.createElement('li')
li.textContent = songs[i].name + ' - ' +songs[i].singer
elList.appendChild(li)
}
//textContent有三种选择:这个是H5的标准API,旧的IE不支持
//innerText = .. ,这个是IE的API,后来其他浏览器也跟进了。支持最广泛,但不标准。
//innerHTML = .. ,支持广泛,也比较标准,问题是可以注入js并且执行。
elDiv.appendChild(elH1)
elDiv.appendChild(elList)
document.body.appendChild(elDiv)
--使用JQ
var $div = $('<div class="song-list"></div>')
var $h1 = $('<h1>热歌榜</h1>')
var $list = $('<ol></ol>')
for(var i=0; i<songs.length; i++){
var $li = $('<li>'+song[i].name+' - '+song[i].singer+'</li>')
$list.append($li)
}
elDiv.append($h1)
elDiv.append($list)
$('body').append($div)
缺点:写起来很复杂。
字符串格式化
before:
var li = '<li>' + songs[i].name + ' - ' + songs[i].singer + '</li>'
after:
var li = stringFormat('<li>{0} - {1}</li>', songs[i].name, songs[i].singer)
自己实现 stringFormat 函数!
function stringFormat(string){
}
function stringFormat(){
var string = arguments[0]
}
//形参会影响length属性
//如果不写形参,不考虑length可以写成
//var string = arguments[0],相当于在函数的第一行做了一个声明,给第一个参数取了个名字。
function stringFormat(string){
this.string
//这样是错误的,不能用this取到,string是一个变量,不是一个属性。
}
相当于
function stringFormat(string){
var string
this.string
//声明一个string,不能用this取到
}
取数组
var a = [1,2,3]
a.unshift() //把数组最后一个取出来 3
a // [1,2,3] 原数组不变
a.shift() //把数组第一个取出来 1
a //[2,3] 原数组反生变化。
建议,不要改arguments,会让别人混乱。
var a = [1,2,3]
b = a.slice(1) //b = [2,3] 从第一个开始所有参数
a //[1,2,3]
用到的知识点
形参的个数可能不确定。
function stringFormat(string){
//var params = arguments.slice(1) //是一个伪数组,取到除了第一个,后面所有数组。
//arguments是个伪数组,所以数组的方法它都没有。
var params = [].slice.call(arguments,1)
//[].slice 的this是[],上面的语法是把this改为arguments,就可以借用另一个数组的slice方法,调用到arguments上面。
//call(),第一个参数是this
//使用正则获取
//var string = 'hi,{0},{1}'
//var regex = /\{\d+}/g
//string.match(regex)
//["{0}", "{1}"]
//string.replace(regex,'xxx') //"hi,xxx,xxx",把满足这个正则的都替换为xxx
//string.replace(regex,function(){console.log(arguments)})
//["{0}", 3, "hi,{0},{1}"] //3代表在第几个位置
//["{1}", 7, "hi,{0},{1}"]
//"hi,undefined,undefined" //函数没有return ,所以返回undefined。
//如果添加return 'xxx'
//则返回"hi,xxx,xxx"
var regex = /\{(\d+)}/g //会把{}里面的数字也拿出来
var regex = /\{(\d+)}/g
string.replace(regex,function(){
console.log(arguments)
return 'xxx'
})
["{0}", "0", 3, "hi,{0},{1},{2},{3}"]
["{1}", "1", 7, "hi,{0},{1},{2},{3}"]
["{2}", "2", 11, "hi,{0},{1},{2},{3}"]
["{3}", "3", 15, "hi,{0},{1},{2},{3}"]
"hi,xxx,xxx,xxx,xxx"
会增加一个数组,把{}里面的数字拿出来
string.replace(regex,function(){
console.log(arguments[0])
return 'xxx'
})
//输出
//{0}
//{1}
//{2}
//{3}
//"hi,xxx,xxx,xxx,xxx"
string.replace(regex,function(){
console.log(arguments[1])
return 'xxx'
})
//输出
//0
//1
//2
//3
//"hi,xxx,xxx,xxx,xxx"
}
正式代码
function stringFormat(string){
var params = [].slice.call(arguments,1)
var regex = /\{(\d+)\}/g
//将字符串中的 {n} 替换为 params[n]
string = string.replace(regex,function(){
var index = arguments[1]
return params[index]
})
return string
}
console.log(stringFormat('Hi,{0}','Jack'))
console.log(stringFormat('Hi,{1}','Jack','Tomy'))
console.log(stringFormat('Hi,{0} and {1}','Jack','Tomy'))
缺点:只能做一个一对一的替换。
模板引擎
作用:渲染生成想要的结果。
字符串格式化的升级版:模板引擎
var string =
<div class = song-list>
<h1>热歌榜</h1>
<ol>
<%for (var i = 0;i<songs.length;i++) {%>
<li><%songs[i].name%> - <%songs[i].singer%></li>
<%}%>
</ol>
</div>
var data = {
songs:songs //下面有介绍用法含义
}
var result = template(string,data)
document.body.innerHTML = result
// <% 是作者定义的语法。和字符串替换中{0}一样,自己定义的。
只需要传入 string 和 data
关于songs:songs
var songs = []
var data = {
songs : songs //属性:变量
}
var songs = []
var a = songs
var data = {
songs:a
}
var data = {
"songs":[...]
}
多行字符串写法
//第一种
var string =
'<div class = song-list>\
<h1>热歌榜</h1>\
<ol>\
<%for (var i = 0;i<songs.length;i++) {%>\
<li><%songs[i].name%> - <%songs[i].singer%></li>\
<%}%>\
</ol>\
</div>'
//js保留语法,末尾加"\"表示这一行没有结束
//问题:"\"还同时表示转义。"\"+" "时,表示这一行结束。
//第二种
var string =
'<div class = song-list>'+
' <h1>热歌榜</h1>'+
' <ol>'+
' <%for (var i = 0;i<songs.length;i++) {%>'+
' <li><%songs[i].name%> - <%songs[i].singer%></li>'+
' <%}%>'+
' </ol>'+
'</div>'
//第三种
var string =
`<div class = song-list>
<h1>热歌榜</h1>
<ol>
<%for (var i = 0;i<songs.length;i++) {%>
<li><%songs[i].name%> - <%songs[i].singer%></li>
<%}%>
</ol>
</div>`
自己实现模板引擎
只有20行Javascript代码!手把手教你写一个页面模板引擎
第一版:替换变量
var TemplateEngine = function(tpl,data){
//
}
var template = '<p>Hello, my name is <%name%>.I\'m <%age%> years old.</p>'
var data = {
name: "krasimir",
age: 29
}
var string = TemplateEngine(template,data)
console.log(string)
预备知识
- string.replace替换字符串
var string = 'jack,tom,jerry'
string.replace('jack','xxx')
'xxx,tom,jerry'//把遇到的第一个符合的,变为'xxx'
//string本身没有变
string.replace('jack','xxx').replace('jack','xxx')
//这样可以替换两次
如果想把所有jack都替换,就不能使用字符串了,要用正则;
string.replace(/jack/g,'xxx') //正则不加g,就替换一个。加g就替换所有
//g global全局
- regex.exec正则、遍历、替换
正则exec方法 ==> execute 执行。在一个字符串上执行这个正则。
var string = 'jack,tom,jerry,jack'
var regex = /jack/
regex.exec(string) //["jack"] //返回第一个
regex.exec(string) //["jack"] //返回第一个
regex.exec(string) //["jack"] //返回第一个
...
//没有g时,每次都找第一个
var regex = /jack/g //全局
regex.exec(string) //["jack"] //返回第一个
regex.exec(string) //["jack"] //返回第二个
regex.exec(string) //null //找不到了
regex.exec(string) //["jack"] //返回第一个
regex.exec(string) //["jack"] //返回第二个
...
//有g时,先找第一个,再找第二个,再找第三个找不到,再从头开始。
//正则在遍历这个字符串
var result;
while(result = regex.exec(string)){ //每次只搜一个
console.log('result是'+result)
string = string.replace(result,'xxx') //每次只换一个
console.log('string是'+string)
}
console.log(string)
//结果:
//result是jack
//string是xxx,tom,jerry,jack
//result是jack
//string是xxx,tom,jerry,xxx
//xxx,tom,jerry,xxx
//result = regex.exec(string) 一个"="
//把regex.exec(string)赋值给result,把result当做判断依据
//while(result == regex.exec(string)){}
//把result == regex.exec(string)当做判断依据
第一版的实现
var TemplateEngine = function(tpl,data){
var regex = /<%([^%>]+)?%>/g; //匹配<% for... %>,<% songs[i].name %>等。 ()分组,会把<% %>内的取出来
while(match = regex.exec(tpl)){
console.log(match)
//match === ["<%name%>", "name", index: 21, input: "<p>Hello, my name is <%name%>.I'm <%age%> years old.</p>"]
tpl = tpl.replace(match[0],data[match[1]])
console.log(data[match[1]])
}
return tpl;
}
var template = '<p>Hello, my name is <%name%>.I\'m <%age%> years old.</p>'
var data = {
name: "krasimir",
age: 29
}
var string = TemplateEngine(template,data)
console.log(string)
第二版:更复杂的逻辑
预备知识
var func = new Function(...)
两种声明方式
1.function xxx(){ console.log(1) }
2.var xxx = function(){ console.log(1) }
//还有另一种
var func = new Function('x','console.log(x)') //都是字符串
//相当于var func = Function(x){console.log(x)} //Function大写F
//多个参数
var xx = new Function('x','y','console.log(x);console.log(y)')
//更复杂的,双引号加转义
var func = new Function('x','y',"console.log(x+\"1\");console.log(y)")
func(1,2) //11,2
思路
var template =
'My skills:' +
'<%if(this.showSkills) {%>' +
'<%for(var index in this.akills) {%>' +
'<a href="#"><%this.skills[index]%></a>' +
'<%}%>' +
'<%} else {%>' +
'<p>none</p>' +
'<%}%>';
观察这个字符串:
<% %>外面,都是字符串
<% %>里面,一是控制语句(if/for),二是变量(this.skills[index])
变成纯js
var lines = ''
lines += 'My skills:'
if(this.showSkills){
for(var index in this.akills){
lines+= '<a href="#">'
lines+= this.skills[index]
lines+= '</a>'
}
else
lines+= '<p>none</p>'
}
xxxxx 不是<%开头,变为 "lines += xxxxx"
xxxxx 是<%
if/for 变为 "xxxxx"
不是if/for 变为 "lines += xxxxx"
生成上面的一段字符串
var func = new Function(result)
var func = function(){}
var func = function(){
var lines = ''
lines += 'My skills:'
if(this.showSkills){
for(var index in this.akills){
lines+= '<a href="#">'
lines+= this.skills[index]
lines+= '</a>'
}
else
lines+= '<p>none</p>'
}
return lines
}
func.call(data)
代码
var TemplateEngine = function(html, options) {
var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
var add = function(line, js) {
js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
return add;
}
while(match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}