ここで、最終的に、彼らは多くの自転車の1つを人々と共有するようになりました(現在は個人的な成果と呼ばれています)。 Habrakat以前は、このソリューションの長所と短所がいくつかありました。
プロから:
- jsperf speed(プリコンパイル済みテンプレートでテスト: jsperf )
- 拡張性:=カスタムコントロール、テンプレートデータ変換
- データバインディング
- さらにキャッシュするためのjsonでのコンパイル
- 素敵な構文(ロジックと構造の破壊的問題はありません)
欠点の:
- テンプレートデータは属性とリテラルにのみ含めることができます(ただし、信じてください-これで十分です)
トピックが興味深い場合-
エントリー
すぐに文法/スタイルについて謝罪させてください。 私はすべてを非常に簡潔に書きたいと思いますが、同時に、誰もが私を理解し、さらに彼らが私を正しく理解するようにします。 あなたは、文字通りすべてを「理解」するコンパイラではありませんが、それぞれが彼自身の経験、判断、その他のことを持っています。 そして、私は記事を書く経験がほとんどなく、ロシア語で書くことも経験していないので、これは私が想像するようにすべてを書くという希望を根本的に否定します。 しかし、私はしようとします、そしてあなたは誓わない。
ちょっとした歴史
数年前、
String.format('N: #{a} : #{b}',{a:1,b:2})
という形式の関数が必要でした。
すぐにそれを使用してhtmlをフォーマットすることに気付きました。ほとんどの場合、私の手は縛られています。 結局、条件、条件付き可視性、およびリストによる書式設定が必要です。 さまざまなテンプレートエンジンを見て、 htmlとjavascriptの混合に大きな嫌悪感を感じ、それに加えて
with(){}
と
eval/new Function('')
も喜ばれませんでした。 「本当に少し必要だ」と思って、自分で書くことにしました。 したがって、2つのタグが
list
visible
れ、
#{a==1?1:-1}
ような形式が作成されました。 正規表現を使用してタグを検索し、
String.format
を検索する必要があり
String.format
。 そして一年半、このエンジンはその目的を完全に果たしました-軽快なものより速く、信頼できるものより信頼性がありました。
「そして、私たちは常に十分ではありません...」
それは残念ですが、私たちはそのように準備されています-まあ、少なくとも私はそうです。 html / javascript messを使用せずに、さらに高速で、さらに拡張性を高めたいと思いました。 その瞬間、私は確かに知っていました。カスタムタグが必要です— プレースホルダーなしで同じhtml構造を10回書かないように(家に挿入した後、そこにレンダリングします) 。 そして今、私が座って追加のタグを処理するためにパーサーを終了するとすぐに、それにもかかわらず、先延ばしになり始めたアートへの欲求が目覚めました-「 書き換えてください。コードを書き換えてください。 CoffeeScript Sass / Less ZenCodingを見たことがありますか?彼らが言う人に書き直してください、そうでなければ、私はあなたを眠らせません-ただ知っています。」 そして、このプレッシャーの下では抵抗できませんでした。 それでも、主な優先事項はエンジンの速度でした-美しいエンジンは必要ありませんが、100馬力です。 開始時の車。
ビジネスに取りかかりましょう:ウッド
独自の構文を使用しているため、ツリーに変換する必要があります。 ノードには、Tag =
{tagName:'someTagName', attr: { key:'value'}, nodes:[] }
、Literal =
{content:'Some String'}
2つのタイプがあります。 そして、古いテンプレートエンジンを使用して1.5年間、タグまたは属性名のテンプレートデータを置き換えることを決して覚えていないので、テンプレートとアナライザーを簡素化するために、それらにのみデータを挿入できるようにします。 したがって、ノードは次の形式になります
{tagName:'name', attr: { key:('value'||function)}, nodes:[] }
:=
{tagName:'name', attr: { key:('value'||function)}, nodes:[] }
およびLiteral:=
{content:('Some String'||function)}
。
function
は、テンプレートデータを置き換える
function
あり、テンプレートデータを必要とする値にのみ使用されます。 そこで、私たちは木を植えました。これまでのところ、複雑なものは何もありません( これ以上複雑になることはありません )。
アナライザー/パーサー
興味深い点:
- 可能であれば線形に分析し、セクションを飛び越えてさらに「サブストリング」を探します
- 最小限の関数呼び出しを使用し、再帰は一切使用しません
- 分析のために、オブジェクトを使用します:
var T = {index:0 /* currentIndex */, length:template.length, template:template}
。 追加の関数を呼び出すとき、それを渡します(または、むしろ、それへのリンクが渡されます)。 この方法では、template string
コピーされません - charCodeAt-charAtまたは[]よりも特に高速ではありませんが、それ以上のロボットは高速です
解析自体は非常に簡単です-40行です。 (属性解析は別の関数として取り出す必要はありませんが、可視性は失われます)
コードの一部
var current = T; for (; T.index < T.length; T.index++) { var c = T.template.charCodeAt(T.index); switch (c) { case 32: //" " continue; case 39: // "'" T.index++; var content = T.sliceToChar("'"); // sliceToChar indexOf 'escape character' // . indexOf , , charCodeAt/charAt/[] if (~content.indexOf('#{')) content = T.serialize == null ? this.toFunction(content) : { template: content }; current.nodes.push({ content: content }); if (current.__single) { // , , ; div > ul > li > span > 'Some' if (current == null) continue; do (current = current.parent) while (current != null && current.__single != null); } continue; case 62: /* '>' */ current.__single = true; continue; case 123: /* '{' */ continue; case 59: /* ';' */ case 125: /* '}' */ if (current == null) continue; // ; , } - do(current = current.parent) while (current != null && current.__single != null); continue; } // - tag var start = T.index; do(c = T.template.charCodeAt(++T.index)) while (c !== 32 && c !== 35 && c !== 46 && c !== 59 && c !== 123); /** while !: ' ', # , . , ; , { */ var tag = { tagName: T.template.substring(start, T.index), parent: current }; current.nodes.push(tag); current = tag; this.parseAttributes(T, current); // ; > {, T.index--; }
コンストラクター
ツリーがあるため、ドキュメントに挿入する
html string
を作成する価値はありません。すぐに
documentFragment
作成する必要があり
documentFragment
(ただし、
function renderHtml
も残して
function renderHtml
ます) 。 これにより、解析に費やされた時間が大幅に補正されます。
プロセス自体も簡単です。
コードピース
function buildDom(node, values, container) { if (container == null) container = document.createDocumentFragment(); if (node instanceof Array) { for (var i = 0, length = node.length; i < length; i++) buildDom(node[i], values, container); return container; } if (CustomTags.all[node.tagName] != null) { var custom = new CustomTags.all[node.tagName](); for (var key in node) custom[key] = node[key]; custom.render(values, container); return container; } if (node.content != null) { // container.appendChild(document.createTextNode(typeof node.content === 'function' ? node.content(values) : node.content)); return container; } var tag = document.createElement(node.tagName); for (var key in node.attr) { var value = typeof node.attr[key] == 'function' ? node.attr[key](values) : node.attr[key]; if (value) tag.setAttribute(key, value); } if (node.nodes != null) { buildDom(node.nodes, values, tag); } container.appendChild(tag); return container; }
カスタムコントロール
上記のコードからわかるように、カスタムコントロールがシーンに入ります。 コンストラクターは、登録済みのタグハンドラーを検出すると、ハンドラーオブジェクトを作成し、
attr
および
nodes
値の
shallow copy
、アセンブリコンテキストをrender関数に渡します。 つまり、コントロールは
.render(currentValues, container)
関数
.render(currentValues, container)
を実装する必要があります
toFunction(templateString)
実際、ここで魔法が起こります。 確かに、あなたはそれを魔法と呼ぶことはできませんが、私はそれがとても欲しいです。 実際、ここではデータを挿入するためのパーツを取得しています。
- または入力データのプロパティのキー、「再帰」
#{obj.other.value}
- または、トランスフォーマ関数
#{fnName:line}
呼び出します。 fnNameが空の文字列の場合、行は条件であり、ValueUtilities.condition(line, values)
関数によって満たされるとValueUtilities.condition(line, values)
れます
、それらを処理し、テンプレートに貼り付けます。
まあ、すべてが照らされているようです。 より興味深い例では、コントロールを介したバインダーの日付の実装があります。 サンプルページのソースコードも参照してください。
例
ソースコード
オフトピック:
武器庫にはさらに多くの「大きなもの」があります。たとえば、IncludeJS-Requireに似ていますが、独自の「グッズ」があります。 そのようなことに興味がある場合(本番用のリリースライブラリではありません)、githubに投稿して記事を書きます。
頑張って