Maison  >  Article  >  interface Web  >  Une brève analyse des méthodes de mise en œuvre de base du moteur de modèle JavaScript_Connaissances de base

Une brève analyse des méthodes de mise en œuvre de base du moteur de modèle JavaScript_Connaissances de base

WBOY
WBOYoriginal
2016-05-16 15:15:321110parcourir

Le modèle sépare les données et la présentation, ce qui facilite la maintenance de la logique et des effets de la présentation. À l’aide de l’objet Function de JavaScript, créez étape par étape un moteur de conversion de modèles extrêmement simple

Présentation du modèle
Le modèle fait généralement référence à du texte intégré à une sorte de code de langage de programmation dynamique. Les données et les modèles peuvent être combinés sous une certaine forme pour produire des résultats différents. Les modèles sont généralement utilisés pour définir le formulaire d'affichage, ce qui peut rendre la présentation des données plus riche et plus facile à maintenir. Par exemple, voici un exemple de modèle :

<ul>
 <% for(var i in items){ %>
 <li class='<%= items[i].status %>'><%= items[i].text %></li>
 <% } %>
</ul>

S'il existe les données des éléments suivants :

items:[
 { text: 'text1' ,status:'done' },
 { text: 'text2' ,status:'pending' },
 { text: 'text3' ,status:'pending' },
 { text: 'text4' ,status:'processing' }
]

En le combinant d'une manière ou d'une autre, le code HTML suivant peut être généré :

<ul>
 <li class='done'>text1<li>
 <li class='pending'>text2<li>
 <li class='pending'>text3<li>
 <li class='processing'>text4<li>
</ul>

Si vous souhaitez obtenir le même effet sans utiliser de modèle, c'est-à-dire afficher les données ci-dessus en conséquence, vous devez procéder comme suit :

var temp = '<ul>';
for(var i in items){
 temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';

On peut constater que l'utilisation de modèles présente les avantages suivants :

Écriture HTML simplifiée
Ayez plus de contrôle sur la présentation des données grâce à des éléments de programmation (tels que des boucles et des branches conditionnelles)
Sépare les données et l'affichage, ce qui rend la logique d'affichage et les effets plus faciles à maintenir
Moteur de modèles
Un programme qui combine des données et des modèles pour générer le résultat final en analysant des modèles est appelé moteur de modèles. Il existe de nombreux types de modèles et de nombreux moteurs de modèles correspondants. Un modèle plus ancien s'appelle ERB, qui est utilisé dans de nombreux frameworks Web, tels que ASP.NET, Rails... L'exemple ci-dessus est un exemple d'ERB. Il existe deux concepts fondamentaux dans ERB : évaluer et interpoler. En apparence, évaluer fait référence à la partie contenue dans 72637aecae1027e7d023ac098a170986 et interpoler fait référence à la partie contenue dans 332000003288cabbdff89f9a8e5a919b. Du point de vue du moteur de modèle, la partie en évaluation ne sera pas directement sortie vers le résultat et est généralement utilisée pour le contrôle du processus tandis que la partie en interpolation sera directement sortie vers le résultat ;

Du point de vue de la mise en œuvre du moteur de modèles, il doit s'appuyer sur les fonctionnalités de compilation dynamique ou d'interprétation dynamique du langage de programmation pour simplifier la mise en œuvre et améliorer les performances. Par exemple : ASP.NET utilise la compilation dynamique de .NET pour compiler des modèles en classes dynamiques et utilise la réflexion pour exécuter dynamiquement le code dans la classe. Cette implémentation est en réalité plus compliquée car C# est un langage de programmation statique, mais en utilisant JavaScript, vous pouvez utiliser Function pour implémenter un moteur de modèle simple avec très peu de code. Cet article implémentera un moteur de modèle ERB simple pour montrer la puissance de JavaScript.

Conversion de texte de modèle
Pour l'exemple ci-dessus, examinez la différence entre utiliser et ne pas utiliser de modèles :

Rédaction du modèle :

<ul>
 <% for(var i in items){ %>
 <li class='<%= items[i].status %>'><%= items[i].text %></li>
 <% } %>
</ul>

Écriture sans modèle :

var temp = '<ul>';
for(var i in items){
 temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';

En y regardant bien, les deux méthodes sont en réalité très "similaires" et se retrouvent dans un certain sens de correspondance biunivoque. Si le texte du modèle peut être transformé en code à exécuter, la conversion du modèle peut alors être réalisée. Il y a deux principes dans le processus de conversion :

Lorsque vous rencontrez du texte ordinaire, il est directement concaténé en chaînes
Lors d'une interpolation (c'est-à-dire 332000003288cabbdff89f9a8e5a919b), le contenu est traité comme une variable et épissé dans la chaîne
Lorsqu'il rencontre une évaluation (c'est-à-dire 72637aecae1027e7d023ac098a170986), il est directement traité comme du code
Transformez l'exemple ci-dessus selon les principes ci-dessus et ajoutez une fonction générale :

var template = function(items){
 var temp = '';
 //开始变换
 temp += '<ul>';
 for(var i in items){
 temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
 }
 temp += '</ul>';
}

Enfin, exécutez cette fonction et transmettez les paramètres de données :

var result = template(items);

Fonction dynamique javascript
On peut voir que la logique de conversion ci-dessus est en fait très simple, mais le problème clé est que le modèle change, ce qui signifie que le code du programme généré doit également être généré et exécuté au moment de l'exécution. Heureusement, JavaScript possède de nombreuses fonctionnalités dynamiques, dont Function. Nous utilisons généralement le mot-clé function pour déclarer des fonctions dans js, et Function est rarement utilisé. En js, function est une syntaxe littérale. Le runtime de js convertira la fonction littérale en un objet Function, donc Function fournit en fait un mécanisme plus bas et plus flexible.

La syntaxe pour créer directement une fonction à l'aide de la classe Function est la suivante :

var function_name = new Function(arg1, arg2, ..., argN, function_body)

Par exemple :

//创建动态函数 
var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
//执行 
sayHi('Hello','World');

Le corps de la fonction et les paramètres peuvent être créés via des chaînes ! Tellement cool ! Grâce à cette fonctionnalité, le texte du modèle peut être converti en une chaîne de corps de fonction, afin que des fonctions dynamiques puissent être créées et appelées dynamiquement.

Idées de mise en œuvre
Utilisez d'abord des expressions régulières pour décrire l'interpolation et l'évaluation, et les parenthèses sont utilisées pour la capture de groupe :

var interpolate_reg = /<%=([\s\S]+&#63;)%>/g;
var evaluate_reg = /<%([\s\S]+&#63;)%>/g;

Afin de faire correspondre en permanence l'ensemble du modèle, ces deux expressions régulières sont fusionnées, mais notez que toutes les chaînes qui peuvent correspondre à l'interpolation peuvent correspondre à l'évaluation, l'interpolation doit donc avoir une priorité plus élevée :

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>/g

Concevez une fonction de conversion de modèles, les paramètres d'entrée sont des chaînes de texte de modèle et des objets de données

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){ ... }

使用replace方法,进行正则的匹配和“替换”,实际上我们的目的不是要替换interpolate或evaluate,而是在匹配的过程中构建出“方法体”:

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){
 var index = 0;//记录当前扫描到哪里了
 var function_body = "var temp = '';";
 function_body += "temp += '";
 text.replace(matcher,function(match,interpolate,evaluate,offset){
 //找到第一个匹配后,将前面部分作为普通字符串拼接的表达式
 function_body += text.slice(index,offset);
 
 //如果是<% ... %>直接作为代码片段,evaluate就是捕获的分组
 if(evaluate){
  function_body += "';" + evaluate + "temp += '";
 }
 //如果是<%= ... %>拼接字符串,interpolate就是捕获的分组
 if(interpolate){
  function_body += "' + " + interpolate + " + '";
 }
 //递增index,跳过evaluate或者interpolate
 index = offset + match.length;
 //这里的return没有什么意义,因为关键不是替换text,而是构建function_body
 return match;
 });
 //最后的代码应该是返回temp
 function_body += "';return temp;";
}

至此,function_body虽然是个字符串,但里面的内容实际上是一段函数代码,可以用这个变量来动态创建一个函数对象,并通过data参数调用:

var render = new Function('obj', function_body);
return render(data);

这样render就是一个方法,可以调用,方法内部的代码由模板的内容构造,但是大致的框架应该是这样的:

function render(obj){
 var temp = '';
 temp += ...
 ...
 return temp;
}

注意到,方法的形参是obj,所以模板内部引用的变量应该是obj:

<script id='template' type='javascript/template'>
 <ul>
 <% for(var i in obj){ %>
  <li class="<%= obj[i].status %>"><%= obj[i].text %></li>
 <% } %>
 </ul>
</script>

看似到这里就OK了,但是有个必须解决的问题。模板文本中可能包含\r \n \u2028 \u2029等字符,这些字符如果出现在代码中,会出错,比如下面的代码是错误的:

temp += '
 <ul>
 ' + ... ;

我们希望看到的应该是这样的代码:

temp += '\n \t\t<ul>\n' + ...;

这样需要把\n前面的转义成\即可,最终变成字面的\\n。

另外,还有一个问题是,上面的代码无法将最后一个evaluate或者interpolate后面的部分拼接进来,解决这个问题的办法也很简单,只需要在正则式中添加一个行尾的匹配即可:

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>|$/g;

相对完整的代码

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>|$/g


//模板文本中的特殊字符转义处理
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var escapes = {
  "'":   "'",
  '\\':   '\\',
  '\r':   'r',
  '\n':   'n',
  '\t':   't',
  '\u2028': 'u2028',
  '\u2029': 'u2029'
 };

//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){
 var index = 0;//记录当前扫描到哪里了
 var function_body = "var temp = '';";
 function_body += "temp += '";
 text.replace(matcher,function(match,interpolate,evaluate,offset){
 //找到第一个匹配后,将前面部分作为普通字符串拼接的表达式
 //添加了处理转义字符
 function_body += text.slice(index,offset)
  .replace(escaper, function(match) { return '\\' + escapes[match]; });

 //如果是<% ... %>直接作为代码片段,evaluate就是捕获的分组
 if(evaluate){
  function_body += "';" + evaluate + "temp += '";
 }
 //如果是<%= ... %>拼接字符串,interpolate就是捕获的分组
 if(interpolate){
  function_body += "' + " + interpolate + " + '";
 }
 //递增index,跳过evaluate或者interpolate
 index = offset + match.length;
 //这里的return没有什么意义,因为关键不是替换text,而是构建function_body
 return match;
 });
 //最后的代码应该是返回temp
 function_body += "';return temp;";
 var render = new Function('obj', function_body);
 return render(data);
}

调用代码可以是这样:

<script id='template' type='javascript/template'>
 <ul>
 <% for(var i in obj){ %>
  <li class="<%= obj[i].status %>"><%= obj[i].text %></li>
 <% } %>
 </ul>
</script>

...

var text = document.getElementById('template').innerHTML;
var items = [
 { text: 'text1' ,status:'done' },
 { text: 'text2' ,status:'pending' },
 { text: 'text3' ,status:'pending' },
 { text: 'text4' ,status:'processing' }
];
console.log(template(text,items));

可见,我们只用了很少的代码就实现了一个简易的模板。

遗留的问题
还有几个细节的问题需要注意:

  • 因为8cf5f6c35ca23753fe8175505a949eed都是模板的边界字符,如果模板需要输出8cf5f6c35ca23753fe8175505a949eed,那么需要设计转义的办法
  • 如果数据对象中包含有null,显然不希望最后输出'null',所以需要在function_body的代码中考虑null的情况
  • 在模板中每次使用obj的形参引用数据,可能不太方便,可以在function_body添加with(obj||{}){...},这样模板中可以直接使用obj的属性
  • 可以设计将render返回出去,而不是返回转化的结果,这样外部可以缓存生成的函数,以提高性能
Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn