美文网首页
实现模板引擎

实现模板引擎

作者: 阿鲁提尔 | 来源:发表于2018-02-19 16:01 被阅读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: '薛之谦'},
       ...
    ]
    

    最傻的办法 - 遍历

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

    • 方案一:拼 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)
    

    预备知识

    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);
    }
    

    相关文章

      网友评论

          本文标题:实现模板引擎

          本文链接:https://www.haomeiwen.com/subject/ipnitftx.html