JavaScript模板引擎的实现原理
&lt;blockquote&gt;JavaScript模板引擎的核心原理是将含标记的字符串转换为可接收数据并生成HTML的函数。它通过正则解析模板中的占位符与逻辑语句,生成拼接HTML的函数体,利用new Function()创建渲染函数,实现数据与视图的高效结合,提升开发效率与代码可维护性,同时需关注编译缓存、执行性能及XSS防护等安全问题。&lt;/blockquote&gt; &lt;p&gt;&lt;img src=&quot;https://img.php.cn/upload/article/001/253/068/175836624265190.jpg&quot; alt=&quot;javascript模板引擎的实现原理&quot;&gt;&lt;/p&gt; &lt;p&gt;JavaScript模板引擎的核心原理,在我看来,其实就是把一段带有特定语法标记的字符串(也就是我们写的模板),通过一套解析机制,转换成一个能够接收数据并最终吐出渲染好的HTML字符串的JavaScript函数。这个过程,有点像编译器,把我们易读的模板代码“编译”成了机器(这里是JS运行时)能直接执行的、高效生成DOM片段的代码。它不是直接操作DOM,而是先生成一个字符串,再由&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;浏览器&quot; href=&quot;https://www.php.cn/zt/16180.html&quot; target=&quot;_blank&quot;&gt;浏览器&lt;/a&gt;解析成DOM。&lt;/p&gt; &lt;h3&gt;解决方案&lt;/h3&gt; &lt;p&gt;要深入理解JavaScript模板引擎,我们得从它“化腐朽为神奇”的几个关键步骤入手。想象一下,你写了一段HTML,里面夹杂着&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<%= name %>&lt;/pre&gt;

&lt;/div&gt;这样的占位符,或者&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<% if (user) { %>&lt;/pre&gt;

&lt;/div&gt;这样的逻辑控制。引擎拿到这段字符串后,第一件事就是解析。它会用一些预设的规则(通常是&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;正则表达式&quot; href=&quot;https://www.php.cn/zt/15947.html&quot; target=&quot;_blank&quot;&gt;正则表达式&lt;/a&gt;)去识别这些特殊的标记。&lt;/p&gt; &lt;p&gt;比如,它会把纯HTML部分原封不动地保留下来,而遇到&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<%= … %>&lt;/pre&gt;

&lt;/div&gt;这种数据输出的标记,它就知道这里需要把某个变量的值插入进来。遇到&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<% … %>&lt;/pre&gt;

&lt;/div&gt;这种逻辑控制标记,比如&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;if&lt;/pre&gt;

&lt;/div&gt;语句或者&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;for&lt;/pre&gt;

&lt;/div&gt;循环,它就明白这里要执行一段JavaScript代码。&lt;/p&gt; &lt;p&gt;解析完成后,最关键的一步来了:代码生成。引擎会把这些解析出来的片段,巧妙地拼接成一个全新的JavaScript函数体。这个函数体内部,会维护一个用于收集最终HTML字符串的变量(可能是数组,然后&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;join(”)&lt;/pre&gt;

&lt;/div&gt;,或者直接字符串拼接)。纯HTML部分会作为字符串字面量直接加入到这个变量中;数据输出部分会被转换成类似&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;_output.push(data.name)&lt;/pre&gt;

&lt;/div&gt;这样的语句;而逻辑控制部分,则会原封不动地嵌入为真正的JavaScript控制流语句。&lt;/p&gt; &lt;p&gt;&lt;span&gt;立即学习&lt;/span&gt;“&lt;a href=&quot;https://pan.quark.cn/s/c1c2c2ed740f&quot; style=&quot;text-decoration: underline !important; color: blue; font-weight: bolder;&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Java免费学习笔记(深入)&lt;/a&gt;”;&lt;/p&gt; &lt;p&gt;最后,这个动态生成的JavaScript函数,通常会通过&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;new Function()&lt;/pre&gt;

&lt;/div&gt;构造函数来创建。当我们调用这个函数,并把需要渲染的数据对象作为参数传进去时,它就会执行内部的逻辑,把数据和模板结合起来,最终返回那个我们期待已久的、完整的HTML字符串。这就是模板引擎的“黑箱”操作,一个从文本到可执行代码,再到最终文本的循环。&lt;/p&gt; &lt;h3&gt;为什么我们需要模板引擎,以及它解决了哪些痛点?&lt;/h3&gt; &lt;p&gt;在我刚开始接触&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;前端&quot; href=&quot;https://www.php.cn/zt/15813.html&quot; target=&quot;_blank&quot;&gt;前端&lt;/a&gt;开发的时候,手动拼接HTML字符串简直是噩梦。尤其是在处理动态列表或者复杂的条件渲染时,代码里充斥着大量的&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;+&lt;/pre&gt;

&lt;/div&gt;号和引号,不仅写起来费劲,维护起来更是痛苦不堪。一旦逻辑稍微复杂一点,比如一个循环里套着一个条件判断,那代码的可读性就直线下降,调试起来也让人头大。&lt;/p&gt; &lt;p&gt;模板引擎的出现,完美地解决了这些痛点。首先,它让&lt;strong&gt;视图层和数据逻辑分离&lt;/strong&gt;变得更清晰。我们可以在&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;.html&lt;/pre&gt;

&lt;/div&gt;或者&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;.tpl&lt;/pre&gt;

&lt;/div&gt;文件里专注于页面的结构和展示,而把数据的获取和处理放在JavaScript里。这大大提升了代码的可读性和可维护性。其次,它&lt;strong&gt;简化了复杂UI的构建&lt;/strong&gt;。通过模板里的循环和条件语句,我们可以用更声明式的方式来描述UI,而不用去写一堆命令式的DOM操作代码。这不仅提高了开发效率,也减少了出错的概率。&lt;/p&gt; &lt;p&gt;再者,对于一些支持&lt;strong&gt;前&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;后端&quot; href=&quot;https://www.php.cn/zt/17190.html&quot; target=&quot;_blank&quot;&gt;后端&lt;/a&gt;同构(SSR)&lt;/strong&gt;的框架来说,模板引擎是核心。同一套模板代码既可以在服务器端预渲染生成HTML,也可以在客户端进行数据绑定和更新,这对于首屏加载速度和SEO都非常有益。最后,虽然不是所有引擎都默认提供,但很多现代模板引擎会内置&lt;strong&gt;XSS(跨站脚本攻击)防护&lt;/strong&gt;机制,自动对输出内容进行转义,这无疑提升了应用的安全性。在我看来,模板引擎不仅仅是一个&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;工具&quot; href=&quot;https://www.php.cn/zt/16887.html&quot; target=&quot;_blank&quot;&gt;工具&lt;/a&gt;,它更是一种编程范式的转变,让&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;前端开发&quot; href=&quot;https://www.php.cn/zt/17277.html&quot; target=&quot;_blank&quot;&gt;前端开发&lt;/a&gt;变得更加优雅和高效。&lt;/p&gt; &lt;h3&gt;核心实现机制:从字符串到可执行函数&lt;/h3&gt; &lt;p&gt;要理解模板引擎的“魔法”,我们得深入到它如何将模板字符串转化为可执行函数的细节。这其中,正则表达式的运用是绕不开的。大部分模板引擎,无论是早期的EJS、Handlebars,还是后来的Underscore.&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;js&quot; href=&quot;https://www.php.cn/zt/15802.html&quot; target=&quot;_blank&quot;&gt;js&lt;/a&gt;模板功能,都会利用正则表达式来识别模板中的特殊标记。&lt;/p&gt; &lt;p&gt;例如,一个简单的模板引擎可能会使用这样的正则:&lt;/p&gt; &lt;div class=&quot;aritcle_card&quot;&gt; &lt;a class=&quot;aritcle_card_img&quot; href=&quot;/ai/opus&quot;&gt;&lt;img src=&quot;https://img.php.cn/upload/ai_manual/000/969/633/68b6d1b4dd832912.png&quot; alt=&quot;Opus&quot;&gt;&lt;/a&gt; &lt;div class=&quot;aritcle_card_info&quot;&gt; &lt;a href=&quot;/ai/opus&quot;&gt;Opus&lt;/a&gt; &lt;p&gt;AI生成视频工具&lt;/p&gt; &lt;div class=&quot;&quot;&gt; &lt;img src=&quot;/static/images/card_xiazai.png&quot; alt=&quot;Opus&quot;&gt;&lt;span&gt;33&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;a href=&quot;/ai/opus&quot; class=&quot;aritcle_card_btn&quot;&gt; &lt;span&gt;查看详情&lt;/span&gt; &lt;img src=&quot;/static/images/cardxiayige-3.png&quot; alt=&quot;Opus&quot;&gt;&lt;/a&gt; &lt;/div&gt; &lt;ul&gt; &lt;li&gt;&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;/<%=(.*?)%>/g&lt;/pre&gt;

&lt;/div&gt; 来匹配需要输出的变量,比如&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<%= name %>&lt;/pre&gt;

&lt;/div&gt;。&lt;/li&gt; &lt;li&gt;&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;/<%(.*?)%>/g&lt;/pre&gt;

&lt;/div&gt; 来匹配需要执行的JavaScript代码块,比如&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<% if (user) { %>&lt;/pre&gt;

&lt;/div&gt;。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;当引擎拿到模板字符串后,它会遍历整个字符串,根据这些正则匹配出不同的部分:纯文本、输出表达式和逻辑代码。然后,它会开始构建一个JavaScript函数的字符串表示。这个过程通常会涉及到一个累积结果的变量,我们称之为&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;_output&lt;/pre&gt;

&lt;/div&gt;。&lt;/p&gt; &lt;p&gt;想象一下,模板引擎在内部会做这样的事情:&lt;/p&gt;&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=’brush:javascript;toolbar:false;’&gt;// 假设这是模板引擎内部生成的函数字符串 var fnBody = &amp;quot;var _output = [];n&amp;quot;; // 初始化一个数组来收集结果 fnBody += &amp;quot;_output.push(‘<div>Hello, ‘);n&amp;quot;; // 遇到纯文本,直接push fnBody += &amp;quot;_output.push(data.name);n&amp;quot;; // 遇到 <%= name %>,转换为push(data.name) fnBody += &amp;quot;_output.push(‘!</div>’);n&amp;quot;; // 遇到 <% if (data.show) { %> fnBody += &amp;quot;if (data.show) {n&amp;quot;; fnBody += &amp;quot;_output.push(‘<p>This is visible.</p>’);n&amp;quot;; fnBody += &amp;quot;}n&amp;quot;; // 遇到 <% } %> fnBody += &amp;quot;return _output.join(”);&amp;quot;; // 最后拼接成字符串返回&lt;/pre&gt;

&lt;/div&gt;&lt;p&gt;这个&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;fnBody&lt;/pre&gt;

&lt;/div&gt;字符串,最终会被&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;new Function(‘data’, fnBody)&lt;/pre&gt;

&lt;/div&gt;这样的方式,转换成一个真正的JavaScript函数。这里的&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;’data’&lt;/pre&gt;

&lt;/div&gt;是这个函数的参数名,我们就可以通过这个参数将实际的数据传入。&lt;/p&gt; &lt;p&gt;早期的很多模板引擎,为了方便在模板内部直接访问数据对象的属性(比如直接写&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;name&lt;/pre&gt;

&lt;/div&gt;而不是&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;data.name&lt;/pre&gt;

&lt;/div&gt;),会使用JavaScript的&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;with&lt;/pre&gt;

&lt;/div&gt;语句。例如:&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;with (data) { … }&lt;/pre&gt;

&lt;/div&gt;。然而,&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;with&lt;/pre&gt;

&lt;/div&gt;语句因为其性能问题(会影响JS引擎的优化)和潜在的变量&lt;a style=&quot;color:#f60; text-decoration:underline;&quot; title=&quot;作用域&quot; href=&quot;https://www.php.cn/zt/35787.html&quot; target=&quot;_blank&quot;&gt;作用域&lt;/a&gt;混淆,在现代JavaScript开发中已经被强烈不推荐使用。所以,现在的模板引擎通常会选择显式地传递数据对象,或者在生成函数时对变量进行预处理,以避免&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;with&lt;/pre&gt;

&lt;/div&gt;的弊端。理解这个从字符串到可执行函数的转换过程,是理解模板引擎工作原理的关键。&lt;/p&gt; &lt;h3&gt;性能考量与安全性挑战&lt;/h3&gt; &lt;p&gt;在实际应用中,模板引擎的性能和安全性是两个非常重要的考量点。&lt;/p&gt; &lt;p&gt;从&lt;strong&gt;性能&lt;/strong&gt;角度看,模板引擎的工作可以分为两个阶段:编译(Parsing and Code Generation)和执行(Execution)。&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;strong&gt;编译阶段&lt;/strong&gt;的性能,主要取决于模板的大小和复杂性,以及引擎的解析和代码生成效率。对于大型应用,如果每次渲染都重新编译模板,那性能开销会非常大。因此,大多数模板引擎都会引入&lt;strong&gt;缓存机制&lt;/strong&gt;,将编译好的渲染函数存储起来。第一次编译后,后续的渲染请求可以直接使用缓存中的函数,大大提升了效率。&lt;/li&gt; &lt;li&gt; &lt;strong&gt;执行阶段&lt;/strong&gt;的性能,则取决于生成的JavaScript代码的效率。例如,频繁的字符串拼接(尤其是在老旧浏览器中)可能不如数组&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;join(”)&lt;/pre&gt;

&lt;/div&gt;高效。现代JS引擎对字符串拼接已经做了很多优化,但这依然是一个需要注意的细节。此外,模板内部的逻辑是否复杂、数据量是否庞大,也会直接影响执行速度。预编译(Pre-compilation)是另一个提升性能的手段,即在项目构建阶段就将模板编译成JS文件,避免了运行时编译的开销。&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;而&lt;strong&gt;安全性&lt;/strong&gt;方面,最主要的就是&lt;strong&gt;XSS(跨站脚本攻击)&lt;/strong&gt;问题。如果模板引擎不加处理地将用户输入的数据直接渲染到HTML中,恶意用户就可以注入JavaScript代码,从而窃取用户信息、篡改页面内容。例如,如果用户在某个输入框输入了&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<script>alert(‘hack’)</script>&lt;/pre&gt;

&lt;/div&gt;,而模板引擎直接将其输出,那么这段脚本就会在用户的浏览器中执行。&lt;/p&gt; &lt;p&gt;为了应对XSS,模板引擎通常会提供&lt;strong&gt;内容转义(Escaping)&lt;/strong&gt;功能。这意味着,当数据被插入到HTML中时,像&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;<&lt;/pre&gt;

&lt;/div&gt;、&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;>&lt;/pre&gt;

&lt;/div&gt;、&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;&amp;&lt;/pre&gt;

&lt;/div&gt;、&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;&quot;&lt;/pre&gt;

&lt;/div&gt;这样的特殊字符会被转换为对应的HTML实体(如&lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;&lt;&lt;/pre&gt;

&lt;/div&gt;, &lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;&gt;&lt;/pre&gt;

&lt;/div&gt;, &lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;&amp;&lt;/pre&gt;

&lt;/div&gt;, &lt;div class=&quot;code&quot; style=&quot;position:relative; padding:0px; margin:0px;&quot;&gt;&lt;pre class=&quot;brush:php;toolbar:false;&quot;&gt;&quot;&lt;/pre&gt;

&lt;/div&gt;)。这样,即使数据中包含恶意脚本,浏览器也会将其视为普通文本而非可执行代码。一个好的模板引擎应该默认对所有输出内容进行转义,除非开发者明确声明不转义(这通常用于插入可信赖的HTML片段)。在我看来,安全性永远是第一位的,任何可能的用户输入都应该被视为不可信,并进行严格的转义处理。&lt;/p&gt;

javascript java html js 前端 正则表达式 seo 浏览器 工具 后端 前端开发 作用域 JavaScript 正则表达式 html xss if for 构造函数 字符串 变量作用域 循环 JS function 对象 作用域 dom alert ui SEO

上一篇
下一篇