欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

怎么用JavaScript實現(xiàn)一個模板引擎

這篇文章給大家分享的是有關(guān)怎么用JavaScript實現(xiàn)一個模板引擎的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

靖安網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),靖安網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為靖安上千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)網(wǎng)站制作要多少錢,請找那個售后服務(wù)好的靖安做網(wǎng)站的公司定做!

功能分析

一個模板引擎,在我看來,就是由兩塊核心功能組成,一個是用來將模板語言解析為 ast(抽象語法樹)。還有一個就是將 ast 再編譯成 html。

先說明一下 ast 是什么,已知的可以忽略。

抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax  tree),是源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式,這里特指編程語言的源代碼。樹上的每個節(jié)點都表示源代碼中的一種結(jié)構(gòu)。之所以說語法是“抽象”的,是因為這里的語法并不會表示出真實語法中出現(xiàn)的每個細節(jié)。比如,嵌套括號被隱含在樹的結(jié)構(gòu)中,并沒有以節(jié)點的形式呈現(xiàn);而類似于if-condition-then這樣的條件跳轉(zhuǎn)語句,可以使用帶有兩個分支的節(jié)點來表示。

在實現(xiàn)具體邏輯之前,先決定要實現(xiàn)哪幾種 tag 的功能,在我看來,for,if else,set,raw還有就是基本的變量輸出,有了這幾種,模板引擎基本上也就夠用了。除了 tag,還有就是 filter 功能也是必須的。

構(gòu)建 AST

我們需要把模板語言解析成一個又一個的語法節(jié)點,比如下面這段模板語言:

<div>
    {% if test > 1 %}
        {{ test }}
    {% endif %}
</div>

很明顯,div 將會被解析為一個文本節(jié)點,然后接著是一個塊級節(jié)點 if ,然后 if 節(jié)點下又有一個變量子節(jié)點,再之后有是一個 的文本節(jié)點,用 json 來表示這個模板解析成的 ast 就可以表示為:

[
    {type: 1,
        text: '<div>'},
    {type: 2,
        tag: 'if',
        item: 'test > 1',
        children: [{           type: 3,
           item: 'test'}]
    },
    {type: 1,
        text: '</div>'}
]

基本上就分成三種類型了,一種是普通文本節(jié)點,一種是塊級節(jié)點,一種是變量節(jié)點。那么實現(xiàn)的話,就只需要找到各個節(jié)點的文本,并且抽象成對象即可。一般來說找節(jié)點都是根據(jù)模板語法來找,比如上面的塊級節(jié)點以及變量節(jié)點的開始肯定是{%或者{{,那么就可以從這兩個關(guān)鍵字符下手:

...const matches = str.match(/{{|{%/);const isBlock = matches[0] === '{%';const endIndex = matches.index;
...

通過上面一段代碼,就可以獲取到處于文本最前面的{{或者{%位置了。

既然獲取到了***個非文本類節(jié)點的位置,那么該節(jié)點位置以前的,就都是文本節(jié)點了,因此就已經(jīng)可以得到***個節(jié)點,也就是上面的<div>了。

獲取到 div 文本節(jié)點后,我們也可以知道獲取到的***個關(guān)鍵字符是{%,也就是上面的endIndex是我們要的索引,記得要更新剩余的字符,直接通過 slice 更新即可:

// 2 是 {% 的長度str = str.slice(endIndex + 2);

而此時我們就可以知道匹配到的當前關(guān)鍵字符是{%,那么他的閉合處就肯定是%},因此就可以再通過

const expression = str.slice(0, str.indexOf('%}'))

獲取到 if test > 1 這個字符串了。然后我們再通過正則/^if\s+([\s\S]+)$/匹配,就可以知道這個字符串是 if 的標簽,同時可以獲得test > 1這一個捕獲組,然后就可以創(chuàng)建我們的第二個節(jié)點,if 的塊級節(jié)點了。

因為 if 是個塊級節(jié)點,那么繼續(xù)往下匹配的時候,在遇到 {% endif %} 之前的所有節(jié)點,都是屬于 if 節(jié)點的子節(jié)點,所以我們在創(chuàng)建節(jié)點時要給它一個children數(shù)組屬性,用來保存子節(jié)點。

緊接著再重復(fù)上面的操作,獲取下一個{%以及{{的位置,跟上面的邏輯差不多,獲取到{{的位置后再判斷}}的位置,就可以創(chuàng)建第三個節(jié)點,test 的變量節(jié)點,并且 push 到 if 節(jié)點的子節(jié)點列表中。

創(chuàng)建完變量節(jié)點后繼續(xù)重復(fù)上述操作,就能夠獲取到{% endif %}這個閉合節(jié)點,當遇到該節(jié)點之后的節(jié)點,就不能保存到 if 節(jié)點的子節(jié)點列表中了。緊接著就又是一個文本節(jié)點。

相對比較完整的實現(xiàn)如下:

const root = [];
let parent;function parse(str){const matches = str.match(/{{|{%/);const isBlock = matches[0] === '{%';const endIndex = matches.index;const chars = str.slice(0, matches ? endIndex : str.length);if(chars.length) {
     ...創(chuàng)建文本節(jié)點 
    }if(!matches) return;

    str = str.slice(endIndex + 2);const leftStart = matches[0];const rightEnd = isBlock ? '%}' : '}}';const rightEndIndex = str.indexOf(rightEnd);const expression = str.slice(0, rightEndIndex)if(isBlock) {
        ...創(chuàng)建塊級節(jié)點 elparent = el;
    } else {
        ...創(chuàng)建變量節(jié)點 el
    }

    (parent ? parent.children : root).push(el);
    parse(str.slice(rightEndIndex + 2));
}

當然,具體實現(xiàn)起來還是有其他東西要考慮的,比如一個文本是{% {{ test }},就要考慮到{%的干擾等。還有比如 else 還有 elseif 節(jié)點的處理,這兩個是需要關(guān)聯(lián)到 if 標簽上的,這個也是需要特殊處理的。不過大概邏輯基本上就是以上。

組合 html

創(chuàng)建好 ast 后,要渲染 html 的時候,就只需要遍歷語法樹,根據(jù)節(jié)點類型做出不同的處理即可。

比如,如果是文本節(jié)點,就直接html += el.text即可。如果是if節(jié)點,則判斷表達式,比如上面的test > 1,有兩種辦法可以實現(xiàn)表達式的計算,一種就是eval,還有一種就是new Function了,eval 會有安全性問題,因此就不考慮了,而是使用new Function的方式來實現(xiàn)。變量節(jié)點的計算也一樣,用new Function來實現(xiàn)。

封裝后具體實現(xiàn)如下:

function computedExpression(obj, expression) {  const methodBody = `return (${expression})`;  const funcString = obj ? `with(__obj__){ ${methodBody} }` : methodBody;  const func = new Function('__obj__', funcString);  try {let result = func(obj);return (result === undefined || result === null) ? '' : result;
  } catch (e) {return '';
  }
}

使用 with ,可以讓在 function 中執(zhí)行的語句關(guān)聯(lián)對象,比如

with({ a: '123' }) {console.log(a); // 123}

雖然 with 不推薦在編寫代碼的時候使用,因為會讓 js 引擎無法對代碼進行優(yōu)化,但是卻很適合用來做這種模板編譯,會方便很多。包括  vue 中的 render function 也是用 with 包裹起來的。不過 nunjucks 是沒有用 with  的,它是自己來解析表達式的,因此在 nunjucks 的模板語法中,需要遵循它的規(guī)范,比如最簡單的條件表達式,如果用 with 的話,直接寫{{ test ? 'good' : 'bad' }},但是在 nunjucks 中卻要寫成?{{ 'good' if test else 'bad' }}。

anyway,各有各的好吧。

實現(xiàn)多級作用域

在將 ast 轉(zhuǎn)換成 html 的時候,有一個很常見的場景就是多級作用域,比如在一個 for 循環(huán)中再嵌套一個 for 循環(huán)。而如何在做這個作用域分割,其實也是很簡單,就是通過遞歸。

比如我的對一個 ast 樹的處理方法命名為:processAst(ast, scope),再比如最初的 scope 是

{ 
  list: [
   { subs: [1, 2, 3] },
   { subs: [4, 5, 6] } 
  ] 
 }

那么 processAst 就可以這么實現(xiàn):

function processAst(ast, scope) {
    ...if(ast.for) {const list = scope[ast.item]; // ast.item 自然就是列表的 key ,比如上面的 listlist.forEach(item => {
            processAst(ast.children, Object.assign({}, scope, {
                [ast.key]: item,  // ast.key 則是 for key in list 中的 key}))
        })
    }
    ...
}

就簡單通過一個遞歸,就可以把作用域一直傳遞下去了。

Filter 功能實現(xiàn)

實現(xiàn)上面功能后,組件就已經(jīng)具備基本的模板渲染能力,不過在用模板引擎的時候,還有一個很常用的功能就是 filter 。一般來說 filter 的使用方式都是這這樣 {{ test | filter1 | filter2 }},這個的實現(xiàn)也說一下,這一塊的實現(xiàn)我參考了 vue 的解析的方式,還是蠻有意思的。

還是舉個例子:

{{ test | filter1 | filter2 }}

在構(gòu)建 AST 的時候,就可以獲取到其中的test | filter1 | filter2,然后我們可以很簡單的就獲取到 filter1 和 filter2 這兩個字符串。起初我的實現(xiàn)方式,是把這些 filter 字符串扔進 ast 節(jié)點的 filters 數(shù)組中,在渲染的時候再一個一個拿出來處理。

不過后來又覺得為了性能考慮,能夠在 AST 階段就能做完的工作就不要放到渲染階段了。因此就改成 vue 的方法組合方式。也就是把上面字符串變成:

_$f('filter2', _$f('filter1', test))

預(yù)先用個方法包裹起來,在渲染的時候,就不需要再通過循環(huán)去獲取 filter 并且執(zhí)行了。具體實現(xiàn)如下:

const filterRE = /(?:\|\s*\w+\s*)+$/;const filterSplitRE = /\s*\|\s*/;function processFilter(expr, escape) {  let result = expr;  const matches = expr.match(filterRE);  if (matches) {const arr = matches[0].trim().split(filterSplitRE);
    result = expr.slice(0, matches.index);// add filter method wrappingutils.forEach(arr, name => {      if (!name) {return;
      }      // do not escape if has safe filter  if (name === 'safe') {escape = false;return;
      }

      result = `_$f('${name}', ${result})`;
    });
  }  return escape ? `_$f('escape', ${result})` : result;
}

上面還有一個就是對 safe 的處理,如果有 safe 這個 filter ,就不做 escape 了。完成這個之后,有 filter 的 variable 都會變成_$f('filter2', _$f('filter1', test))這種形式了。因此,此前的 computedExpression 方法也要做一些改造了。

function processFilter(filterName, str) {  const filter = filters[filterName] || globalFilters[filterName];  if (!filter) {throw new Error(`unknown filter ${filterName}`);
  }  return filter(str);
}function computedExpression(obj, expression) {  const methodBody = `return (${expression})`;  const funcString = obj ? `with(_$o){ ${methodBody} }` : methodBody;  const func = new Function('_$o', '_$f', funcString);  try {const result = func(obj, processFilter);return (result === undefined || result === null) ? '' : result;
  } catch (e) {// only catch the not defined errorif (e.message.indexOf('is not defined') >= 0) {      return '';
    } else {      throw e;
    }
  }
}

其實也是很簡單,就是在 new Function 的時候,多傳入一個獲取 filter 的方法即可,然后有 filter 的 variable 就能被正常識別解析了。

感謝各位的閱讀!關(guān)于“怎么用JavaScript實現(xiàn)一個模板引擎”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

網(wǎng)站題目:怎么用JavaScript實現(xiàn)一個模板引擎
分享網(wǎng)址:http://aaarwkj.com/article48/ihppep.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗、響應(yīng)式網(wǎng)站、移動網(wǎng)站建設(shè)、微信公眾號品牌網(wǎng)站制作、App設(shè)計

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)
日本女优久久精品观看| 国产黄色av片免费| 亚洲欧美综合精品久久成人| 日韩特级黄片在线免费观看| 亚洲三级成人一区在线| 亚洲综合另类小说专区| 久久精品性少妇一区二区三区| 久久国产高清亚洲电影| 国产高清不卡一二三区| 国产激情一区二区三区| 天美传媒剧国产在线观看| 日本一区二区三级在线观看| 性感美女国产av一区二区三区| 国产又粗又长又爽网站| 一区二区在线视频中文字幕| 亚洲精品一品区二品区三| 濑亚美莉在线观看一区二区三区| 日韩亚洲欧洲一区二区三区| 我要看黄色一级性生活片| 国产三级精品在线免费| 日韩精品a区二区在线电影| 美女后入式在线观看| 午夜福利一区二区在线| 97热久久精品中文字幕一区| 久久精品国产亚洲七七| 精品欧美日韩国产一区| 91精品国产色综合久久不| 国产黄片免费看久久久| 成人国产午夜福利网| 成人福利在线观看免费视频| 91精品婷婷国产综合| 我想看亚洲一级黄色录像| 日本韩国国语对白一区二区三区| 老湿机午夜十分钟视频| 国产成人亚洲欧美激情| 国产有码日产一区在线观看| 传媒视频免费在线观看| 日韩av有码在线播放| 久久综合热这里只有精品| 午夜视频免费在线观看| 精品国产一区二区日韩91 |