// 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><script></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:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9EkeOVTWU9kejpXTmZEWjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DN0QzN0ITMwIDOycDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
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 = ;
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調用内部屬性,遠不如直接調用對象屬性來的更快
沒了