前端模板引擎的實現總結

BFC發表於2018-07-05

參考連結:https://juejin.im/post/59663eaa6fb9a06ba73d4c35

閱讀這篇文章後,發現前端模板引擎的實現其實也並不是很難,我們現在一步步來分析。

1、原始拼接html片段:

var author = 'me';
var html = `<p>my name is ${author}</p>`; 複製程式碼

缺點:手動拼接字串比較容易出錯,並且瀏覽拼接速度較慢。


2、既然容易出錯,拼接慢,是否有另一種替代方案呢?答案就是陣列轉字串,join()方法

var author = 'me';
var result = ['<p> my name is ', author, '</p>'].join('');複製程式碼


下面根據第二種方案來解決模板問題:例如

// 模板編譯函式
function TemplateEgine(tpl, data) {
  
}
//
function Test() {
  this.id = 'test';
  this.data = { 
    name: 123465
  }
  this.result = [];
}

var tpl = '<p>my name is <% id %> </p>';
var test = new Test();
TemplateEgine(tpl, test.id);

// 分析
1)對於這種單一變數的替換,自然想到使用正規表示式來替換掉佔位符的內容,然後替換真正的值,結果為:

result.push('<p> my name is');
result.push(test.id);
result.push('</p>');

但是如果變數是巢狀呢?例如:

var tpl = '<p><% id %>my name is <% data.name %> </p>';
var test = new Test();
TemplateEgine(tpl, {
  id: test.id,
  data: test.data
});

我們不能再單純替換了,而是應該執行佔位符裡面的程式碼,能夠執行js程式碼的,方法時eval和 new Function(),
一般new Function效能比evel好,這裡採用Function。
還是使用陣列儲存:

result.push(this.id);
result.push('<p> my name is');
result.push(this.data.name);
result.push('</p>');
new Function('return this.result.join("")').call(test);
// 由於 new Function執行是建立一個匿名函式,作用域是全域性的,所以要指定this

2)雖然解決變數的問題,但是如果是遇到複雜的語句,if for 等,如果push到陣列去,不能執行,因此需要根據
正規表示式來判斷是複雜語句還是變數,如果是變數直接替換,如果是複雜語句先執行裡面內容,將執行結果push到
陣列,轉為變數這種情況。例如:

<% for (var i = 0; i < 11; i++) {%>
<p><% i %></p>
<%}%>

結果:
for (var i =0; i < 11; i++) {
  result.push('<p>');
  result.push(this.i);
  result.push('</p>');
}
new Function('return this.result.join("")').call(test);

總結:對於變數直接替換,對於複雜語句先執行,轉為變數這種情況,兩者使用 new Function來執行,由於是全域性
作用域,所以需要繫結this.

區別:vue react模板引擎是帶指令的,會把其轉為AST樹,過程比上面更復雜,待續。。。複製程式碼


相關文章