underscore.js 剩余属性1-- template

// By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate: /<%([\s\S]+?)%>/g,
    interpolate: /<%=([\s\S]+?)%>/g,
    escape: /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'": "'",
    '\\': '\\',
    '\r': 'r',
    '\n': 'n',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

  var escapeChar = function(match) {
    return '\\' + escapes[match];
  };

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  // NB: `oldSettings` only exists for backwards compatibility.
  _.template = function(text, settings, oldSettings) {
    if (!settings && oldSettings) settings = oldSettings;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
      index = offset + match.length;

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      } else if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      } else if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }

      // Adobe VMs need the match returned to produce the correct offset.
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    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';

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

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

    // Provide the compiled source as a convenience for precompilation.
    var argument = settings.variable || 'obj';
    template.source = 'function(' + argument + '){\n' + source + '}';

    return template;
  };

模版的用法

我们先看官方文档对这个的说明:

_.template(templateString, [settings])

我们看到有两个参数,一个是templateString,另一个则是可选的settings

var compiled = _.template("hello: <%= name %>");
compiled({name: 'moe'});
=> "hello: moe"

var template = _.template("<b><%- value %></b>");
template({value: '<script>'});
=> "<b>&lt;script&gt;</b>"

大致是通过<%= … %>或者 <%- … %>
其中一种是纯字符串插入,另外一种则是html转义,也就是会采用html中的<br>等标签

而<% … %>则是执行javascript代码块

var compiled = _.template("<% var i = 0; while (i++ < 4) { %> <%= name %> <%}%>");

var pin = compiled({name: 'I am here !!'});

console.log(pin); //  I am here !!  I am here !!  I am here !!  I am here !! 
_.template("Using 'with': <%= data.answer %>", {variable: 'data'})({answer: 'no'});
=> "Using 'with': no"

还可以在setting中设置variable

那么,以上强大的功能究竟是如何实现的呢?

template settings

_.templateSettings = {
    evaluate: /<%([\s\S]+?)%>/g,
    interpolate: /<%=([\s\S]+?)%>/g,
    escape: /<%-([\s\S]+?)%>/g
  };

evaluate:
这里写图片描述

interpolate:
这里写图片描述

escape:
这里写图片描述

正则表达式本身并不复杂,作用如上面三个图所示
var noMatch = /(.)^/;
还有一个noMatch,跟/$(.)/
作用相同,即完全不匹配


  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'": "'",
    '\\': '\\',
    '\r': 'r',
    '\n': 'n',
    '\u2028': 'u2028', // 换行分割符
    '\u2029': 'u2029'
  };

对特殊字符串进行处理,用在escape中

对需要escape字符串的处理

  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; // 找到这几个特殊字符

var escapeChar = function(match) {
    return '\\' + escapes[match];
  };

我们看到escapeChar的作用,就是符合match条件的值即进行改变

_.template

_.template = function(text, settings, oldSettings) {
    if (!settings && oldSettings) settings = oldSettings; // 看是否有oldsettings
    settings = _.defaults({}, settings, _.templateSettings); // 此处如果设置了setting, 则不调用默认。否则就用默认

    // Combine delimiters into one regular expression via alternation.
    var matcher = RegExp([
      (settings.escape || noMatch).source, // source字符串化正则
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g'); // settings中的三种情况或者不匹配

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='"; // 构造function

    // 后面开始生成source
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { // 抽取模版内容
      source += text.slice(index, offset).replace(escapeRegExp, escapeChar); // 二次过滤,防止无法执行的部分,换行,换段,转义等
      index = offset + match.length;

      if (escape) { // 存在分组的时候, 匹配到了escape
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; // 当中存在underscore
      } else if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; // 与escape相同,但不会处理html特殊的字符如<br>
      } else if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='"; // 新的p+=是为了连接后面的value, 从而形成evaluate中夹字符串的情况,达到运行js的目的
      }

      // Adobe VMs need the match returned to produce the correct offset.
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; // 没有variable则用{}或者规定的obj来做context, 其中obj是函数的默认传参,因此达到不设定variable时,可以直接用参数名输入的效果

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

    var render; // render函数
    try {
      render = new Function(settings.variable || 'obj', '_', source); // 建立一个新的function,默认以obj为传入参数
    } catch (e) {
      e.source = source;
      throw e;
    }

    var template = function(data) {
      return render.call(this, data, _); // 第三个参数传入underscore,因为new function创造的函数内部无法直接读取外部,只能依靠传参
    };

    // Provide the compiled source as a convenience for precompilation.
    var argument = settings.variable || 'obj';
    template.source = 'function(' + argument + '){\n' + source + '}'; // 建立source变量,检查函数,但由于其argument仅含'obj',并不含其实已经传入的underscore,因此有一定误导性。

    return template;
  };

已在注释中解释,其中有一点值得注意,其实new Function可以用eval来进行替代,经过某网站的测试(忘记哪个网站了),eval的效率甚至可以更高,超过new Function,并不像谣传的那样降低效率。然而根据javascript秘密花园中所说,eval存在复杂的作用域问题,容易出错,所以不推荐用eval。
还有值得一提的一点,定义了variable后渲染速度会大大加快,这点非常有意思,也就是说用with来规定context调用内部属性,远不如直接调用对象属性来的更快

没了

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值