> 웹 프론트엔드 > JS 튜토리얼 > 프런트 엔드 템플릿이란 무엇입니까? 프런트엔드 템플릿의 원리와 예시 소개

프런트 엔드 템플릿이란 무엇입니까? 프런트엔드 템플릿의 원리와 예시 소개

不言
풀어 주다: 2018-09-04 10:35:22
원래의
6785명이 탐색했습니다.

프런트 엔드 템플릿이란 무엇입니까? 프런트 엔드 템플릿을 구현하는 방법은 무엇입니까? 많은 친구들이 이에 대해 잘 알지 못할 수도 있으므로 다음 기사에서는 프런트 엔드 템플릿의 원칙과 간단한 구현 코드를 소개합니다.

프런트엔드 템플릿 개발

템플릿은 프론트엔드 개발에서 가장 일반적으로 사용되는 도구 중 하나라고 할 수 있습니다. 페이지의 고정 콘텐츠를 템플릿으로 추출하고, 서버에서 반환한 동적 데이터를 템플릿의 예약된 피트에 채운 다음, 마지막으로 이를 완전한 페이지 HTML 문자열로 조합하여 구문 분석을 위해 브라우저에 제공합니다.

템플릿은 개발 효율성을 크게 향상시킬 수 있습니다. 템플릿이 없으면 개발자는 문자열을 수동으로 입력해야 할 수도 있습니다.

var tpl = &#39;<p>&#39; + user.name + &#39;</p>&#39;;
$(&#39;body&#39;).append(tpl);
로그인 후 복사

최근 프론트엔드 개발 과정에서 템플릿도 바뀌었습니다:

1. PHP 템플릿 JSP 템플릿

초창기에는 프론트엔드와 백이 분리되는 시대가 없었습니다. -end. 프론트엔드는 백엔드 프로젝트의 폴더일 뿐이었습니다. 당시의 PHP와 Java는 모두 자체 템플릿 엔진을 제공했습니다. JSP를 예로 들어 보겠습니다. Java 웹 애플리케이션의 페이지는 일반적으로 .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 템플릿에 이르기까지 템플릿은 점점 더 구문이 단순해지고 기능이 풍부해지고 있지만 기본 기능은 필수입니다.

    변수 출력(escaped/unescaped): 보안상의 이유로 템플릿은 기본적으로 변수의 문자열을 이스케이프하여 출력합니다. 물론 unescaped 출력 기능도 구현하므로 주의해서 사용하세요.
  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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿