20行实现javascript模板引擎

20行实现javascript模板引擎

我仍然在用AbsurdJS预处理器写javascript。起初,这只是一个CSS预处理器,后来我把它扩展为CSS/HTML预处理器。最近,它可以实现javascript到CSS/HTML的转换,因为它可以作为模板引擎来生成HTML。比如,可以用数据填充HTML模板。

然后,我就想写一个简单的模板引擎可以完美地与我当前的开发工作相配合。AbsurdJS主要是作为nodejs模块来发布的,但是它也可以作为客户端使用。有了这种想法,我意识到我不能利用现有的模板引擎。因为现在的大多数模板引擎只是基于nodejs,很难把他们复制到浏览器来使用。我需要一个体积小的用原生JS写的模板引擎。我曾经拜读过John Resig的一篇文章JavaScript Micro-Templating。这好像就是我所需要的。我把里面的代码做了些许变动,使之缩减为20行。我想这脚本的运行机制是非常有趣的。本文中,我一步步地重新创建一个模板引擎,然后你就会体会到来自John的伟大创意。

我们以下面的代码作为开始吧:

 

  1. var TemplateEngine = function(tpl, data) {
  2. // magic here ...
  3. }
  4. var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
  5. console.log(TemplateEngine(template, {
  6. name: "Krasimir",
  7. age: 29
  8. }));

一个简单的函数,来处理我们的模板与数据对象。

你可能会猜想到,我们最终想要达到的结果就是下面的样子: 

 

  1. <p>Hello, my name is Krasimir. I'm 29 years old.</p>

首先我们必须处理模板内部的动态语法,然后我们用传递给模板引擎的真实数据来替换这些动态语法。我决定利用正则表达式来实现。正则表达式不是我的强项,所以你可以留言建议给我一个更好的正则表达式。 

 

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

这样,我们会捕获到分别以“%”开始与结束的分组。“g”(global全局)表示我们得到的不是一个,而是全部匹配到的。采用正则表达式的exec方法可以把匹配到分组的以数组的形式表现: 

 

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

结果: 

 

  1. [
  2. "<%name%>",
  3. " name ",
  4. index: 21,
  5. input:
  6. "<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>"
  7. ]

但是我们只得到了一个,再来改善一下: 

 

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

OK,我们的最初目标达到了,但这远远不够。这只能容易获取到data['property']。但在实践中,我们可能遇到复杂的嵌套对象。比如: 

 

  1. {
  2. name: "Krasimir Tsonev",
  3. profile: { age: 29 }
  4. }

我们先前所做的工作就这样失效了!

那我们就分析一下其他的情况。比如,我们有个这样的模板: 

 

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

再或者我们可能还见过这样的模板: 

 

  1. var template =
  2. 'My skills:' +
  3. '<%for(var index in this.skills) {%>' +
  4. '<a href=""><%this.skills[index]%></a>' +
  5. '<%}%>';

这该怎么办呢? John采用了 new Function 语法实现——可以从字符串来创建函数。

那我们先来熟悉一下这种语法。看一个简单的例子: 

 

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

上述代码创建的函数fn等价于: 

 

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

这样我们可以自定义函数,其参数与函数体可以来自简单的字符串。而我们所需要的方法应该能够返回为最终的编译模板。就像这样: 

 

  1. return
  2. "<p>Hello, my name is " +
  3. this.name +
  4. ". I\'m " +
  5. this.profile.age +
  6. " years old.</p>";

而对于 

 

  1. var template =
  2. 'My skills:' +
  3. '<%for(var index in this.skills) {%>' +
  4. '<a href=""><%this.skills[index]%></a>' +
  5. '<%}%>';

我们所需要的应该是这样: 

 

  1. return
  2. 'My skills:' +
  3. for(var index in this.skills) { +
  4. '<a href="">' +
  5. this.skills[index] +
  6. '</a>' +
  7. }

当然我们所设想的会产生语法错误,那我们可以改变一下: 

 

  1. var r = [];
  2. r.push('My skills:');
  3. for(var index in this.skills) {
  4. r.push('<a href="">');
  5. r.push(this.skills[index]);
  6. r.push('</a>');
  7. }
  8. return r.join('');

有了预想结果,下一步就是分别处理每一行来产生自定义函数。

在进行处理每一行的时候,我们应该考虑到以下问题:

  • 引号的转义,否则产生的脚本不可用
  • <% %>里面的字符不应该被当做字符串处理 
  1. var TemplateEngine = function(tpl, data) {
  2. var re = /<%([^%>]+)?%>/g,
  3. code = 'var r=[];\n',
  4. cursor = 0;
  5. var add = function(line) {
  6. // 引号转义,将"替换为\"放入定义的函数体
  7. code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
  8. }
  9. //匹配到<% %>
  10. while(match = re.exec(tpl)) {
  11. // <% %>之前当做字符串放入函数体
  12. add(tpl.slice(cursor, match.index));
  13. // <% %>中间部分
  14. add(match[1]);
  15. //迭代处理<% %>后面部分
  16. cursor = match.index + match[0].length;
  17. }
  18. add(tpl.substr(cursor, tpl.length - cursor));
  19. code += 'return r.join("");'; // <-- return the result
  20. console.log(code);
  21. return tpl;
  22. }
  23. var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
  24. console.log(TemplateEngine(template, {
  25. name: "Krasimir Tsonev",
  26. profile: { age: 29 }
  27. }));

考虑到if/else等JS语句,我们再做优化: 

 

  1. var TemplateEngine = function(html, options) {
  2. var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
  3. var add = function(line, js) {
  4. js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
  5. (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
  6. return add;
  7. }
  8. while(match = re.exec(html)) {
  9. add(html.slice(cursor, match.index))(match[1], true);
  10. cursor = match.index + match[0].length;
  11. }
  12. add(html.substr(cursor, html.length - cursor));
  13. code += 'return r.join("");';
  14. return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
  15. }

终极目标实现!!

详见最终版本

原文作者:http://krasimirtsonev.com/blog/article/Javascript-template-engine-in-just-20-line

AI实战-学生生活方式模式数据集分析预测实例(含24个源代码+69.54 KB完整的数据集) 代码手工整理,无语法错误,可运。 包括:24个代码,共149.89 KB;数据大小:1个文件共69.54 KB。 使用到的模块: pandas os matplotlib.pyplot seaborn plotly.express warnings sklearn.model_selection.StratifiedShuffleSplit sklearn.pipeline.Pipeline sklearn.compose.ColumnTransformer sklearn.impute.SimpleImputer sklearn.preprocessing.OrdinalEncoder numpy sklearn.model_selection.cross_val_score sklearn.linear_model.LinearRegression sklearn.metrics.mean_squared_error sklearn.tree.DecisionTreeRegressor sklearn.ensemble.RandomForestRegressor sklearn.model_selection.train_test_split sklearn.preprocessing.PowerTransformer imblearn.pipeline.Pipeline imblearn.over_sampling.SMOTE sklearn.ensemble.AdaBoostClassifier sklearn.metrics.accuracy_score sklearn.metrics.precision_score sklearn.metrics.recall_score sklearn.metrics.f1_score optuna scipy.stats torch torch.nn torchvision.transforms torchvision.models torch.optim cv2 glob glob.glob torch.utils.data.DataLoader torch.utils.data.Dataset random.shuffle torch.utils.data.random_split torchsummary.summary matplotlib.ticker pyspark.sql.SparkSession pyspark.sql.functions.count pyspark.sql.functions.max pyspark.sql.functions.min pyspark.sql.functions.avg pyspark.sql.functions.stddev_samp pyspark.sql.functions.skewness pyspark.sql.functions.kurtosis pyspark.sql.functions pyspark.ml.feature.Tokenizer pyspark.ml.feature.VectorAssembler sklearn.preprocessing.LabelEncoder keras.models.Sequential keras.layers.Dense keras.utils.to_categorical ptitprince statsmodels.distributions.empirical_distribution.ECDF statsmodels.stats.outliers_influence.variance_inflation_factor ppscore sklearn.feature_selection.mutual_info_classif sklearn.decomposition.PCA sklearn.model_selection.StratifiedKFold sklearn.tree.DecisionTreeClassifier sklearn.metrics.balanced_accuracy_score sklearn.metrics.confusion_matrix mlxtend.plotting.plot_confusion_matrix scipy.stats.pearsonr scipy.stats.f_oneway sklearn.feature_selection.mutual_info_regression sklearn.feature_selecti
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值