手擼 JavaScript 模板引擎

前言

當下前端充斥着各類各樣的開發框架:React,Vue 等等。然而大多數這些框架的設計模式是採用了以數據爲核心的 MVVM 模式。MVC 的開發模式已經離咱們漸行漸遠。javascript

對於 MVVM 模式來講,最核心的部件就是一個圍繞數據的模板引擎。html

模板引擎分爲前端和後端前端

  • 前端經常使用的模板引擎如:mustache.js,渲染是在客戶端完成的;
  • 後端的模引擎,渲染就是在服務器完成的;

今天咱們就來深刻研究一下客戶端模板引擎的實現!java

需求分析

模板引擎分爲兩個部分:git

  • 模板結構,用於規定渲染頁面的結構
  • 數據源,用於填充模板內的數據

由此咱們就能夠寫下咱們的第一行代碼:github

/** * @param {String} template * @param {Object} data * @returns {String} * @description render the template with the data source */
function TemplateEngine(template, data) {
  return;
}
複製代碼

假如我這裏有以下模板須要渲染:後端

var template =
  "<div>" +
  "<p>Name<span><% this.name %></span></p>" +
  "<p>Gender<span>" +
  "<% if(this.gender === 'male') { %>" +
  "Male" +
  "<% } else { %> " +
  "Female" +
  "<% } %>" +
  "</span></p>" +
  "</div>";
複製代碼

數據源:設計模式

var data = {
  name: "AJie",
  gender: "male"
};
複製代碼

須要的渲染結果:數組

<div>
  <p>Name<span>AJie</span></p>
  <p>Gender<span>Male</span></p>
</div>
複製代碼

實現思路

咱們先將數據源和渲染結果放在一邊,先來看看咱們的模板。瀏覽器

倘若你是瀏覽器,你會將模板最終渲染成爲何樣子呢?當你去除掉字符串的引號以及<% %>以後,答案漸漸的就浮出水面了。

<div>
  <p>Name<span>this.name</span></p>
  <p>Gender<span>
  if(this.gender === 'male') {
  Male
  } else {
  Female
  }
  </span></p>
</div>"
複製代碼

咱們如今來分析一下上面這段代碼,不難發現,只要是以前沒有包含在<% %>之中的模板字符串,都進行原樣輸出了;而那些再<% %>中的模板字符串則變成了可執行的 JavaScript 邏輯代碼。

所以咱們能夠先定義一個空數組,用於存儲 JavaScript 邏輯代碼:

var r = [];
r.push("<div><p>Name<span>");
r.push(this.name);
r.push("</span></p><p>Gender<span>");
if (this.gender === "male") {
  r.push(" Male ");
} else {
  r.push(" Female ");
}
r.push("</span></p></div>");
return r.join("");
複製代碼

對於原樣輸出的字符串,咱們先將它們封裝在 push() 的代碼之中,變爲 JavaScript 代碼,而對於以前的邏輯代碼則予以保留。

這麼一來,上面的代碼就更像是一個函數的函數體了,而調用者正是咱們的 data 數據源。其返回值則是咱們最終須要的結果——渲染好的字符串!

由此咱們能夠逐步完善以前的代碼:

function TemplateEngine(template, data) {
	var code = [];
  ...
  return new Function(code).apply(data);
}
複製代碼

值得注意:

  • new Function ([arg1[, arg2[, ...argN]],] functionBody)

    函數的參數首先出現,而函數體在最後。全部參數都寫成字符串形式。

  • apply(thisobj, args)

    若是不瞭解 apply(),還能夠查看我以前寫的文章:《Function.call()的需求分析》

到了這一步的時候,思路漸漸的明瞭了起來,咱們只須要在函數內部寫出一個生成可執行 JavaScript 代碼的字符串數組便可。

這時候咱們就應該想怎麼去處理函數內的 template 形參。

有了以前的那些思路鋪墊以後,咱們不難發現這是一個字符串檢索匹配的過程:

  1. 初始化 code = "var r=[];\n"
  2. 咱們先檢索模板字符串,依據<% %>將模板字符串劃分爲幾段
  3. 將每段按前後順序 push() 進 code 數組之中
    • 對於沒有被<% %>包裹的模板字符串,咱們直接將其放在'r.push("' + ... + '");\n'之中,再放進 code 數組。
    • 對於被<% %>包裹的模板字符串,須要判斷書語句仍是屬性:
      • 若是是屬性,就放在"r.push(" + 變量 + ")"之中,再放進 code 數組。
      • 若是是語句,就直接放入 code 數組之中。

代碼實現

結果前面的分析,對於該模板引擎的設計大體已經 OKay 了!

藉助於正則作模式字符串匹配進行功能實現:

/** * @param {String} template * @param {Object} data * @returns {String} * @description render the template with the data source */
function TemplateEngine(template, data) {
  var re = /<%([^%>]+)?%>/g,
    reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, //用於匹配語句標識
    code = "var r=[];\n",
    cursor = 0,
    match;
  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(template))) {
    add(template.slice(cursor, match.index))(match[1], true);
    cursor = match.index + match[0].length;
  }
  add(template.substr(cursor, template.length - cursor));
  code += 'return r.join("");';
  return new Function(code.replace(/[\r\t\n]/g, "")).apply(data);
}
複製代碼

第一個正則會匹配<%%>而後把<%%>之間的內容保存下來,第二個正則正好會處理第一個正則保存下來的內容。在<%的後面和%>的前面能夠沒有空格,也能夠有一個空格,好比<%name%><% name %>應該被認爲是同樣的,因此爲了知足這個需求,前面須要添加一個( )?( )表示匹配一個空格,?表示前面的重複 0 到 1 次,因此( )?的意思就是說能夠有一個空格,也能夠沒有。 在while循環中,首先用第一個re去匹配(match = re.exec(tpl)),而後<%%>之間的內容被保存在match[1]中,而後用re2去匹配(re2.test(match[1]))。 注意,re<%%>之間的內容所有放在match[1]中了,因此若是是<%name%>那麼match[1]中的就是"name",可是若是是<% name %>那麼match[1]中的就是" name ",因此須要使用( )?來處理一下空格。

測試

測試代碼以下:

console.log(TemplateEngine(template, data));
複製代碼

控制檯打印輸出:

<div>
  <p>Name<span>AJie</span></p>
  <p>Gender<span>Male</span></p>
</div>
複製代碼

總結

咱們始終要記住 MVVM 模式是以數據爲驅動的。

所謂模板引擎,簡單地說,就是依據頁面結構模板和數據源,渲染出真正的頁面結構的功能函數。

-EFO-


筆者專門在 github 上建立了一個倉庫,用於記錄平時學習全棧開發中的技巧、難點、易錯點,歡迎你們點擊下方連接瀏覽。若是以爲還不錯,就請給個小星星吧!👍


2019/04/25

AJie

相關文章
相關標籤/搜索