实现模板引擎

2018-02-19  本文已影响0人  阿鲁提尔

字符串替换

Template(模板)

目录

  1. 字符串拼接
  2. string format(字符串格式化)
  3. 模板替换
  4. 自制模板引擎
  5. 常见模板引擎介绍

一个简单的需求

用 JS 『渲染』一个歌曲列表

  1. 数据来自一个数组 songs
  2. 不能写死在页面里
songs === [
   {name: '绅士', url: 'http://music.163.com/xxx', singer: '薛之谦'},
   {name: '刚刚好', url: 'http://music.163.com/yyy', singer: '薛之谦'},
   ...
]

最傻的办法 - 遍历

你必须要能想到一个最傻的办法

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代码,也会执行。存在注入风险。

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)

预备知识

  1. 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全局
  1. 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);
}
上一篇下一篇

猜你喜欢

热点阅读