目次
フロントエンドテンプレートの開発
JSPからvueテンプレートに至るまで、テンプレートの構文はますますシンプルになり、機能が豊富になってきていますが、基本的な関数は不可欠です:
タイトルにあるように、テンプレートは本質的にプレーンテキスト文字列です。文字列は js プログラムでどのように動作するのでしょうか。
次に、口ひげテンプレートを例として、基本的な機能を実装するテンプレートを手動で実装します。
テンプレートエンジンはテンプレート文字列を取得しますその後、通常は正規表現を使用して文字列を切り取り、静的な文字列とコンパイルが必要なコード ブロックを区別し、抽象構文ツリー (AST) を生成します。
ASTを生成した後は、通常の文字列は管理する必要がなくなり、最終的にはテンプレート構文の分類に焦点を当てて直接出力されます。
3. 变量查找与赋值
4. 节点的条件渲染与嵌套
5. 绘制页面
总结
ホームページ ウェブフロントエンド jsチュートリアル フロントエンドテンプレートとは何ですか?フロントエンドテンプレートの原理と例の紹介

フロントエンドテンプレートとは何ですか?フロントエンドテンプレートの原理と例の紹介

Sep 04, 2018 am 10:35 AM
html5 javascript テンプレート

フロントエンドテンプレートとは何ですか?フロントエンドテンプレートを実装するにはどうすればよいですか?これについてはあまり知らない友人も多いかもしれないので、次の記事でフロントエンド テンプレートの原則と簡単な実装コードを紹介します。

フロントエンドテンプレートの開発

テンプレートは、フロントエンド開発において最も一般的に使用されるツールの1つと言えます。ページの固定コンテンツをテンプレートに抽出し、サーバーから返された動的データをテンプレート内の予約されたスペースに埋め込み、最後にそれを完全なページ HTML 文字列に組み立てて、解析のためにブラウザに渡します。

テンプレートを使用すると、開発効率が大幅に向上します。テンプレートがないと、開発者は文字列を手動で入力する必要がある場合があります。

var tpl = &#39;<p>&#39; + user.name + &#39;</p>&#39;;
$(&#39;body&#39;).append(tpl);

近年のフロントエンド開発の過程で、テンプレートも変化しました:

1. PHP テンプレート JSP テンプレート

初期の頃は、フロントエンドとバックが分離されている時代はありませんでした。フロントエンドはバックエンド プロジェクト内の単なるフォルダーでした。当時の PHP と Java は両方とも独自のテンプレート エンジンを提供していました。 JSP を例に挙げます。通常、Java Web アプリケーションのページは .jsp ファイルです。このファイルの内容はほとんどが HTML であり、一部のテンプレートには独自の構文がありますが、これは HTML でも Java でもありません。

JSP 構文:index.jsp

<html>
<head><title>Hello World</title></head>
<body>
Hello World!<br/>
<%
out.println("Your IP address is " + request.getRemoteAddr());
%>
</body>
</html>

この時代のテンプレート エンジンは、多くの場合、サーバーを使用してテンプレート文字列をコンパイルし、クライアント用の HTML 文字列を生成します。

2. ハンドルバーヒゲユニバーサルテンプレート

Node は 2009 年にリリースされ、JavaScript を使用してサーバー側関数を実装することもできるため、開発者も非常に容易になります。口ひげとハンドルバーのテンプレートの誕生により、どちらのテンプレートも JavaScript を使用して実装されるようになりましたが、このフロントエンド テンプレートはサーバーまたはクライアントで実行できますが、ほとんどの使用シナリオは JS データに基づいています。サーバーによって非同期的に取得されたデータがテンプレートに挿入され、新しい DOM 挿入ページ番号が生成されます。 フロントエンド開発とバックエンド開発の両方にとって非常に有益です。 mustache 構文:index.mustache

<p>Username: {{user.name}}</p>
{{#if (user.gender === 2)}}
    <p>女</p>
{{/if}}

3. React の vue JSX のテンプレート


次世代の vue のテンプレート記述方法は、以前のテンプレートとは異なり、より強力です。クライアントとサーバーの両方で使用できますが、使用シナリオは大きく異なります。データに応じてページが変更されることが多く、テンプレートによって生成される DOM も変更されるため、テンプレートには高いパフォーマンスが必要です。

vue構文:index.vue

<p>Username: {{user.name}}</p>
<template v-if="user.gender === 2">
    <p>女</p>
</div>

テンプレートによって実装される関数

JSPからvueテンプレートに至るまで、テンプレートの構文はますますシンプルになり、機能が豊富になってきていますが、基本的な関数は不可欠です:

    変数出力 (エスケープ/アンエスケープ): セキュリティ上の理由から、テンプレートは基本的に変数の文字列をエスケープして出力します。 もちろん、エスケープなしで出力する機能も実装されているため、注意して使用してください。
  1. 条件判断(if else): 開発でよく必要となる機能です。
  2. ループ変数: 配列をループし、繰り返しコード スニペットを多数生成します。
  3. テンプレートのネスト: テンプレートのネストを使用すると、多くの重複コードを削減でき、ネストされたテンプレートはスコープを統合します。
  4. 上記の関数は基本的に、ほとんどのテンプレートの基本関数をカバーしています。これらの基本関数については、テンプレートの実装方法を調べることができます。

テンプレート実装の原則

タイトルにあるように、テンプレートは本質的にプレーンテキスト文字列です。文字列は js プログラムでどのように動作するのでしょうか。

テンプレートの使用法:

var domString = template(templateString, data);

テンプレート エンジンは、テンプレート文字列とテンプレートのスコープを取得し、コンパイル後に完全な DOM 文字列を生成します。

ほとんどのテンプレートの実装原則は基本的に同じです:

テンプレート文字列は、まずさまざまな方法で通常の文字列とテンプレート構文文字列を分離し、抽象構文ツリー AST を生成します。その後、テンプレート構文フラグメントをコンパイルし、その間にテンプレート変数が削除されます。エンジンは入力変数を検索し、テンプレート構文フラグメントは通常の HTML フラグメントを生成し、出力用に元の通常の文字列と結合されます。

実際、テンプレートのコンパイル ロジックは特に複雑ではありません。データを動的にバインドする Vue などのテンプレートについては、時間があれば記事の最後にあるリンクを参照してください。

簡単なテンプレートをすばやく実装する

次に、口ひげテンプレートを例として、基本的な機能を実装するテンプレートを手動で実装します。

テンプレート文字列テンプレート:index.txt

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Page Title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
  <script src="main.js"></script>
</head>
<body>
  <h1>Panda模板编译</h1>
  <h2>普通变量输出</h2>
  <p>username: {{common.username}}</p>
  <p>escape:{{common.escape}}</p>
  <h2>不转义输出</h2>
  <p>unescape:{{&common.escape}}</p>
  <h2>列表输出:</h2>
  <ul>
  {{#each list}}
    <li class="{{value}}">{{key}}</li>
  {{/each}}
  </ul>
  <h2>条件输出:</h2>
  {{#if shouldEscape}}
    <p>escape{{common.escape}}</p>
  {{else}}
    <p>unescape:{{&common.escape}}</p>
  {{/if}}
</body>
</html>

テンプレート対応データ:

module.exports = {
  common: {
    username: &#39;Aus&#39;,
    escape: &#39;<p>Aus</p>&#39;
  },
  shouldEscape: false,
  list: [
    {key: &#39;a&#39;, value: 1},
    {key: &#39;b&#39;, value: 2},
    {key: &#39;c&#39;, value: 3},
    {key: &#39;d&#39;, value: 4}
  ]
};

テンプレートの使用方法:

var fs = require("fs");
var tpl = fs.readFileSync(&#39;./index.txt&#39;, &#39;utf8&#39;);
var state = require(&#39;./test&#39;);
var Panda = require(&#39;./panda&#39;);

Panda.render(tpl, state)

次に、テンプレートを実装します:

1. 定期的に文字列をカットします

テンプレートエンジンはテンプレート文字列を取得しますその後、通常は正規表現を使用して文字列を切り取り、静的な文字列とコンパイルが必要なコード ブロックを区別し、抽象構文ツリー (AST) を生成します。

// 将未处理过的字符串进行分词,形成字符组tokens
Panda.prototype.parse = function (tpl) {
  var tokens = [];
  var tplStart = 0;
  var tagStart = 0;
  var tagEnd = 0;

  while (tagStart >= 0) {
    tagStart = tpl.indexOf(openTag, tplStart);
    if (tagStart < 0) break;
    // 纯文本
    tokens.push(new Token(&#39;text&#39;, tpl.slice(tplStart, tagStart)));

    tagEnd = tpl.indexOf(closeTag, tagStart) + 2;
    if (tagEnd < 0) throw new Error(&#39;{{}}标签未闭合&#39;);
    // 细分js

    var tplValue = tpl.slice(tagStart + 2, tagEnd - 2);
    var token = this.classifyJs(tplValue);
    tokens.push(token);

    tplStart = tagEnd;
  }

  // 最后一段
  tokens.push(new Token(&#39;text&#39;, tpl.slice(tagEnd, tpl.length)));

  return this.parseJs(tokens);
};

文字列を分割するこのステップは、通常、正規表現を使用して行われます。正規メソッドは、後で文字列を取得するときに広範囲に使用されます。

このステップでは、通常、テンプレート タグが異常に閉じられていることを確認し、エラーを報告できます。

2. テンプレート構文の分類

ASTを生成した後は、通常の文字列は管理する必要がなくなり、最終的にはテンプレート構文の分類に焦点を当てて直接出力されます。

// 专门处理模板中的js
Panda.prototype.parseJs = function (tokens) {
  var sections = [];
  var nestedTokens = [];
  var conditionsArray = [];
  var collector = nestedTokens;
  var section;
  var currentCondition;

  for (var i = 0; i < tokens.length; i++) {
    var token = tokens[i];
    var value = token.value;
    var symbol = token.type;

    switch (symbol) {
      case &#39;#&#39;: {
        collector.push(token);
        sections.push(token);

        if(token.action === &#39;each&#39;){
          collector = token.children = [];
        } else if (token.action === &#39;if&#39;) {
          currentCondition = value;
          var conditionArray;
          collector = conditionArray = [];
          token.conditions = token.conditions || conditionsArray;

          conditionsArray.push({
            condition: currentCondition,
            collector: collector
          });
        }
        break;
      }
      case &#39;else&#39;: {
        if(sections.length === 0 || sections[sections.length - 1].action !== &#39;if&#39;) {
          throw new Error(&#39;else 使用错误&#39;);
        }

        currentCondition = value;
        collector = [];

        conditionsArray.push({
          condition: currentCondition,
          collector: collector
        });

        break;
      }
      case &#39;/&#39;: {
        section = sections.pop();

        if (section && section.action !== token.value) {
          throw new Error(&#39;指令标签未闭合&#39;);
        }

        if(sections.length > 0){
          var lastSection = sections[sections.length - 1];
          if(lastSection.action === &#39;each&#39;){
            collector = lastSection.chidlren;
          } else if (lastSection.action = &#39;if&#39;) {
            conditionsArray = [];
            collector = nestedTokens;
          }
        } else {
          collector = nestedTokens;
        }

        break;
      }
      default: {
        collector.push(token);
        break;
      }
    }
  }

  return nestedTokens;
}

前のステップで AST を生成しました。この AST は単語分割トークンの配列です:

[
    Token {},
    Token {},
    Token {},
]

このトークンは、トークン タイプ、アクション、サブトークン、条件付きトークン、その他の情報を記録する各文字列です。 。

/**
 * token类表示每个分词的标准数据结构
 */
function Token (type, value, action, children, conditions) {
  this.type = type;
  this.value = value;

  this.action = action;
  this.children = children;
  this.conditions = conditions;
}

在这一步要将循环方法中的子token嵌套到对应的token中,以及条件渲染子token嵌套到对应token中。

这步完成之后,一个标准的带有嵌套关系的AST完成了。

3. 变量查找与赋值

现在开始根据token中的变量查找到对应的值,根据相应功能生成值得字符串。

/**
 * 解析数据结构的类
 */
function Context (data, parentContext) {
  this.data = data;
  this.cache = { &#39;.&#39;: this.data };
  this.parent = parentContext;
}

Context.prototype.push = function (data) {
  return new Context(data, this);
}

// 根据字符串name找到真实的变量值
Context.prototype.lookup = function lookup (name) {
  name = trim(name);

  var cache = this.cache;

  var value;
  // 查询过缓存
  if (cache.hasOwnProperty(name)) {
    value = cache[name];
  } else {
    var context = this, names, index, lookupHit = false;

    while (context) {
      // user.username
      if (name.indexOf(&#39;.&#39;) > 0) {
        value = context.data;
        names = name.split(&#39;.&#39;);
        index = 0;

        while (value != null && index < names.length) {
          if (index === names.length - 1) {
            lookupHit = hasProperty(value, names[index]);
          }

          value = value[names[index++]];
        }
      } else {
        value = context.data[name];
        lookupHit = hasProperty(context.data, name);
      }

      if (lookupHit) {
        break;
      }

      context = context.parent;
    }

    cache[name] = value;
  }

  return value;
}

为了提高查找效率,采用缓存代理,每次查找到的变量存储路径方便下次快速查找。

不同于JavaScript编译器,模板引擎在查找变量的时候找不到对应变量即终止查找,返回空并不会报错。

4. 节点的条件渲染与嵌套

这里开始讲模板语法token和普通字符串token开始统一编译生成字符串,并拼接成完整的字符串。

// 根据tokens和context混合拼接字符串输出结果
Panda.prototype.renderTokens = function (tokens, context) {
  var result = &#39;&#39;;
  var token, symbol, value;

  for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
    value = undefined;
    token = tokens[i];
    symbol = token.type;

    if (symbol === &#39;#&#39;) value = this.renderSection(token, context);
    else if (symbol === &#39;&&#39;) value = this.unescapedValue(token, context);
    else if (symbol === &#39;=&#39;) value = this.escapedValue(token, context);
    else if (symbol === &#39;text&#39;) value = this.rawValue(token);

    if (value !== undefined) result += value;
  }

  return result;
}

5. 绘制页面

页面字符串已经解析完成,可以直接输出:

Panda.prototype.render = function (tpl, state) {
  if (typeof tpl !== &#39;string&#39;) {
    return new Error(&#39;请输入字符串!&#39;);
  }

  // 解析字符串
  var tokens = this.cache[tpl] ? tokens : this.parse(tpl);
  // 解析数据结构
  var context = state instanceof Context ? state : new Context(state);
  // 渲染模板
  return this.renderTokens(tokens, context);
};

输出页面字符串被浏览器解析,就出现了页面。

以上只是简单的模板实现,并没有经过系统测试,仅供学习使用,源码传送门。成熟的模板引擎是有完整的异常处理,变量查找解析,作用域替换,优化渲染,断点调试等功能的。

总结

前端模板这块能做的东西还很多,很多框架都是集成模板的功能,配合css,js等混合编译生成解析好样式和绑定成功事件的dom。

另外实现模板的方式也有很多,本文的实现方式参考了mustache源码,模板标签内的代码被解析,但是是通过代码片段分类,变量查找的方式来执行的,将纯字符串的代码变成了被解释器执行的代码。

另外向vue这种可以实现双向绑定的模板可以抽空多看一看。

相关推荐:

javascript - 大家前端js模板是用underscore还是handlebars呢?

学习前端模板引擎 jade (一)_html/css_WEB-ITnose

以上がフロントエンドテンプレートとは何ですか?フロントエンドテンプレートの原理と例の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Stock Market GPT

Stock Market GPT

AIを活用した投資調査により賢明な意思決定を実現

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

html5を使用してファビコンをウェブサイトに追加する方法 html5を使用してファビコンをウェブサイトに追加する方法 Aug 27, 2025 am 02:35 AM

WebサイトFaviconを正しく追加するには、最初に32×32または64×64 Pixel .ico、.pngまたは.svg形式のアイコンファイルを準備し、Favicon.icoなどに名前を付け、Webサイトルートディレクトリまたは指定されたパスに配置し、HTMLタグで明確なステートメントを使用します。たとえば、PNGの異なるサイズバージョン、SVGアイコン、Apple Touchアイコンの追加など、複数の形式とデバイスを同時にサポートすることをお勧めします。最後に、キャッシュをクリアし、正常に表示されるかどうかをテストして、パスが正しく、ファイルにアクセスできることを確認します。プロセス全体では、ロード障害を回避するために、ファイル形式、パス、互換性に注意が必要です。

森林プロトコル(森林通貨)とは何ですか?それはどうですか?森林トークンの経済モデルと市場の見通し分析 森林プロトコル(森林通貨)とは何ですか?それはどうですか?森林トークンの経済モデルと市場の見通し分析 Sep 05, 2025 am 09:09 AM

Catalog ForestProtocol's birth background The innovative technology architecture of interactive tokens (Playable Tokens) CampaignOS: Turn tokens into "playable products" Launchpad and AMM: No curves, no migration, flywheels and fees: Convert usage and revenue into repurchase and destroy CampaignOS's role and value Launchpad and AMM mechanism $FOREST's token economic model Where the value of $FOREST comes from the latest price and market outlookロードマップ:テンプレートから

HTML5におけるプレースホルダー属性の目的は何ですか? HTML5におけるプレースホルダー属性の目的は何ですか? Aug 31, 2025 am 06:58 AM

placeholderattributeprovides ashorthintininpitfieldspitspientsappearswhentypingbegins;

コンテンツセキュリティポリシー(CSP)はHTML5でどのように機能しますか? コンテンツセキュリティポリシー(CSP)はHTML5でどのように機能しますか? Aug 30, 2025 am 01:29 AM

cspenhanceshtml5security bydefining strustedcontentsourcestopreventxss、clickjacking、andcodeinjection.1.itrestrictsinlinescriptscriptsandstylesbyblockingthemunless'unsafe-inline '、nonces、orhashesareused.2.itcontorlolsexternalresourcessviadirectivedirciprccriptive

HTML5テンプレートタグの使用方法 HTML5テンプレートタグの使用方法 Aug 31, 2025 am 08:23 AM

html5tagStoresinert、reusablehtmlcontent thatcanbecloned withjavascript; itremainsunrendereduntilは、プロダクティックに依存していることを作成し、プロダクトを作成することを作成します

CVE-2024-20674 |Windows Kerberosセキュリティ関数は脆弱性をバイパスします CVE-2024-20674 |Windows Kerberosセキュリティ関数は脆弱性をバイパスします Sep 02, 2025 pm 05:18 PM

0x00序文Kerberosは、これらのサイバーセキュリティの問題の解決策としてMITによって作成されました。ネットワーク上でセキュリティ検証処理を提供するクライアント/サーバーアーキテクチャです。検証を通じて、ネットワークトランザクションの送信者と受信者の身元が真実であることを保証できます。このサービスは、前後に渡されたデータの有効性(整合性)を検証し、送信中にデータを暗号化することもできます(機密性)。 0x01脆弱性の説明被害者ネットワークにアクセスできる攻撃者は、中間(MITM)攻撃または他のローカルネットワークスプーフィング技術を確立し、クライアントの被害者のコンピューターに悪意のあるKerberosメッセージを送信し、Kerberos認証サーバーのふりをすることにより、この脆弱性を活用できます。 0x02cve

HTML5 ContentEdable属性の使用方法 HTML5 ContentEdable属性の使用方法 Aug 23, 2025 am 09:55 AM

thecontentededitediTableattributemakeshtmlelementsedaittable byaddingcontenteditable = "toelementslikediv、p、orh1 – h6.2.usejavascripttoretrevecontentviainnerhtmlforformatedtexttextextextcontextcontentforplintext.3.

HTML5の記事とセクションの違いは何ですか? HTML5の記事とセクションの違いは何ですか? Aug 22, 2025 am 08:29 AM

Useforself-conteded、Indepondentlydistributablecontentlikeblogpostsorcomments;

See all articles