Maison  >  Article  >  interface Web  >  Comment utiliser Javascript pour écrire des idées de code de moteur de modèle et des exemples détaillés

Comment utiliser Javascript pour écrire des idées de code de moteur de modèle et des exemples détaillés

伊谢尔伦
伊谢尔伦original
2017-07-22 16:01:251355parcourir

AbsurdJS lui-même est principalement publié en tant que module pour NodeJS, mais il publie également une version client. Dans cette optique, je ne peux pas utiliser directement les moteurs existants car la plupart d'entre eux fonctionnent sur NodeJS et ne peuvent pas fonctionner sur le navigateur.

L'idée originale est la suivante :


var TemplateEngine = function(tpl, data) {
  // magic here ...
}
var template = &#39;<p>Hello, my name is <%name%>. I\&#39;m <%age%> years old.</p>&#39;;
console.log(TemplateEngine(template, {
  name: "Krasimir",
  age: 29
}));

Une fonction simple, l'entrée est notre modèle et notre objet de données, Je suppose qu'il est facile pour vous de penser au résultat, comme suit :

<p>Hello, my name is Krasimir. I&#39;m 29 years old.</p>

La première étape consiste à trouver les paramètres du modèle à l'intérieur, puis à les remplacer par les données spécifiques transmises au moteur. J'ai décidé d'utiliser des expressions régulières pour accomplir cette étape. Mais je ne suis pas le meilleur dans ce domaine, alors n'hésitez pas à commenter si votre écriture n'est pas bonne.


var re = /<%([^%>]+)?%>/g;

Cette expression régulière capturera tous les fragments commençant par d01c9b8657475002c4bafa3b514f86c2. Le paramètre g (global) à la fin signifie que non seulement un fragment correspond, mais que tous les fragments correspondants le sont. Il existe de nombreuses façons d'utiliser les expressions régulières en Javascript. Ce dont nous avons besoin est de générer un tableau contenant toutes les chaînes basées sur l'expression régulière. C'est exactement ce que fait exec.


var re = /<%([^%>]+)?%>/g;
var match = re.exec(tpl);

Si nous utilisons console.log pour imprimer la correspondance de variable, nous verrons :


[
  "<%name%>",
  " name ", 
  index: 21,
  input: 
  "<p>Hello, my name is <%name%>. I\&#39;m <%age%> years old.</p>"
]

Mais nous pouvons voir que le tableau renvoyé ne contient que la première correspondance. Nous devons envelopper la logique ci-dessus avec une boucle while afin que nous puissions obtenir toutes les correspondances.


var re = /<%([^%>]+)?%>/g;
while(match = re.exec(tpl)) {
  console.log(match);
}

Si vous exécutez le code ci-dessus, vous verrez que db185f51abe1d02301f57d0c9e8c9eb5 et 5775174bcf4feadc65f033b84eb1e107

Vient maintenant la partie intéressante. Après avoir identifié les correspondances dans le modèle, nous devons les remplacer par les données réelles transmises à la fonction. Le moyen le plus simple consiste à utiliser la fonction de remplacement. On pourrait l'écrire ainsi :


var TemplateEngine = function(tpl, data) {
  var re = /<%([^%>]+)?%>/g;
  while(match = re.exec(tpl)) {
    tpl = tpl.replace(match[0], data[match[1]])
  }
  return tpl;
}

Bon, donc ça marche, mais ce n'est pas assez bien. Ici, nous utilisons un objet simple pour transmettre des données sous la forme de données["propriété"], mais dans les situations réelles, nous aurons probablement besoin d'objets imbriqués plus complexes. Nous avons donc légèrement modifié l'objet de données :


{
  name: "Krasimir Tsonev",
  profile: { age: 29 }
}

Cependant, si nous l'écrivons directement comme ceci, il ne fonctionnera toujours pas car 8303cb87a8639fbc92bb87526af91630, afin que les données entrantes puissent être évaluées directement, comme suit :

var template = &#39;<p>Hello, my name is <%this.name%>. I\&#39;m <%this.profile.age%> years old.</p>&#39;;

Vous pourriez être curieux de savoir comment se passe cela a été réalisé ? Ici, John utilise la nouvelle syntaxe Function pour créer une fonction basée sur une chaîne. Jetons un coup d'œil à un exemple :


var fn = new Function("arg", "console.log(arg + 1);");
fn(2); // outputs 3

fn est une véritable fonction. Il accepte un paramètre et le corps de la fonction est console.log(arg + 1);. Le code ci-dessus est équivalent au code suivant :


var fn = function(arg) {
  console.log(arg + 1);
}
fn(2); // outputs 3

Avec cette méthode, nous pouvons construire une fonction à partir d'une chaîne, y compris ses paramètres et le corps de la fonction. N’est-ce pas exactement ce que nous voulons ! Mais ne vous inquiétez pas, avant de construire la fonction, regardons à quoi ressemble le corps de la fonction. Selon l'idée précédente, le retour final de ce moteur de modèle devrait être un modèle compilé. Toujours en utilisant la chaîne de modèle précédente comme exemple, le contenu renvoyé devrait être similaire à :


return
"<p>Hello, my name is " + 
this.name + 
". I\&#39;m " + 
this.profile.age + 
" years old.</p>";

Bien sûr, dans le moteur de modèle actuel, nous couperons le modèle Divisé en petits morceaux de texte et en code Javascript significatif. Plus tôt, vous m'avez peut-être vu utiliser une simple concaténation de chaînes pour obtenir l'effet souhaité, mais cela n'est pas conforme à 100 % à nos exigences. Puisque l'utilisateur est susceptible de transmettre du code Javascript plus complexe, nous avons besoin ici d'une autre boucle, comme suit :


var template = 
&#39;My skills:&#39; + 
&#39;<%for(var index in this.skills) {%>&#39; + 
&#39;<a href=""><%this.skills[index]%></a>&#39; +
&#39;<%}%>&#39;;

Si la concaténation de chaînes est utilisée, le code devrait ressembler à ceci :


return
&#39;My skills:&#39; + 
for(var index in this.skills) { +
&#39;<a href="">&#39; + 
this.skills[index] +
&#39;</a>&#39; +
}

Bien entendu, ce code ne peut pas être exécuté directement, sinon une erreur se produira. J'ai donc utilisé la logique écrite dans l'article de John, mis toutes les chaînes dans un tableau et les ai assemblées à la fin du programme.


var r = [];
r.push(&#39;My skills:&#39;); 
for(var index in this.skills) {
r.push(&#39;<a href="">&#39;);
r.push(this.skills[index]);
r.push(&#39;</a>&#39;);
}
return r.join(&#39;&#39;);

L'étape suivante consiste à collecter différentes lignes de code dans le modèle pour générer des fonctions. Grâce à la méthode présentée précédemment, nous pouvons savoir quels espaces réservés (Note du traducteur : ou correspondances d'expressions régulières) se trouvent dans le modèle et leurs positions. Ainsi, en s'appuyant sur une variable auxiliaire (curseur, curseur), on peut obtenir les résultats souhaités.


var TemplateEngine = function(tpl, data) {
  var re = /<%([^%>]+)?%>/g,
    code = 'var r=[];\n',
    cursor = 0;
  var add = function(line) {
    code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
  }
  while(match = re.exec(tpl)) {
    add(tpl.slice(cursor, match.index));
    add(match[1]);
    cursor = match.index + match[0].length;
  }
  add(tpl.substr(cursor, tpl.length - cursor));
  code += 'return r.join("");'; // <-- return the result
  console.log(code);
  return tpl;
}
var template = &#39;<p>Hello, my name is <%this.name%>. I\&#39;m <%this.profile.age%> years old.</p>&#39;;
console.log(TemplateEngine(template, {
  name: "Krasimir Tsonev",
  profile: { age: 29 }
}));

  上述代码中的变量code保存了函数体。开头的部分定义了一个数组。游标cursor告诉我们当前解析到了模板中的哪个位置。我们需要依靠它来遍历整个模板字符串。此外还有个函数add,它负责把解析出来的代码行添加到变量code中去。有一个地方需要特别注意,那就是需要把code包含的双引号字符进行转义(escape)。否则生成的函数代码会出错。如果我们运行上面的代码,我们会在控制台里面看见如下的内容:


var r=[];
r.push("<p>Hello, my name is ");
r.push("this.name");
r.push(". I&#39;m ");
r.push("this.profile.age");
return r.join("");

  等等,貌似不太对啊,this.name和this.profile.age不应该有引号啊,再来改改。


var add = function(line, js) {
  js? code += &#39;r.push(&#39; + line + &#39;);\n&#39; :
    code += &#39;r.push("&#39; + line.replace(/"/g, &#39;\\"&#39;) + &#39;");\n&#39;;
}
while(match = re.exec(tpl)) {
  add(tpl.slice(cursor, match.index));
  add(match[1], true); // <-- say that this is actually valid js
  cursor = match.index + match[0].length;
}

  占位符的内容和一个布尔值一起作为参数传给add函数,用作区分。这样就能生成我们想要的函数体了。


var r=[];
r.push("<p>Hello, my name is ");
r.push(this.name);
r.push(". I&#39;m ");
r.push(this.profile.age);
return r.join("");

  剩下来要做的就是创建函数并且执行它。因此,在模板引擎的最后,把原本返回模板字符串的语句替换成如下的内容:

return new Function(code.replace(/[\r\t\n]/g, &#39;&#39;)).apply(data);

  我们甚至不需要显式地传参数给这个函数。我们使用apply方法来调用它。它会自动设定函数执行的上下文。这就是为什么我们能在函数里面使用this.name。这里this指向data对象。

  模板引擎接近完成了,不过还有一点,我们需要支持更多复杂的语句,比如条件判断和循环。我们接着上面的例子继续写。


var template = 
&#39;My skills:&#39; + 
&#39;<%for(var index in this.skills) {%>&#39; + 
&#39;<a href="#"><%this.skills[index]%></a>&#39; +
&#39;<%}%>&#39;;
console.log(TemplateEngine(template, {
  skills: ["js", "html", "css"]
}));

  这里会产生一个异常,Uncaught SyntaxError: Unexpected token for。如果我们调试一下,把code变量打印出来,我们就能发现问题所在。


var r=[];
r.push("My skills:");
r.push(for(var index in this.skills) {);
r.push("<a href=\"\">");
r.push(this.skills[index]);
r.push("</a>");
r.push(});
r.push("");
return r.join("");

  带有for循环的那一行不应该被直接放到数组里面,而是应该作为脚本的一部分直接运行。所以我们在把内容添加到code变量之前还要多做一个判断。


var re = /<%([^%>]+)?%>/g,
  reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
  code = &#39;var r=[];\n&#39;,
  cursor = 0;
var add = function(line, js) {
  js? code += line.match(reExp) ? line + &#39;\n&#39; : &#39;r.push(&#39; + line + &#39;);\n&#39; :
    code += &#39;r.push("&#39; + line.replace(/"/g, &#39;\\"&#39;) + &#39;");\n&#39;;
}

  这里我们新增加了一个正则表达式。它会判断代码中是否包含if、for、else等等关键字。如果有的话就直接添加到脚本代码中去,否则就添加到数组中去。运行结果如下:


var r=[];
r.push("My skills:");
for(var index in this.skills) {
r.push("<a href=\"#\">");
r.push(this.skills[index]);
r.push("</a>");
}
r.push("");
return r.join("");

  当然,编译出来的结果也是对的。

My skills:<a href="#">js</a><a href="#">html</a><a href="#">css</a>

  最后一个改进可以使我们的模板引擎更为强大。我们可以直接在模板中使用复杂逻辑,例如:


var template = 
&#39;My skills:&#39; + 
&#39;<%if(this.showSkills) {%>&#39; +
  &#39;<%for(var index in this.skills) {%>&#39; + 
  &#39;<a href="#"><%this.skills[index]%></a>&#39; +
  &#39;<%}%>&#39; +
&#39;<%} else {%>&#39; +
  &#39;<p>none</p>&#39; +
&#39;<%}%>&#39;;
console.log(TemplateEngine(template, {
  skills: ["js", "html", "css"],
  showSkills: true
}));

  除了上面说的改进,我还对代码本身做了些优化,最终版本如下:


var TemplateEngine = function(html, options) {
  var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = &#39;var r=[];\n&#39;, cursor = 0;
  var add = function(line, js) {
    js? (code += line.match(reExp) ? line + &#39;\n&#39; : &#39;r.push(&#39; + line + &#39;);\n&#39;) :
      (code += line != &#39;&#39; ? &#39;r.push("&#39; + line.replace(/"/g, &#39;\\"&#39;) + &#39;");\n&#39; : &#39;&#39;);
    return add;
  }
  while(match = re.exec(html)) {
    add(html.slice(cursor, match.index))(match[1], true);
    cursor = match.index + match[0].length;
  }
  add(html.substr(cursor, html.length - cursor));
  code += &#39;return r.join("");&#39;;
  return new Function(code.replace(/[\r\t\n]/g, &#39;&#39;)).apply(options);
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

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