65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 jsRazor 进行尖端 Web 开发:替代 ASP.NET (PHP, MVC, JSP 等) 渲染的微型 JavaScript!

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2013年4月24日

CPOL

21分钟阅读

viewsIcon

75647

downloadIcon

511

jsRazor 是一种无与伦比的强大且极其简单的客户端渲染方法,它仅由两个函数组成。这个微型 JavaScript 库完全消除了对服务器页面渲染的需求,同时为您的应用程序提供了无与伦比的简洁性和灵活性。

概述

作为一名后端编程专家,我一直倾向于基于服务器端模板的渲染。然而,不久前,在使用众多客户端模板引擎并权衡所有优缺点之后,我最终决定将我的大部分 Web 应用程序切换到纯 JavaScript 驱动的模板化 UI。虽然为此有大量的现有框架,其中一些效果很好,但有几个原因促使我推出了自己的客户端模板渲染解决方案。使用其他框架时,我总觉得有一种更简单的解决方案。基于 DOM 的库会用虚拟元素和属性淹没您的 HTML 模板,使其难以阅读,而且性能会受到大量 DOM 转换的影响。客户端 MVC 和编译模板非常有趣,但要告别清晰的所见即所得 (WYSIWYG) 标记。此外,框架及其编译器本身就很大,还需要额外的框架来调试所有这些,再加上漫长的学习周期。在我看来,所有这一切都比它应该的复杂得多。而且这种复杂性很可能被那些犹豫是否使用客户端渲染并倾向于坚持使用 MVC4 Razor、PHP 和其他框架的开发人员所察觉。

我总是说“简单必须保持简单”(我的开发网站上的口号),我写这篇文章是为了分享我使用其他框架后提出的自己的客户端模板解决方案。一方面,这种新解决方案允许渲染无限复杂的输出。另一方面,它极其简单且无与伦比。它比你想象的还要简单。整个库大约只有 100 行代码,它效率超高,由两个功能原语组成,你只需花 2 分钟就能学会它。

哦,是的,我知道这听起来如何微笑 | :) 我的另一部分在我黑暗面想出这个渲染方案时也有同样的反应。那么,让我引导您了解这种方法,做一些很好的例子,并演示它如何工作。

注意:本文也发布在我的网站上。如果您愿意,可以在那里留下评论并下载所有源代码。项目仓库在 GitHub 上:http://github.com/rgubarenko/jsrazor

更新:我已对 jsRazor 进行了重要更改,使其成为终极客户端应用程序框架。我将在 5 月底发布一篇概述文章以及 jsRazor 与 AngularJS 的比较文章。有关更多详细信息,请参阅我的评论

更新2jsRazor vs. AngularJS:“Todo”和“JS Projects”jsRazor 演示!文章刚刚发布!该文章通过演示应用程序对 jsRazor 和 AngularJS 进行了实际的并排比较。


目录 

引言

通过对 JSON 和客户端模板执行 JavaScript 来生成 HTML 输出的思路并不新鲜。至少 5 年来,一直存在关于后端应用程序任务是否应仅限于提供 JSON 数据,将渲染任务留给客户端的持续讨论。在 HTML5 的 pushState() 功能问世后,许多开发人员开始越来越倾向于客户端。

我开始使用客户端时,从使用 Adobe Spry 框架中的 JSONDataSet 开始。这是一个基于 DOM 的库,对于某些任务来说效果不错,但它无法生成完全自定义的输出。然后我使用了 JavaScriptMVCKnockoutAngular,以及此列表中的其他一些。我喜欢 MVC 和编译后的 JS 模板的灵活性和可定制性。我建议您查看我列出的框架,以便了解下面的解决方案与它们的区别。然而,有一些合理的观点认为,如果(例如)客户端 MVC 语法在编程方面没有使其更简单,为什么不直接使用服务器端 MVC4 或 PHP5 来实现相同的效果呢?此外,服务器端调试要方便得多。是的,客户端框架可以做很多事情,但是为什么要费心从我们可爱的服务器端切换呢,除非有明显的编程优势?

在这篇短文中,我将展示切换实际上具有巨大的好处和许多充分的理由。我将提出一种解决方案,它确实使编程更简单、更灵活。它实际上使其几乎微不足道,因此每个了解 JavaScript 的图形设计师都可以像高级专家一样进行页面渲染。这引出了另一个有趣的问题——如果整个复杂的服务器端渲染机制可以被简单的 JavaScript 取代,我们还需要服务器页面框架(ASP、MVC、PHP 等)吗?听起来很吓人,是吧?

好的,现在让我介绍我简单的客户端模板渲染解决方案。

解决方案

假设我们的网页上有一些 JSON 数据。加载 JSON 有几个不错的选择,但最佳实践是在初始页面加载时将 JSON 数据包含在页面中(即无需额外的 Ajax 调用),然后使用异步 Ajax 按需更新部分 JSON 数据。但这里跑题了,所以我们只是假设 JSON 已经可用。

那么,现在基于这个 JSON 构建 HTML 的最佳方式是什么?用笔和纸思考之后,我得出的结论是,抽象地说,每个可能的渲染任务都可以通过两个函数原语的随机组合来完成

  • repeat - 为数组中的每个对象重复 HTML 代码片段
  • toggle - 根据布尔标志显示或隐藏 HTML 片段
我称这个解决方案为 jsRazor,因为它削减了所有冗余的复杂性。任何你能想到的疯狂 UI 设计,你都可以通过将这两个功能以不同的嵌套配置组合起来实现。

模板

我们解决方案的关键部分是模板以及 jsRazor 使用它的方式。这里应用了“简单必须保持简单”的原则:jsRazor 不进行 DOM 转换、JS 编译或任何类似的操作。模板完全被视为纯文本,所有转换都是对字符串对象进行的简单搜索-替换-插入操作。在 JS 世界中,没有任何东西能比这更高效!

典型的 jsRazor 模板如下所示

some HTML
<!--repeatfrom:name1-->
  HTML fragment to repeat 
  plus value placeholders {Value1}   
  <!--repeatfrom:name2-->
    nested HTML fragment to repeat 
    plus value placeholders {Value2}
  <!--repeatstop:name2-->
  more HTML
  <!--showfrom:name3-->
    HTML fragment to show or hide
  <!--showstop:name3-->
  more HTML here
<!--repeatstop:name1-->
more HTML here

所以,这里的一切都非常简单。为了标记用于 repeat 函数的目标片段,我们使用 <!--repeatfrom:name--><!--repeatstop:name--> 注释分隔符。对于 toggle 函数,使用 <!--showfrom:name--><!--showstop:name--> 分隔符。要输出实际值,请使用 {SomeValue} 格式的占位符。现在,将这两个简单的功能以任何可能的配置组合起来,即可获得所需的输出。例如,要实现切换上的逻辑 OR,您可以将两个或更多切换并排放置;对于逻辑 AND,您可以将一个切换嵌套到另一个中。依此类推。

注释的好处是它们不会干扰您的 HTML,也不会破坏所见即所得 (WYSIWYG) 体验。默认情况下使用注释分隔符,但您可以将它们更改为任何您喜欢的格式(请参阅下一节)。有时您希望将模板本身包装在注释中,以便设置脚本不触及模板。在这种情况下,您需要将分隔符格式更改为非注释格式。

现在让我们看看实际执行所有工作的函数。

函数

正如我之前所述,jsRazor 仅包含两个函数即可完成所有工作。我决定将它们创建为 jsrazor 命名空间中的 jQuery 插件。这只是因为我喜欢 jQuery 插件语法,但实际上没有框架依赖。虽然我将在示例中使用 jQuery,但该插件无需 jQuery 库也能正常工作。让我们看看实际的调用语法和参数。

首先,有一个设置可以用来更改分隔符格式。要使用 [repeatfrom:name] 格式而不是注释,您需要执行以下操作

$.jsrazor.settings.limiterFormat = "[{type}:{name}]";

现在我们来看看这些函数。第一个是 repeat

$.jzrazor.repeat(template, name, items[, render])  
描述 重复模板中由分隔符包围的片段。
Returns 最终渲染的内容
参数
  • template (类型: string): 包含要重复片段的纯文本模板。
  • name (类型: string): 识别片段分隔符的名称令牌,例如 <!--repeatfrom:name--><!--repeatstop:name-->
  • items (类型: Array): 迭代器将遍历的任何类型的对象数组。
  • render (类型: Function(string tmp, Number idx, Object item)): 可选的渲染回调,在每次迭代时调用,以返回当前 item 的渲染内容。参数 tmp 包含当前 item 的模板。参数 idxitems 数组中 item 的位置,该数组已传递给原始函数调用。如果当前 item 是 JSON 对象,则其 stringNumber 类型的属性值将输出到 {<property-name>} 占位符。如果当前 itemstringNumber 类型,则其值将输出到 {item} 占位符。请注意,属性的自动输出到占位符是在渲染回调返回后由插件完成的。

换句话说,在 render 回调中,您处理 tmp 中传递的内容并返回当前 item 的所需输出。所有 item 属性都会自动输出到 {<property-name>} 占位符。要插入自定义值,我建议使用 {SomeValue} 格式的占位符,并通过 string.replace(..) 函数替换,但您可以根据自己的喜好选择任何格式。回调内部的处理没有限制,因此您可以实现任何您喜欢的自定义输出。

第二个函数是 toggle:

$.jzrazor.toggle(template, name, flag)   
描述 显示或隐藏模板中被分隔符包围的片段
Returns 最终渲染的内容
参数
  • template (类型: string): 包含要切换的片段的纯文本模板。
  • name (类型: string): 标识片段分隔符的名称令牌,例如 <!--showfrom:name--><!--showstop:name-->
  • flag (类型: Boolean): 应为 true 以显示片段,为 false 以隐藏它。

这里一切都很简单。如果 flagtrue,则片段保留(分隔符被移除)。否则,片段和分隔符都会被移除。

现在让我们思考一下。两个 JavaScript 函数取代了整个 ASP.NET、PHP、JSP 等渲染机制!所有那些服务器控件、控制流语句、绑定表达式、页面生命周期等等突然变得多余了。就三层 Web 应用程序设计而言,整个表示层都转移到了客户端。服务器端只剩下数据层和业务逻辑,只需要生成 JSON 数据流。

听起来不错,不是吗?现在是时候看看这一切的实际效果了!

示例

我将使用与 Adobe Spry 演示中使用的相同的 JSON 数据——我希望他们不介意 微笑 | :) 所以,假设 JSON 是在后端从我们的数据层创建并交付到客户端的。这里有一些很好的例子。

示例 1

假设我们有一个描述颜色的简单 JSON 对象数组

var data_Colors =
[
  {
    color: "red",
    value: "#f00"
  },
  {
    color: "green",
    value: "#0f0"
  },
  // ...
];

我们希望将颜色输出为逗号分隔的名称,后跟括号中的十六进制值。这是所需的输出

ex1

我两次输出相同的内容,只是为了演示稍微不同的 JavaScript 语法。

<div class="cont-ex1">
  <div>
    1) <!--repeatfrom:colors1-->{color} ({value}), <!--repeatstop:colors1-->
  </div>
  <div>
    2) <!--repeatfrom:colors2-->{color} ({value}), <!--repeatstop:colors2-->
  </div>
</div>

这里有 2 个并排的重复器:colors1colors2。它们都将做同样的事情,但代码略有不同,只是为了向您演示一个功能。现在让我们看看实际的 JavaScript

1: var $cont = $(".cont-ex1");
2: {
3:   var output = $cont.html();
4:   output = $.mksft.repeat(output, "colors1", data_Colors); // most basic default repeater
5:   output = $.mksft.repeat(output, "colors2", data_Colors, function (tmp, idx, item) { return tmp; });
6:   $cont.html(output);
7: }

我们在这里所做的就是从某个元素的内部 HTML 中获取模板,对其进行处理,然后将结果插回。我使用 jQuery 来处理内部 HTML,但是,正如我前面提到的,插件本身不依赖于任何框架。

在我们的案例中,模板是从 <div class="cont-ex1"> 容器的内部 HTML 中获取并分配给 output 变量的。首先,我们在第 4 行对其应用 colors1 重复器。请注意,可选的 render 回调已省略,因此没有自定义处理,只有基于 JSON 对象属性的自动占位符替换。在模板中,我们使用了 {color}{value} 占位符,它们将在每次迭代中被当前项的 colorvalue 属性替换。

第 5 行,我们应用了第二个 colors2 重复器。唯一的区别是这次我包含了 render 回调。这个回调所做的,只是返回当前项模板而没有修改,这与 colors1 重复器省略回调本质上是相同的。

示例 2

再举一个基本示例。假设我们有一个值数组

var data_Numbers = [100, 500, 300, 200, 400];

我们希望它们按以下方式输出

ex2

模板很简单

<div class="cont-ex2">
  <div><!--repeatfrom:numbers-->{item}, <!--repeatstop:numbers--></div>
</div> 

JavaScript 代码更简单

var $cont = $(".cont-ex2");
{
  var output = $.mksft.repeat($cont.html(), "numbers", data_Numbers);
  $cont.html(output);
}

这里不需要 render 回调。回想一下 API,当当前项是 stringNumber 类型时,其值会自动输出到 {item} 占位符中。完成。

示例 3

在此示例中,我们进行一些自定义处理。我们的数据仍然是相同的数组,但这次,对于数组中的每个数字 N,我们希望输出 N-1<N<N+1。这是所需的输出

ex3

对于 N-1N+1 值,我们将需要自定义占位符。我们不希望在最后一个项目后面出现逗号,就像我们之前的示例一样。为了实现这一点,我们将需要使用片段切换。这是模板

<div class="cont-ex3">
  <div>
    <!--repeatfrom:numbers-->
    {ItemL}&lt;{item}&lt;{ItemM}<!--showfrom:not-last-->, <!--showstop:not-last-->
    <!--repeatstop:numbers-->
  </div>
</div>

在此模板中,我们有 {ItemL}{ItemM} 额外的占位符。此外,逗号被包装在 not-last 切换中,并且只在当前项不是数组中最后一个时显示。现在让我们看看代码

1: var $cont = $(".cont-ex3");
2: {
3:   var output = $.mksft.repeat($cont.html(), "numbers", data_Numbers, function (tmp, idx, item)
4:   {
5:     // toggle conditional area
6:     tmp = $.mksft.toggle(tmp, "not-last", idx < data_Numbers.length - 1);
7:     // custom placeholders
8:     tmp = tmp
9:       .replace(/{ItemL}/g, (item - 1))
10:       .replace(/{ItemM}/g, (item + 1));
11: 
12:     return tmp;
13:   });
14: 
15:   $cont.html(output);
16: }

好的,现在更有趣了,因为 render 回调中有一些处理!在第 3 行,我们像以前一样应用 numbers 重复器。然后,render 回调需要返回每个 item 的渲染结果。在第 6 行,我们将 not-last 切换应用于包含逗号的片段。回想一下,第 3 个参数是 flag,它需要为 true 才能显示片段,因此在我们的例子中,当 item 不是数组中的最后一个时,它为 true。然后,在第 8-10 行,我们通过简单的 string.replace(..) 操作输出我们的自定义占位符。item 的值等于输入数组中的当前项,即在我们的例子中它是一个整数。就是这样。

示例 4

今天最后一个例子是包含多个嵌套数组的复杂 JSON,需要嵌套重复器来输出它们。我们将需要将它们与几个片段切换结合起来,以实现更自定义的输出。这是一个描述甜甜圈菜单的 JSON

var data_Donuts =
{
  "donuts":
    [
      {
        "id": "0001",
        "type": "donut",
        "name": "Cake",
        "ppu": 0.55,
        "batters":
          [
            { "id": "1001", "type": "Regular" },
            { "id": "1002", "type": "Chocolate" },
            { "id": "1003", "type": "Blueberry" },
            { "id": "1004", "type": "Devil's Food" }
          ],
        "toppings":
          [
            { "id": "5001", "type": "None" },
            { "id": "5002", "type": "Glazed" },
            { "id": "5005", "type": "Sugar" },
            { "id": "5007", "type": "Powdered Sugar" },
            { "id": "5006", "type": "Chocolate with Sprinkles" },
            { "id": "5003", "type": "Chocolate" },
            { "id": "5004", "type": "Maple" }
          ]
      },
      // ...
      {
        "id": "0004",
        "type": "bar",
        "name": "Bar",
        "ppu": 0.75,
        "batters":
          [
            { "id": "1001", "type": "Regular" }
          ],
        "toppings":
          [
            { "id": "5003", "type": "Chocolate" },
            { "id": "5004", "type": "Maple" }
          ],
        "fillings":
          [
            { "id": "7001", "name": "None", "addcost": 0 },
            { "id": "7002", "name": "Custard", "addcost": 0.25 },
            { "id": "7003", "name": "Whipped Cream", "addcost": 0.25 }
          ]
      },
      // ...
    ]
};

这是我们想要达到的输出

ex4

这是实现此目的的模板

1: <div class="cont-ex4">
2:   <ul>
3:     <!--repeatfrom:donuts-->
4:     <li>[{id}] | {type} | <b>{name}</b> | ${ppu} 
5:       <ul>
6:         <li>Batters
7:           <ul>
8:             <!--repeatfrom:batters-->
9:             <li>[{id}] {type}</li>
10:             <!--repeatstop:batters-->
11:           </ul>
12:         </li>
13:         <li>Toppings
14:           <!--showfrom:has-chocolate-->
15:           <span style="color:brown;">
16:              {CountChocolate} chocolate toppings available
17:           </span>
18:           <!--showstop:has-chocolate-->
19:           <ul>
20:             <!--repeatfrom:toppings-->
21:             <li>[{id}] {type}</li>
22:             <!--repeatstop:toppings-->
23:           </ul>
24:         </li>
25:         <!--showfrom:has-fillings-->
26:         <li>Fillings
27:           <ul>
28:             <!--repeatfrom:fillings-->
29:             <li>[{id}] {name}
30:               <!--showfrom:cost-extra-->
31:               <span style="color:red;font-weight:bold;">+${addcost} = ${NewPPU}</span>
32:               <!--showstop:cost-extra-->
33:             </li>
34:             <!--repeatstop:fillings-->
35:           </ul>
36:         </li>
37:         <!--showstop:has-fillings-->
38:       </ul>
39:     </li>
40:     <!--repeatstop:donuts-->
41:   </ul>
42: </div>

因此,为了输出我们的分层数据,我们希望使用嵌套的 HTML 列表。第一层使用第 3-40 行donuts 重复器输出甜甜圈及其直接属性。第二层输出面糊(第 8-10 行)、配料(第 20-22 行)和馅料(第 28-34 行)数组。有些甜甜圈没有馅料,在这种情况下,我们希望什么都不显示,而不是显示“馅料”标题和空列表。为了实现这一点,我们将整个馅料部分包含在第 25-37 行has-fillings 切换中。接下来,有些馅料会增加每单位价格的额外成本。在这种情况下,我们希望使用“+$X=$X1”格式以红色字体输出额外金额,其中 X 是额外成本,X1 是更新后的每单位价格。为此,我们有第 30-32 行cost-extra 切换和自定义 {NewPPU} 占位符。最后,如果有任何巧克力配料可用,我们希望显示一条消息,例如“有 2 种巧克力配料”。为此,我们有第 14-18 行的另一个 has-chocolate 切换和 {CountChocolate} 占位符。这就是模板的全部内容!现在让我们看看代码

1: var $cont = $(".cont-ex4");
2: {
3:   // first level repeater for all donuts
4:   var output = $.mksft.repeat($cont.html(), "donuts", data_Donuts.donuts, function (tmp, idx, item)
5:   {
6:     // default repeaters for batters and toppings
7:     tmp = $.mksft.repeat(tmp, "batters", item.batters);
8:     tmp = $.mksft.repeat(tmp, "toppings", item.toppings);
9: 
10:     // display fillings only if there any
11:     tmp = $.mksft.toggle(tmp, "has-fillings", !!item.fillings);
12:     // render fillings list
13:     if (item.fillings)
14:     {
15:       var ppu = item.ppu; // save ppu for child value scope
16:       // custome repeater for fillings to display extra cost and new PPU
17:       tmp = $.mksft.repeat(tmp, "fillings", item.fillings, function (tmp, idx, item)
18:       {
19:         // display price warning if there is additional cost involved
20:         tmp = $.mksft.toggle(tmp, "cost-extra", item.addcost > 0);
21:         // custom placeholder to display new PPU value
22:         tmp = tmp.replace(/{NewPPU}/g, ppu + item.addcost);
23:         return tmp;
24:       });
25:     }
26: 
27:     // display number of chocolate toppings
28:     var countChoco = 0; // count number of chocolate toppings
29:     $.each(item.toppings, function (idx, item) { if (/chocolate/i.test(item.type)) countChoco++; });
30:     tmp = $.mksft.toggle(tmp, "has-chocolate", countChoco > 0); // show/hide chocolate message
31:     tmp = tmp.replace(/{CountChocolate}/g, countChoco); // render chocolate topping counter
32: 
33:     return tmp;
34:   });
35: 
36:   $cont.html(output);
37: }

请注意,由于 JavaScript 变量作用域,我可以在所有嵌套的重复器 render 回调中重用 tmpidxitem 变量,这节省了大量的代码行。

所以,外部的 donuts 重复器在第 4 行Render 回调从第 7-8 行应用嵌套的 batterstoppings 重复器开始。我们保留它们的默认值,因为我们只需要输出属性占位符。接下来,在第 11 行,我们应用 has-fillings 切换来隐藏整个馅料区域,如果没有馅料的话。如果存在馅料,我们将在第 17 行应用 fillings 重复器来输出它们。在第 15 行,我必须将 PPU 值保存到单独的 ppu 变量中,因为 itemrender 回调或嵌套重复器中会有不同的含义。在 fillings 重复器的 render 回调内部,我从第 20 行应用 cost-extra 切换开始。然后在第 22 行,我输出 {NewPPU} 占位符的值。最后,我们需要处理巧克力配料。我们在第 28-29 行统计它们,在第 30 行应用 has-chocolate 切换,并在第 31 行输出计数。我们完成了!

我想你明白了。结合两个简单的函数,你可以根据任何结构的 JSON 实现任何复杂度的输出——仅此而已。模板尽可能简单透明。注释分隔符不干扰 HTML,使你的模板标记绝对干净,可以在任何 HTML 编辑器中查看。当你可以用极其简单的方式实现所有所需的输出时,我看不出有任何理由使用编译模板。

现在是时候总结一下我们目前所看到的一切了。

客户端解决方案总结

我不会深入探讨客户端渲染的一般优缺点,因为这已经被讨论了千百次。相反,我将主要关注 jsRazor 解决方案特有的方面。

学习周期

说真的,阅读上面的“解决方案”和“示例”部分花了多长时间?5 分钟?10 分钟?这就是您需要了解 jsRazor 的全部内容。这里不需要学习,而其他客户端渲染框架可能会变得相当复杂。例如,JS MVC 需要相当多的学习以及用于调试视图的额外框架。我甚至没有提到像 ASP.NET 这样的服务器端,在那里需要几年时间才能成为渲染专家并了解服务器控件的所有特殊之处。在这里,经过 10 分钟的教程,几乎每个 HTML 设计师都能够构建具有任何服务器端框架都无法实现的灵活性的应用程序!

开发过程

看看我们上一个例子。有了这些 JSON 数据,您需要 10 分钟来创建模板,5 分钟来编写 JavaScript,同意吗?这很简单透明。只是为了比较,您在 ASP.NET、MVC、JSP 中做同样的事情需要多长时间?您可能会使用像 DataGrid 这样的庞然大物?MVC Razor 会比经典的 ASP.NET 花费更少的时间,但仍然比 jsRazor 多得多。那么,客户端呢?编译模板和 JS MVC 可以完成工作,但语法和简单性甚至无法接近 jzRazor 解决方案。这是您可以查看的客户端 MVC 列表,以便进行比较。

开发人员技能水平

由于整体的简单性,jsRazor 渲染任务可以由具有基本 JavaScript 知识的 HTML 设计师完成。此外,要维护现有解决方案,甚至更低的技能水平也可以胜任。后端开发人员只有在需要创建新的 JSON 数据源或修改现有数据源时才会参与,这也不需要太多的框架知识。另一方面,创建一个常规的服务器页面网站从一开始就需要高级程序员,并且至少需要中级程序员来维护现有解决方案。客户端 MVC 也需要高级开发人员,尤其是对于调试代码。

架构

在应用程序架构方面,jsRazor 处处获胜!首先,如果后端仅用于生成 JSON,这自动意味着 100% 的表示层分离。其次,jsRazor 还提供了客户端分离!你看,在编译模板中,你的代码与 HTML 标记混杂在一起。然后编译器将其转换为可执行的 JavaScript。在 jsRazor 中,模板是独立的,代码也是独立的,这正是设计使然。绝对独立。所以这里是双重分离:后端分离加上前端模板/控制器分离。

其他IT专家也多次指出,客户端渲染提供了客户端-服务器应用简单性的最大理论水平。凭借简单的jsRazor模板,这一特性变得更加明显。无论您的UI功能多么复杂,在后端它始终是一堆JSON端点,而在前端它始终是一堆带有微型JavaScript控制器(如果我可以这样称呼它的话)的简单模板。

性能如何?

关于客户端渲染,我们脑海中浮现的首要问题是性能。通常我听到两个反对客户端渲染的普遍论点:初始页面加载时需要 JSON 数据调用,以及在页面显示之前需要执行大量 JavaScript。

关于初始加载时的 Ajax 调用,我之前已经提过,如果应用程序编写得当,实际上并不需要。初始 JSON 必须插入到页面中并随页面一起加载,因此无需任何额外调用即可使用,而 Ajax 应该用于更新现有 JSON 或加载额外数据。所以这里没有问题。这只是每个开发人员都必须牢记的设计考虑。

下一个问题是执行 JS 来完成页面 UI 有多糟糕?嗯,我想说,在今天,当微型智能手机都能播放全高清视频时,这种担忧变得微不足道。为了理解我们在这里谈论的是什么,让我们看看用例。我将以 ASP.NET 为例,但结果也适用于其他框架,因为就工作流而言,它们都非常相似。

页面渲染由许多步骤组成。让我们描绘从客户端请求命中页面到页面在用户浏览器中显示之间的步骤。图片如下

page-rendering-steps-1

在现实世界中还有更多步骤,但这只是一个模型。所以,请求通过 IIS 并到达 ASP.NET。然后进行一些内务管理,运行模块,设置上下文等。然后页面开始生命周期执行:通常我们从数据层获取实体并通过一些业务逻辑处理它们。然后页面被渲染(花费时间为 tR)。然后再次进行内务管理,然后通过网络将响应发送回客户端(花费时间为 tW)。到达客户端后,它会在浏览器中显示。如果使用 jsRazor 渲染(花费时间为 tR*),那么它必须在页面显示之前执行。我们将使用纯服务器端渲染的情况称为“情况 A”,将使用 jsRazor 客户端渲染的情况称为“情况 B”。

在情况 A 中,只有 tR。在情况 B 中,只有 tR*,因为 tR 完全消失了。所以,大致来说,就是比较 tRtR*,对吗?我没有在这里做任何实际的基准测试,所以我们只是理论上思考一下。为了渲染服务器页面(即 tR),ASP.NET 构建控制树,遍历它,为每个控件调用整个生命周期等等——工作量很大。然而,这都是服务器上的编译代码,所以,除非页面真的非常复杂,否则渲染应该会很快。另一方面,tR* 是由浏览器 JavaScript 完成的。jsRazor 的整个渲染过程都基于简单的字符串操作,所以它应该更高效。然而,它在浏览器中运行,JavaScript 比编译代码慢。所以,直觉上我认为 tR<tR*,但我将来会尝试进行实际的基准测试。无论如何,即使是这种情况,这仍然不是一个问题,因为它无论如何都无法肉眼可见。

如果我们进一步思考,可能会注意到情况 B 的 tW 实际上小于情况 A 的 tW。这是因为在情况 B 中,响应是带有模板和 JSON 的原始页面,而在情况 A 中,它是一个完全渲染的网页,要大得多。您的数据网格越大,两种情况下的 tW 差异就越大。因此,jsRazor 方法在这方面提供了更好的性能。对于慢速互联网连接,这实际上可能是一个很大的优势。

此外,服务器负载也有好处,因为我们只是将大部分处理从服务器上移除。我们可以进一步说,没有必要使用 ASP.NET 来生成 JSON,而是使用 Web 服务。这将进一步减轻 ASP.NET 的负载。

缓存是服务器端渲染会更快的地方。无论是 ASP.NET 页面缓存还是网络级缓存 (CDN),对于情况 B,我们只能缓存原始页面和 JSON 数据,这意味着总是涉及 tR*。在情况 A 中,我们缓存不需要任何处理的完全完成的页面。因此,在这些条件下,情况 A 将比情况 B 快 tR*。这一点值得一提,但根本不是什么大问题。再次,在 jsRazor 所做的简单 JavaScript 字符串操作的情况下,tR* 可以忽略不计。

结论

当然,某些类型的应用程序可能只要求服务器端渲染。范式转换总是难以接受的,但对于常规的 Web 2.0 应用程序(电子商务、门户、社交应用等),纯客户端渲染,特别是 jsRazor,似乎是一种更简单、更灵活的解决方案。服务器页面框架的很大一部分可以用两个微小的 JavaScript 函数取代——这不是很酷吗?

另外,考虑这方面的另一个重要方面。通常由后端开发人员完成的一部分工作,现在可以由 HTML 设计师完成!这实际上非常好,因为它意味着开发团队内部更明确的分工,以及为忙碌的高级开发团队成员节省更多时间微笑 | :)

访问jsRazor 页面获取下载链接和更多信息。如果您希望做出贡献,请访问 GitHub 仓库:http://github.com/rgubarenko/jsrazor

© . All rights reserved.