天天看點

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 = ;
    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:

underscore.js 剩餘屬性1-- template

interpolate:

underscore.js 剩餘屬性1-- template

escape:

underscore.js 剩餘屬性1-- template

正規表達式本身并不複雜,作用如上面三個圖所示

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 = ;
    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調用内部屬性,遠不如直接調用對象屬性來的更快

沒了

underscore.js 剩餘屬性1-- template