我刚刚开始用JavaScript编写自己的小模板库,因为当我遍历其他人时,总会有这样的声音说:“哦,这是很多代码和功能。这真的有必要还是会它会降低性能吗?“

如果您能够遵守约定,则可能会遇到较少的问题。在我的脑海里报价。约定是,数据需要一个对象,并且其键必须与模板中的键相同,并在模板分隔符括号“ {{key}}”中提到。 >但是现在我开始思考一个事实,其他库包含的代码比我的更多,并且一定有原因吧?

还有其他一些事情



更少的代码=更好的性能?
为什么这么多人将他们的项目与这个模板库联系起来?
(性能问题=>会驳斥我的第一点)

如果有人可以向我解释一些有关此主题的事情,那将很酷。我的心态?
当我说对了,有什么可以改善的,甚至当我做错了吗?
如果你向我推荐一个图书馆,你的原因是什么?


#1 楼


...当我遍历其他人时,总有这样的声音:“哦,这是很多代码和功能。这真的有必要还是会降低性能?” br />
这是一个合理的考虑,但过早的优化是有原因的。尝试推出自己的解决方案可能是一个好主意,但在自己实施该问题之前了解更多有关该问题和其他现有解决方案的信息将非常有益。 _.template
它实际上是45行(添加一两个辅助函数)并且可以很好地完成工作。
对于您的解决方案,我有几点建议:模板
如果您担心性能,那么通过AJAX加载模板是个坏主意。这将是一个可怕的瓶颈,而不是另一个模板库中的几百行代码。
此外,您的代码假定您每次渲染内容时都需要发出HTTP请求。如果您有一个包含20个项目的列表视图,那将是20个请求。
这绝对不适合生产代码。
不要像以往那样使用同步AJAX调用
AJAX的功能是异步处理事物,而不会阻止其他脚本执行。很少有理由同步执行操作。
再次,请不要在生产代码中执行此操作。
相反,将模板放入HTML或(最好)将其编译为JS
如果不是全部,则模板库假定模板在客户端代码中已经可用,这是正确的方法。现在,有两种方法可以实现此目的:
1。您可以使用<script text="text/template">之类的技巧将模板嵌入DOM中。好处是操作简单(没有其他构建步骤),但是缺点是必须将所有模板的HTML重复传递到每一页。 /> 2。但实际上,您应该将模板预编译为JS文件
在这种情况下,当某个命令行脚本遍历您的templates/*.html文件并将每个HTML模板编译为一个JavaScript函数时,该工作流程将包括一个附加步骤,该JavaScript函数“采用”数据参数并返回“呈现的” HTML字符串。 />例如,将使用诸如
<div class="editor-Gallery-media">
  <div class="editor-Gallery-mediaItem"
    style="background-image: url({{ thumbnail_url }});"
    data-orientation="{{ orientation }}">

    <div class="editor-Gallery-deleteMediaItem"><i class="icon-cross"></i></div>

    <div class="editor-GrabboxMedia-orientation"></div>

    <div class="editor-Gallery-mediaItemCaption">{{ caption }}</div>
  </div>
</div>

之类的函数编译模板,如
this["st"]["Template"]["templates/editor/items/gallery_media.html"] = function(obj) {
obj || (obj = {});
var __t, __p = '', __e = _.escape;
with (obj) {
__p += '<div class="editor-Gallery-media">\n  <div class="editor-Gallery-mediaItem"\n    style="background-image: url(' +
((__t = ( thumbnail_url )) == null ? '' : __t) +
');"\n    data-orientation="' +
((__t = ( orientation )) == null ? '' : __t) +
'">\n\n    <div class="editor-Gallery-deleteMediaItem"><i class="icon-cross"></i></div>\n\n    <div class="editor-GrabboxMedia-orientation"></div>\n\n    <div class="editor-Gallery-mediaItemCaption">' +
((__t = ( caption )) == null ? '' : __t) +
'</div>\n  </div>\n</div>';

}
return __p
};

,该函数将被放置在生成的templates.js文件中。
请注意,这种情况下没有解析的开销,因为解析是在构建过程中在您的计算机上进行的。客户端使用预先生成的函数。
大多数模板引擎可以生成这些函数。
即使我们将模板与HTML捆绑在一起(script type="text/template"方法),也不要两次解析同一模板
代码仍然遭受这样的事实,即每次渲染时都会解析相同的模板。这意味着,对于20个相同的项目,相同的模板将被解析20次。当然,与获取HTML相比,解析无济于事,但是相对而言,优化起来相对简单。
相反,您应该解析一次模板,以某种方式缓存“解析”版本并弄清楚如何“应用”它适用于不同的型号。正如我之前所提倡的那样,自然的方法是让template构建一个与您的模板相对应的函数。
这正是Underscore的_.template所做的,因此您需要查看其实现(另请参见带注释的版本) :
_.template = function(text, data, settings) {
  var render;
  settings = _.defaults({}, settings, _.templateSettings);

  var matcher = new RegExp([
    (settings.escape || noMatch).source,
    (settings.interpolate || noMatch).source,
    (settings.evaluate || noMatch).source
  ].join('|') + '|$', 'g');

  var index = 0;
  var source = "__p+='";
  text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
    source += text.slice(index, offset)
      .replace(escaper, function(match) { return '\' + escapes[match]; });

    if (escape) {
      source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
    }
    if (interpolate) {
      source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
    }
    if (evaluate) {
      source += "';\n" + evaluate + "\n__p+='";
    }
    index = offset + match.length;
    return match;
  });
  source += "';\n";


  if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

  source = "var __t,__p='',__j=Array.prototype.join," +
    "print=function(){__p+=__j.call(arguments,'');};\n" +
    source + "return __p;\n";

  try {
    render = new Function(settings.variable || 'obj', '_', source);
  } catch (e) {
    e.source = source;
    throw e;
  }

  if (data) return render(data, _);
  var template = function(data) {
    return render.call(this, data, _);
  };

  template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

  return template;
};

它解析您的模板,将其“转换”为JavaScript函数,然后返回此函数。当您使用不同的模型调用此函数时,不会进行解析:在初始化期间仅进行一次解析。
注意事项


您的模板库可能与jQuery无关。毕竟,您要做的是将一个string + data转换为另一个string。将此字符串插入DOM是另一回事,而且似乎不属于这里。 ;您无需显式调用StringArrayObject构造函数。如果要将string设置为空字符串,请写入var string = '';。不过,这是不必要的,因为您不打算使用空值,那么为什么要麻烦分配呢?写下var string;并保留它,直到以后再分配。但是undefined对于变量来说确实是个坏名字。是模板,对不对?因此,您可能应该写
  var string = new String();

string代表一个尚未初始化/就绪的值。


如果您在这里提高性能, doT声称是最快的(尽管我不相信他们奇怪的插值语法可以节省几毫秒)。


最后,我建议您向Underscore,doT和胡须学习。 js。学习重新发明东西既有趣又有益,但是在花时间测试它之前要考虑您的实现更高效是不明智的。

评论


\ $ \ begingroup \ $
哇,我没想到会有如此大量的有用信息!谢谢你,我很感激!
\ $ \ endgroup \ $
–琼·拉默(Jon Lamer)
2014年1月3日,3:10

#2 楼

从头再来:



delimiter应该是delimiters


var string = new String();应该是var string = '';,它引起的问题更少
总的来说,我认为对每个模板使用AJAX都是不好的设计。通过使用拆分连接技巧可以实现以下效果:


模板,这确实与模板的用途背道而驰。
您一直返回exchange,但是您没有链接函数调用
我不确定代码如何工作?更新:现在我知道它是如何工作的,每个模板函数调用的同步调用是一个坏主意。 />
this.exchange = function() {
  for ( var key in this.data ) {
    var token = this.delimiter[0] + key + this.delimiter[1];
    this.template = this.template.split( token ).join( this.data[key] );
  }
};


评论


\ $ \ begingroup \ $
感谢您的评论,您提到了许多有用的东西:)
\ $ \ endgroup \ $
–琼·拉默(Jon Lamer)
2014年1月2日,18:47

\ $ \ begingroup \ $
因此,当您说“对每个模板进行ajax”很不好的时候,一定有更好的方法来处理它,不是吗?但是,如果我使用AJAX,则应该在就绪状态发生变化时调用交换函数,因此在http.onreadystatechange函数中,这允许我再次使ajax调用异步,还是我现在完全错了?
\ $ \ endgroup \ $
–琼·拉默(Jon Lamer)
2014年1月2日,18:57

\ $ \ begingroup \ $
我的意思是,这会降低性能,我个人将使用初始JS脚本获取模板。
\ $ \ endgroup \ $
– konijn
2014年1月2日,19:11



\ $ \ begingroup \ $
好的,我只是一个初学者,所以欢迎提出任何意见:)
\ $ \ endgroup \ $
–琼·拉默(Jon Lamer)
2014年1月2日在20:08

\ $ \ begingroup \ $
this.template = string;似乎没有意义,因为尚未设置字符串-Jon正在使用同步XMLHTTPRequest。所以有点奏效。 (虽然不是一个好主意。)
\ $ \ endgroup \ $
–丹
2014年1月2日在21:27



#3 楼

不同的模板库提供不同的功能:包括辅助函数/逻辑(例如,对象/数组和if语句的foreach),预编译和DOM绑定。

就原始性能而言,预编译模板的库通常会最快,因为您要提供特殊的javascript文件,其内容与模板function(context) { return "name: " + context.name}中的"name: {{name}}"相似,因此无需在客户端进行编译。

无论如何,您的模板引擎似乎exchange函数还具有一个简单的格式化程序,并且没有太多功能。"{{name}} {{name}}"函数还具有一个非常明显的错误,即它将丢失多个键。例如,尝试在{{name}}上运行它-只会替换第一次出现的q4312079q。
this.exchange = function() {
    var data = this.data;
    this.template = this.template.replace(/\?\{\{([^{}]+)\}\}/g, function(match, name){//reasonably complicated regex replace matches a string surrounded by {{key}}
        return data[name] || match;//if the object has property in {{key}} replace it otherwise don't change
    });

    return this;
};


评论


\ $ \ begingroup \ $
好的,这很有帮助,并且您提到的字符串的多次出现是一个不错的功能,我没想到,但这可能是因为我不需要该功能。所以您对我的建议可能是,我应该直接将数据发送到文件(例如php脚本)?
\ $ \ endgroup \ $
–琼·拉默(Jon Lamer)
2014年1月2日,18:09



\ $ \ begingroup \ $
您的方法绝对适合格式化字符串,但是您可能要考虑使用regexp替换交换功能。我将在今天晚些时候更新答案,并举例说明如何实现
\ $ \ endgroup \ $
– megawac
2014年1月2日,18:30

\ $ \ begingroup \ $
除了{{name}} {{name}}无效外,您还可以在模板的参数内放置替换标记,这通常是不希望的。您的解决方案也可以解决此问题。
\ $ \ endgroup \ $
–icktoofay
2014年1月3日在6:41

#4 楼

在一个好的实现中,仅当您实际上在使用该功能时,您才需要支付大量功能-因此,许多功能并不一定意味着速度很慢。实际上,具有少量功能的实现通常很草率且不快。考虑使用with-下划线。这无疑会降低性能,但是这样做是因为不需要执行者的任何努力。在不使用with的情况下正确实现变量引用会花费更多代码。这是一个简单的物理定律。

#5 楼

我也想在JavaScript中使用模板,并设法创建比您的方法更简单的东西:

/>
String.prototype.replaceAll = function(find, replace) {
    return this.split(find).join(replace);
};

String.prototype.insertVariables = function (nameValuePairs) {
    var string = this.valueOf();

    for (var key in nameValuePairs) {
        if (nameValuePairs.hasOwnProperty(key)) {
            string = string.replaceAll('{{' + key + '}}', nameValuePairs[key]);
        }
    }

    return string;
};