20行だけのJavaScriptテンプレートエンジンの例を詳しく説明します。


本論文の実例はJavaScriptテンプレートエンジンを述べている。皆さんに参考にしてあげます。具体的には以下の通りです。
原文のリンク:JavaScript template engine in just 20 lines
(訳者のツッコミ:収集だけしてもいいですか?全部ごろつきです。)
前言
私は相変わらずJSプリプロセッサAbsurdJSの開発を行っています。元々はCSSプリプロセッサだったが、その後はCSS/HTMLプリプロセッサに拡張され、すぐにJSからCSS/HTMLへの変換をサポートします。これはテンプレートエンジンのようにHTMLコードを生成することができます。つまり、テンプレートの中の識別セグメントをデータで埋めることができます。
ですから、私の現在の需要を満たすためのテンプレートエンジンを書きたいです。AbsurdJSは主にNodeJSのモジュールとして使用されているが、同時にクライアントで使用されてもよい。この目的のために、私は市場にすでに存在しているテンプレートエンジンを使うことができません。それらはほとんどNodeJSに依存していますので、ブラウザで使うのは難しいです。もっと小さくて純粋なJSで作成したテンプレートエンジンが必要です。このジョン・Resigによって書かれたブログを見ましたが、これはまさに私の必要なもののようです。中のコードを少し修正して、20行に濃縮しました。
このコードの運行原理はとても面白いです。この文章の中でジョンのwonderful ideaを一歩ずつ紹介します。
1、識別フラグメントの抽出
これは私たちが最初に獲得するものです。

var TemplateEngine = function(tpl, data) {
 // magic here ...
}
var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
console.log(TemplateEngine(template, {
 name: "Krasimir",
 age: 29
}));
簡単な関数で、テンプレートとデータをパラメータとして入力します。あなたが想像しているように、次のような結果が得られます。

<p>Hello, my name is Krasimir. I'm 29 years old.</p>
私たちがやるべき最初のことは、テンプレートの中の識別セグメント<%...%>を取得し、それを着信エンジンのデータで充填することです。これらの機能を正規表現で行うことにしました。正則は私の得意分野ではないので、我慢してください。もっといい正則があれば、私にも提出してください。

var re = /<%([^%>]+)?%>/g;
私たちは<%で始まり、%>で終わるコードブロックと一致します。最後のgは私たちが複数のコードにマッチすると表しています。多くの方法が正則にマッチすることができますが、文字列をロードできる配列が必要です。これはまさにexecの仕事です。

var re = /<%([^%>]+)?%>/g;
var match = re.exec(tpl);
コンソールconsole.log(match)で見られます。

[
 "<%name%>",
 " name ", 
 index: 21,
 input: 
 "<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>"
]
正確なマッチング結果を得ましたが、ご覧のように、識別フラグメント<%name%>にしかマッチしていません。したがって、すべての識別セグメントを取得するためにwhileサイクルが必要です。

var re = /<%([^%>]+)?%>/g, match;
while(match = re.exec(tpl)) {
 console.log(match);
}
実行中、すべての識別セグメントがすでに私たちによって取得されていることが分かりました。
2、データ充填と論理処理
識別されたセグメントを取得したら、データを塗りつぶします。.replace方法を使うのが一番簡単な方法です。

var TemplateEngine = function(tpl, data) {
 var re = /<%([^%>]+)?%>/g, match;
 while(match = re.exec(tpl)) {
  tpl = tpl.replace(match[0], data[match[1]])
 }
 return tpl;
}

data = {
 name: "Krasimir Tsonev",
 age: 29
}
OK、正常運行。しかし、これは十分ではないことは明らかです。現在のデータ構造は非常に簡単ですが、実際の開発ではより複雑なデータ構造に直面します。

{
 name: "Krasimir Tsonev",
 profile: { age: 29 }
}
エラーが発生した理由は、テンプレートに<%profile.age%>を入力したときに、私たちが得たdata["profile.age"]はundefinedです。確かに.replace方法は通用しません。本物のJSコードを「%と%」に挿入する他の方法が必要です。以下の栗のようです。

var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
これは完成できそうにないです。ジョンはnew Functionを使用して、つまり文字列を通して関数を作成する方法でこの機能を完成しました。くりを一つあげる:

var fn = new Function("arg", "console.log(arg + 1);");
fn(2); //    3
fnは本物の関数であり、その関数はconsole.log(arg + 1)であるパラメータを含む。以上のコードは下記のコードに相当します。

var fn = function(arg) {
 console.log(arg + 1);
}
fn(2); //    3
new Functionを通して、私たちは文字列を通して関数を作成することができます。これはまさに私たちの必要なものです。このような関数を作成する前に、この関数を構築する必要があります。関数は最終的にスティッチングされたテンプレートを返します。前の文書のテンプレート文字列を使用して、この関数が返されるべき結果を想像してください。

return 
"<p>Hello, my name is " + 
this.name + 
". I\'m " + 
this.profile.age + 
" years old.</p>";
明らかに、テンプレートをテキストとJSコードに分けました。上記のコードのように、簡単な文字列スティッチングを使って最終結果を取得しましたが、この方法は100%私達の需要を実現できません。これは後ほど循環などのJS論理を処理します。

var template = 
'My skills:' + 
'<%for(var index in this.skills) {%>' + 
'<a href=""><%this.skills[index]%></a>' +
'<%}%>';
文字列スティッチングを使うと、結果はこうなります。

return
'My skills:' + 
for(var index in this.skills) { +
'<a href="">' + 
this.skills[index] +
'</a>' +
}
当然です。これは間違えます。これもジョンさんの文章を参考にして論理を書くことにした理由です。すべての文字列を一つの配列にプッシュして、最後にやっとそれらをつなぎ合わせました。

var r = [];
r.push('My skills:'); 
for(var index in this.skills) {
r.push('<a href="">');
r.push(this.skills[index]);
r.push('</a>');
}
return r.join('');
次のロジックは、得られた各ラインのコードを整理して関数を生成します。テンプレートからいくつかの情報を抽出しました。セグメントの内容と位置を識別することが分かりました。だから、私たちはポインタ変数を通して最終的な結果を得るのを手伝ってくれます。

var TemplateEngine = function(tpl, data) {
 var re = /<%([^%>]+)?%>/g,
  code = 'var r=[];
', cursor = 0, match; var add = function(line) { code += 'r.push("' + line.replace(/"/g, '\\"') + '");
'; } while(match = re.exec(tpl)) { add(tpl.slice(cursor, match.index)); add(match[1]); cursor = match.index + match[0].length; } add(tpl.substr(cursor, tpl.length - cursor)); code += 'return r.join("");'; // <-- return the result console.log(code); return tpl; } var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>'; console.log(TemplateEngine(template, { name: "Krasimir Tsonev", profile: { age: 29 } }));
変数codeは、関数全体の関数として、1つの配列を宣言するために始まる。私が言ったように、ポインタ変数cursorは、私たちがテンプレートのどの位置にいるかを表しています。すべての文字列を巡回して、充填データのセグメントをスキップする必要があります。また、add関数のタスクは、関数体を構築するプロセス方法として、文字列をcode変数に挿入することである。ここには苦手なところがあります。識別子<%%>をスキップしないとJSスクリプトが無効になります。上記のコードを直接実行すれば、結果は以下のようになります。

var r=[];
r.push("<p>Hello, my name is ");
r.push("this.name");
r.push(". I'm ");
r.push("this.profile.age");
return r.join("");
えっと、これは私達が欲しいものではないです。this.namethis.profile.ageは引用符を持つべきではない。add関数を改善します。

var add = function(line, js) {
 js? code += 'r.push(' + line + ');
' : code += 'r.push("' + line.replace(/"/g, '\\"') + '");
'; } var match; while(match = re.exec(tpl)) { add(tpl.slice(cursor, match.index)); add(match[1], true); // <-- say that this is actually valid js cursor = match.index + match[0].length; }
識別フラグメントの内容は、1つのbollan値によって制御される。正しい関数を得ました。

var r=[];
r.push("<p>Hello, my name is ");
r.push(this.name);
r.push(". I'm ");
r.push(this.profile.age);
return r.join("");
次はこの関数を生成して実行します。このテンプレートエンジンの最後に、私達は以下のコードを使って直接にtplオブジェクトに戻ります。

return new Function(code.replace(/[\r\t
]/g, '')).apply(data);
私たちは、apply方法がこのステップのために完全に動作しているので、関数に任意のパラメータを伝達する必要もない。これは自動的に機能領域を設置しています。これもどうしてthis.nameが動作できるのですか?thisは私達のdataを指しています。
3、コードの最適化
大体完成しました。最後の事柄については、if/else式やループなど、より複雑な表現をサポートする必要があります。同じ例で次のコードを実行してみましょう。

var template = 
'My skills:' + 
'<%for(var index in this.skills) {%>' + 
'<a href="#"><%this.skills[index]%></a>' +
'<%}%>';
console.log(TemplateEngine(template, {
 skills: ["js", "html", "css"]
}));
結果はエラーとなります。エラーはUncaught SyntaxError: Unexpected token forです。よく観察してください。code変数を通して問題点を見つけられます。

var r=[];
r.push("My skills:");
r.push(for(var index in this.skills) {);
r.push("<a href=\"\">");
r.push(this.skills[index]);
r.push("</a>");
r.push(});
r.push("");
return r.join("");
forサイクルを含むコードは、セットにプッシュされるべきではなく、直接スクリプトに入れるべきです。この問題を解決するためには、コードpushをcode変数に変更する前に、さらなる判断が必要です。

var re = /<%([^%>]+)?%>/g,
 reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
 code = 'var r=[];
', cursor = 0; var add = function(line, js) { js? code += line.match(reExp) ? line + '
' : 'r.push(' + line + ');
' : code += 'r.push("' + line.replace(/"/g, '\\"') + '");
'; }
新しい正則を追加しました。この正則の役割は、JSコードの一部がif, for, else, switch, case, break, |で始まると、それらは直接関数に追加されます。そうでない場合は、プッシュはcode変数になります。以下は修正後の結果です。

var r=[];
r.push("My skills:");
for(var index in this.skills) {
r.push("<a href=\"#\">");
r.push(this.skills[index]);
r.push("</a>");
}
r.push("");
return r.join("");
当たり前のように正確に実行します。

My skills:<a href="#" >js</a><a href="#">html</a><a href="#">css</a>
次の修正はより強力な機能を与えてくれます。私たちはもっと複雑な論理がテンプレートに入れるかもしれません。

var template = 
'My skills:' + 
'<%if(this.showSkills) {%>' +
 '<%for(var index in this.skills) {%>' + 
 '<a href="#"><%this.skills[index]%></a>' +
 '<%}%>' +
'<%} else {%>' +
 '<p>none</p>' +
'<%}%>';
console.log(TemplateEngine(template, {
 skills: ["js", "html", "css"],
 showSkills: true
}));
いくつかの細かい最適化を行った後、最終バージョンは以下の通りです。

var TemplateEngine = function(html, options) {
 var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];
', cursor = 0, match; var add = function(line, js) { js? (code += line.match(reExp) ? line + '
' : 'r.push(' + line + ');
') : (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");
' : ''); 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
]/g, '')).apply(options); }
最適化後のコードは15行よりも少ないです。
後記。
これは初めて完璧に文章を翻訳しました。誤文が多いので、ご了承ください。これからも引き続き努力して、より良い文章の翻訳を分かち合うように努力します。
先端のフレームワークやテンプレートエンジンなどのツールに興味があり、その原理を学びたいと思い、比較的簡単なテンプレートエンジンを探して研究しました。googleはこの文章を見て、非常に優秀だと思います。一歩ずつ説明していきます。コードは本人のテストを通して正確に文章の説明の結果を得られます。
テンプレートエンジンには様々なデザインのアイデアがありますが、本論文はその中の一つにすぎません。性能などのパラメータはまだテストを待っています。
ありがとうございます
興味のある友達はオンラインHTML/CSS/JavaScriptコードを使ってツールを実行できます。http://tools.jb51.net/code/HtmlJsRun上記コードの運行効果をテストします。
もっと多くのJavaScriptに関する内容に興味がある読者は、当駅のテーマを見ることができます。「javascript対象向け入門教程」、「JavaScriptエラーとデバッグテクニックのまとめ」、「JavaScriptデータ構造とアルゴリズム技術のまとめ」、「JavaScriptはアルゴリズムと技術の総括を遍歴します。」および「JavaScript数学演算の使い方のまとめ
本論文で述べたように、JavaScriptプログラムの設計に役に立ちます。