實(shí)現(xiàn)一個(gè)簡單的 js 模板

要實(shí)現(xiàn)的目標(biāo)

<p>這是<%name%>的第一個(gè)<% content %></p>

data = {
  name: 'a',
  content: 'template'
}

變成

<p>這是a的第一個(gè)template</p>

這是個(gè)比較簡單的 html 解析代碼,用正則即可完成煌恢。思路是將所有的<% %>包裹的東西骇陈,與 data 對(duì)象的 key 匹配,匹配上替換即可瑰抵。

let templateDel = function(tpl, data) {
    var result = tpl.replace(/<%\s*([^%>|\s]+)?\s*%>/g, function(s0, s1) {
        return data[s1]
    })
    console.log(result)
}

檢驗(yàn)一下這個(gè)模板處理函數(shù)是否達(dá)到我們預(yù)期的效果

html

<body>
  <div id="results">

  </div>
  <script type="text/html" id="item_tmpl">
    <p>這是<%name%>的第一個(gè)<% age %></p>
    <a href=""><%name%></a>
  </script>
</body>

id="item_tmpl" 標(biāo)簽中包含著我們需要處理的模板語言你雌,處理完后放到id="results"

js

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var result = tpl.replace(/<%\s*([^%>|\s]+)?\s*%>/g, function(s0, s1) {
        return data[s1]
    })
    console.log(result)
    document.getElementById(toWhere).innerHTML = result
}
let data = {
    name: 'a',
    age: 'template'
}
templateDel('item_tmpl', data, 'results')

審查頁面的元素

<div id="results">
    <p>這是a的第一個(gè)template</p>
    <a href="">a</a>
</div>

這樣似乎滿足了我們一開始的需求,但是如果處理的數(shù)據(jù)比較復(fù)雜二汛,這樣似乎就不行了

<script type="text/html" id="item_tmpl">
    <p>這是<%name%>的第一個(gè)<% age %></p>
    <a href=""><%obj.x%></a>
</script>
let data = {
    name: 'a',
    age: 'template',
    obj: {
        x: 'xxxx'
    }
}
templateDel('item_tmpl', data, 'results')

這時(shí)模板里的 obj.x 就被替換成 data[obj.x],也就是 undefined 了
而且模板中一般會(huì)有一些循環(huán)和條件判斷的處理婿崭,這樣寫也沒有辦法解析

<script type="text/html" id="item_tmpl">
  <% for(var i=0; i< obj.arr.length; i++){ %>
    <p>這是obj.arr的第<%i%>個(gè)<% obj.arr[i] %></p>
    <% if(i != 2){ %>
      <a href=""><%i%></a>
    <%}%>
  <%}%>
</script>

這時(shí)我們不能簡單的用正則匹配替換就可以,而是換一種思路习贫。即逛球,把模板語句 用 function 執(zhí)行千元。

所以我們需要做的就是把模板當(dāng)作字符串 分割開來苫昌,然后在對(duì)變量和函數(shù)來 處理后在拼接起來。

第一步:用正則將代碼的分割

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var reg = /<%\s*([^%>]+)?\s*%>/g
    while ((match = reg.exec(tpl))) {
        console.log(match)
        // 這里match[1]是所有用了模板符號(hào)的代碼,那沒有用模板的標(biāo)簽或其他代碼怎么表示呢祟身?
    }
}

第二步:用一個(gè)變量 cursor 來標(biāo)示當(dāng)前解析到了哪個(gè)位置奥务,可以稱之為 游標(biāo)

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var reg = /<%\s*([^%>]+)?\s*%>/g,
        cursor = 0 // 默認(rèn)位置為0
    while ((match = reg.exec(tpl))) {
        console.log(match)
        // 這里match[1]是所有用了模板符號(hào)的代碼

        console.log(match.index)
        // 當(dāng)前正則解析到的模板的開始位置

        console.log(tpl.slice(cursor, match.index))
        // 當(dāng)前正則解析到的模板之前的沒有用模板的標(biāo)簽或其他代碼

        cursor = match.index + match[1].length
        // 重新定位游標(biāo)位置
    }
}

第三步:將分割完的代碼用數(shù)組存起來

這里不是簡單的定義一個(gè)變量類型為數(shù)組,然后往里 push袜硫,而是應(yīng)該定義一個(gè) 字符串氯葬, 該字符串應(yīng)是我們處理模板的函數(shù)體。

定義一個(gè) add 函數(shù)婉陷,專門處理 分割后的代碼往字符串里添加

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var reg = /<%\s*([^%>]+)?\s*%>/g,
        cursor = 0, // 默認(rèn)位置為0
        code = 'var r=[]; \n',
        add = function(line) {
            code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n'
        }
    while ((match = reg.exec(tpl))) {
        add(tpl.slice(cursor, match.index))
        add(match[1])
        cursor = match.index + match[1].length
        // 重新定位游標(biāo)位置
    }
    add(tpl.substr(cursor, tpl.length - cursor)) // 這里不能忘了最后一個(gè)正則匹配后的非模板標(biāo)識(shí)符包含的代碼塊
}

第四步:完善 add 函數(shù)

我們處理分割后的字符串帚称,應(yīng)該區(qū)分對(duì)待

不在模板標(biāo)識(shí)符中的 以字符串形式,存儲(chǔ)在數(shù)組中

在模板標(biāo)識(shí)符中的變量 以變量形式存在數(shù)組中

在模板標(biāo)識(shí)符中的函數(shù)語句秽澳,如 for闯睹,if 等 不存在數(shù)組中,而是直接拼接在函數(shù)體中

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var reg = /<%\s*([^%>]+)?\s*%>/g,
        cursor = 0, // 默認(rèn)位置為0
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 該正則是用來檢測是否是for担神,if等函數(shù)語句
        code = 'var r=[]; \n',
        add = function(line, js) {
            // js為判斷是否是在模板標(biāo)識(shí)符中包含的代碼塊
            js
                ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
                : (code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n')
        }
    while ((match = reg.exec(tpl))) {
        add(tpl.slice(cursor, match.index))
        add(match[1], true)
        cursor = match.index + match[1].length
        // 重新定位游標(biāo)位置
    }
    add(tpl.substr(cursor, tpl.length - cursor))
}

第五步:完善 code 函數(shù)體楼吃,并生成一個(gè)函數(shù)

code 除了包含我們分割的代碼,還應(yīng)該有個(gè) return妄讯, return 回的就是我們?cè)跀?shù)組 r 拼接后的字符串

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var reg = /<%\s*([^%>]+)?\s*%>/g,
        cursor = 0, // 默認(rèn)位置為0
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 該正則是用來檢測是否是for孩锡,if等函數(shù)語句
        code = 'var r=[]; \n',
        add = function(line, js) {
            // js為判斷是否是在模板標(biāo)識(shí)符中包含的代碼塊
            js
                ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
                : (code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n')
        }
    while ((match = reg.exec(tpl))) {
        add(tpl.slice(cursor, match.index))
        add(match[1], true)
        cursor = match.index + match[1].length
        // 重新定位游標(biāo)位置
    }
    add(tpl.substr(cursor, tpl.length - cursor))
    code += 'return r.join("")'
    let fn = new Function('obj', code.replace(/[\r\t\n]/g, ''))
    console.log(fn)
    document.getElementById(toWhere).innerHTML = fn(data.obj)
}

第六步:檢測代碼

    let data = {
        obj: {
            arr: ['a', 'b', 'c', 'd']
        }
    }
    templateDel('item_tmpl', data, 'results')

  // 結(jié)果為:
  <div id="results">
    <p>這是obj.arr的第0個(gè)a</p>
    <a href="">0</a>
    <p>這是obj.arr的第1個(gè)b</p>
    <a href="">1</a>
    <p>這是obj.arr的第2個(gè)c</p>
    <p>這是obj.arr的第3個(gè)d</p>
    <a href="">3</a>
  </div>

第七步: 進(jìn)一步完善

這里定義fn時(shí),傳入的形參為obj亥贸,限制了使用躬窜。有很大的局限性

let templateDel = function(id, data, toWhere) {
    let tpl = document.getElementById(id).innerHTML
    var reg = /<%\s*([^%>]+)?\s*%>/g,
        cursor = 0, // 默認(rèn)位置為0
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, // 該正則是用來檢測是否是for,if等函數(shù)語句
        code = 'var r=[]; \n',
        add = function(line, js) {
            // js為判斷是否是在模板標(biāo)識(shí)符中包含的代碼塊
            js
                ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
                : (code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n')
        }
    while ((match = reg.exec(tpl))) {
        add(tpl.slice(cursor, match.index))
        add(match[1], true)
        cursor = match.index + match[1].length
        // 重新定位游標(biāo)位置
    }
    add(tpl.substr(cursor, tpl.length - cursor))
    code += 'return r.join("")'
    let fn = new Function(code.replace(/[\r\t\n]/g, '')).call(this, data)
    console.log(fn)
    document.getElementById(toWhere).innerHTML = fn.apply(data)
}

改動(dòng)一下模板用法

<script type="text/html" id="item_tmpl">
  <% for(var i=0; i< this.obj.arr.length; i++){ %>
    <p>這是obj.arr的第<%i%>個(gè)<% this.obj.arr[i] %></p>
    <% if(i != 2){ %>
      <a href=""><%i%></a>
    <%}%>
  <%}%>
</script>

可能遇到的問題

模板片段分割時(shí)砌函,記得將"轉(zhuǎn)譯成\",不然會(huì)報(bào)錯(cuò)
不要忘了最后一段分割的代碼斩披,即add(tpl.substr(cursor, tpl.length - cursor))

大神的實(shí)現(xiàn)方式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市讹俊,隨后出現(xiàn)的幾起案子垦沉,更是在濱河造成了極大的恐慌,老刑警劉巖仍劈,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厕倍,死亡現(xiàn)場離奇詭異,居然都是意外死亡贩疙,警方通過查閱死者的電腦和手機(jī)讹弯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來这溅,“玉大人组民,你說我怎么就攤上這事”ィ” “怎么了臭胜?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我耸三,道長乱陡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任仪壮,我火速辦了婚禮憨颠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘积锅。我一直安慰自己爽彤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布缚陷。 她就那樣靜靜地躺著淫茵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹬跃。 梳的紋絲不亂的頭發(fā)上匙瘪,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蝶缀,去河邊找鬼丹喻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛翁都,可吹牛的內(nèi)容都是我干的碍论。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柄慰,長吁一口氣:“原來是場噩夢啊……” “哼鳍悠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坐搔,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤藏研,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后概行,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蠢挡,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年凳忙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了业踏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涧卵,死狀恐怖勤家,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柳恐,我是刑警寧澤伐脖,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布桐臊,位于F島的核電站,受9級(jí)特大地震影響晓殊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伤提,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一巫俺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肿男,春花似錦介汹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至如庭,卻和暖如春叹卷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坪它。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工骤竹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人往毡。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓蒙揣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親开瞭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子懒震,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,149評(píng)論 0 13
  • 一嗤详、Python簡介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡介】: Python 是一個(gè)...
    _小老虎_閱讀 5,746評(píng)論 0 10
  • 這周難得的一天休息,晚上還要上晚自習(xí).早上本打算把女兒送去練舞蹈然后抽出這兩小時(shí)的時(shí)間去逛逛添置些換季的衣...
    特殊的今天閱讀 211評(píng)論 0 1
  • 香香不知何許人也个扰,亦不詳其繆字,公司有女主播葱色,因以為號(hào)焉 锨匆。閑靜少言,不慕榮利冬筒。好做號(hào)恐锣,不求掃雷;每有廣告舞痰,便欣然...
    綻放的黑玫瑰閱讀 189評(píng)論 0 1