mustache.js(3.0.0版本) 是一個javascript前端模板引擎。官方文檔(https://github.com/janl/mustache.js)
根據官方介紹:Mustache可以被用於html文件、配置文件、源代碼等很多場景。它的運行得益於擴展一些標簽在模板文件中,然后使用一個hash字典或對象對其進行替換渲染操作。
基本語法如下:
1. {{ keyName }}: 讀取屬性值, 如果有html標簽的話,會被轉義。
2. {{{ keyName }}}: 讀取屬性值且原樣輸出,即html不進行轉義。
3. {{ #keyName }} {{ /keyName }}: 用於遍歷。
4. {{ ^keyName }} {{ /keyName }}: 反義數據,當keyName不存在、或為null,或為false時會生效。可以理解相當於我們js中的 !(非)。
5. {{.}}: 用於遍歷數組。
6. {{ !comments }}: 用於注釋。
7. Partials: 使用可重用的模板,使用方式:{{> 變量}}。
1. 變量 {{ keyName }} 或 {{{ keyName }}}
標簽最主要是通過一個變量來使用。比如 {{ keyName }}標簽在模板中會嘗試查找keyName這個變量在當前的上下文中,如果上下文中不存在keyName變量,那么它會通過遞歸的方式依次查找它的父級元素,依次類推... 如果最頂級的上下文中依然找不到的話,那么該keyName變量就不會被渲染。否則的話,keyName標簽就會被渲染。
如果變量中存在html標簽會被轉義的。因此如果我們不想html標簽轉義的話,我們可以使用三個花括號 {{{ keyName }}}.
比如如下列子:
項目基本結構如下:
|--- mustache 文件夾 | |--- index.html | |--- mustache.js (庫文件)
基本代碼如下所示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "<a>kongzhi<a>", "msg": { "sex": " male ", "age": "31", "marriage": 'single' } } var tpl = '<p> {{name}}</p>'; var html = Mustache.render(tpl, data); console.log(html); // 打印如下:<p> <a>kongzhi<a></p> </script> </body> </html>
如上可以看到,我們name字段,存在a標簽中的 < 或 > 被轉義了,如果我們想它們不需要轉義的話,我們需要使用三個花括號 {{{}}}。如下代碼輸出:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "<a>kongzhi<a>", "msg": { "sex": " male ", "age": "31" } } var tpl = '<p> {{{name}}}</p>'; var html = Mustache.render(tpl, data); console.log(html); // 打印 <p> <a>kongzhi<a></p> </script> </body> </html>
當然如果我們上面不想使用三個花括號的話,我們也可以使用 & 告訴上下文不需要進行轉義。比如 {{ &name }} 這樣的,如上面的三個花括號 {{{ name }}}, 我們也可以改成 {{ &name }}; 效果是一樣的。
2. 塊
2.1 {{#keyName}} {{/keyName}}
{{#keyName}} 是一個標簽,它的含義是塊的意思。所謂塊就是渲染一個區域的文本一次或多次。
塊的開始形式是:{{#keyName}},結束形式是:{{/keyName}}。
我們可以使用該 {{#keyName}} {{/keyName}} 標簽來遍歷一個數組或對象。如下代碼所示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "kongzhi", "msg": { "sex": " male ", "age": "31", "marriage": 'single' } } var tpl = `{{ #msg }}<div>{{sex}}</div><div>{{age}}</div><div>{{marriage}}</div>{{ /msg }}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 <div> male </div><div>31</div><div>single</div> </script> </body> </html>
注意:如果上面的 msg 是一個布爾值 false的話,即 msg: false, 那么 tpl 模板不會被渲染。最后html為 ''; 但是如果 msg 的值是 msg: {} 這樣的話,那么tpl會渲染,只是沒有值而已,最后輸出:'<div></div><div></div><div></div>' 這樣的。
Function
當keyName的值是一個可以被調用的對象,或者是一個函數的話,那么該函數會被調用並且傳遞標簽包含的文本進去。如下代碼所示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "kongzhi", "msg": { "sex": " male ", "age": "31", "marriage": 'single' }, "wrapped": function() { return function(text, render) { return '<div>' + render(text) + '</div>' } } } var tpl = `{{#wrapped}} {{name}} is men {{/wrapped}}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 <div> kongzhi is men </div> </script> </body> </html>
如果該變量的值也是一個函數的話,那么我們也可以迭代上下文的數組。如下代碼演示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "msg": [ { 'firstName': 'kongzhi111', "lastName": 'kong' }, { 'firstName': 'kongzhi222', "lastName": 'zhi' } ], "name": function() { return this.firstName + " " + this.lastName; } } var tpl = `{{#msg}} {{name}} {{/msg}}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 kongzhi111 kong kongzhi222 zhi </script> </body> </html>
2.2 {{ ^keyName }} {{ /keyName }}
{{ ^keyName }} {{ /keyName }} 的含義是:取相反的數據。當keyName不存在、或為null,或為false時會生效。可以理解相當於我們js中的 !(非) 如下代碼所示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "kongzhi", "msg": null // 為null, undefined, '' 或 false,數據才會被渲染 } var tpl = `{{ ^msg }}<div>暫無數據</div>{{ /msg }}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 <div>暫無數據</div> </script> </body> </html>
2.3 {{.}}
{{.}} 也是可以遍歷一個數組。
如下代碼:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "kongzhi", "msg": ['111', '222', '333'] } var tpl = `{{#msg}} {{.}} * {{/msg}}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 111 * 222 * 333 * </script> </body> </html>
3. {{ !comments }}
{{ !comments }} 可以理解為代碼注釋。良好的編碼習慣,都會有一些注釋來輔佐。同樣在我們的 mustache中也存在注釋的標簽。
下面我們來看看如何使用注釋:
如下代碼:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": 'kongzhi' } var tpl = `<div>{{name}}</div>{{ ! 這是一段注釋 }}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 <div>kongzhi</div> </script> </body> </html>
4. Partials的使用
Partials的含義是:使用可重用的模板,使用方式:{{> 變量}}. 相當於 include 的意思。
可以查看如下demo演示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> var data = { "name": "kongzhi", "msg": ['111'] } var tpl = `{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 111 * <div>kongzhi</div> /* * 如上我們的tpl模板文件中引入了 <div>{{name}}</div> 模塊,但是該模塊在其他的地方 * 也使用到了,因此我們想讓他當做一個模板定義,在需要的地方 引用進來。因此我們如下這樣做了: var data = { "name": "kongzhi", "msg": ['111'] } var temp = `<div>{{name}}</div>`; var tpl = `{{#msg}} {{.}} * {{> user }} {{/msg}}`; var html = Mustache.render(tpl, data, { user: temp }); console.log(html); // 打印 111 * <div>kongzhi</div> */ </script> </body> </html>
5. 設置分割符號
有些時候我們想修改一下 mustache默認的標簽分割符號 {{}}. mustache也允許我們這樣做的。並且修改的方法很簡單。
比如說我們把分隔符改成 {% %} 這樣的 ,或者 {{% %}}這樣的,也是可以的。我們只需要 Mustache.render 方法中傳遞第四個參數,並且模板也需要改成這樣的分割符號,如下代碼所示:
<!DOCTYPE html> <html> <head> <title>mustache--demo</title> <meta charset="utf-8"> <script type="text/javascript" src="./mustache.js"></script> </head> <body> <script type="text/javascript"> console.log(Mustache); var data = { "name": "kongzhi", "msg": ['111'] } var tpl = `{{%#msg%}} {{%.%}} * <div>{{%name%}}</div> {{%/msg%}}`; var html = Mustache.render(tpl, data, {}, [ '{{%', '%}}' ]); console.log(html); // 打印 111 * <div>kongzhi</div> </script> </body> </html>
如上可以看到,我們在 Mustache.render 方法中,傳遞了第四個參數為 [ '{{%', '%}}' ],因此在模板中我們的開始標簽需要使用 '{{%'這樣的,在結束標簽使用 '%}}' 這樣的即可。或者改成任何其他自己喜歡的分隔符都可以,關鍵設置第四個參數和模板要對應起來。
二:Mustache.js 源碼分析
我們首先引入 mustache庫文件后,然后我們在頁面上打印 console.log(Mustache); 看到打印如下信息:
{ Context: fn(view, parentContext), Scanner: fn, Writer: fn, clearCache: fn, escape: function escapeHtml(){}, name: "mustache.js", parse: fn(template, tags), render: fn(template, view, partials, tags), tags: ["{{", "}}"], to_html: fn(template, view, partials, send), version: "3.0.0" }
如上我們可以看到我們的 Mustache.js 庫對外提供了很多方法。下面我們來分析下源碼:
1. 入口結構如下:
(function defineMustache (global, factory) { /* 如下判斷支持 CommonJS 規范引入文件 或 AMD 規范引入文件,或直接引入js文件, Mustache 就是我們的全局變量對外暴露。 */ if (typeof exports === 'object' && exports && typeof exports.nodeName !== 'string') { factory(exports); // CommonJS } else if (typeof define === 'function' && define.amd) { define(['exports'], factory); // AMD } else { global.Mustache = {}; factory(global.Mustache); // script, wsh, asp } }(this, function mustacheFactory(mustache) { var objectToString = Object.prototype.toString; /* * 判斷是否是一個數組的方法 */ var isArray = Array.isArray || function isArrayPolyfill (object) { return objectToString.call(object) === '[object Array]'; }; // 對象是否是一個函數 function isFunction (object) { return typeof object === 'function'; } // 判斷類型 function typeStr (obj) { return isArray(obj) ? 'array' : typeof obj; } function escapeRegExp (string) { return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'); } // 判斷對象是否有該屬性 function hasProperty (obj, propName) { return obj != null && typeof obj === 'object' && (propName in obj); } // 判斷原型上是否有該屬性 function primitiveHasOwnProperty (primitive, propName) { return ( primitive != null && typeof primitive !== 'object' && primitive.hasOwnProperty && primitive.hasOwnProperty(propName) ); } // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 // See https://github.com/janl/mustache.js/issues/189 var regExpTest = RegExp.prototype.test; function testRegExp (re, string) { return regExpTest.call(re, string); } var nonSpaceRe = /\S/; function isWhitespace (string) { return !testRegExp(nonSpaceRe, string); } // 對< > 等進行轉義 var entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }; // 轉換html標簽進行轉義操作 function escapeHtml (string) { return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { return entityMap[s]; }); } var whiteRe = /\s*/; // 匹配0個或多個空白 var spaceRe = /\s+/; // 匹配至少1個或多個空白 var equalsRe = /\s*=/; // 匹配字符串 "=",且前面允許0個或多個空白符,比如 "=" 或 " =" 這樣的。 var curlyRe = /\s*\}/; // 匹配 "}" 或 " }" var tagRe = /#|\^|\/|>|\{|&|=|!/; // 匹配 '#', '^' , '/' , '>' , { , & , = , ! 任何一個字符 // ...... 代碼略 mustache.name = 'mustache.js'; mustache.version = '3.0.0'; mustache.tags = [ '{{', '}}' ]; // ..... 代碼略 mustache.escape = escapeHtml; // Export these mainly for testing, but also for advanced usage. mustache.Scanner = Scanner; mustache.Context = Context; mustache.Writer = Writer; mustache.clearCache = function clearCache () {}; mustache.parse = function parse (template, tags) {}; mustache.render = function render (template, view, partials, tags) {}; mustache.to_html = function to_html (template, view, partials, send) {}; }));
如上代碼內部的一些工具函數,稍微了解下就好。及把很多函數掛載到 mustache對外暴露的對象上。因此我們上面打印 console.log(Mustache); 就可以看到 該對象下有很多方法和屬性,如上就是對外暴露的。
下面我們可以根據demo來分析,如下demo代碼:
var data = { "name": "kongzhi", "msg": ['111'] } var tpl = `{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 111 * <div>kongzhi</div>
從上面我們打印的 console.log(Mustache) 可知:該全局變量有很多方法,其中就有一個 render方法,該方法接收4個參數,如下代碼:Mustache.render(tpl, data, ,partials, tags); 各個參數含義分別如下:tpl(模板),data(模板數據),partials(可重用的模板), tags(可自定義設置分隔符);
如上我們只傳入兩個參數,其中 tpl 是必須傳遞的參數,否則不傳會報錯。因此會調用內部 render() 方法,方法代碼如下所示:
mustache.render = function render (template, view, partials, tags) { if (typeof template !== 'string') { throw new TypeError('Invalid template! Template should be a "string" ' + 'but "' + typeStr(template) + '" was given as the first ' + 'argument for mustache#render(template, view, partials)'); } return defaultWriter.render(template, view, partials, tags); };
然后返回 defaultWriter.render(template, view, partials, tags); 函數,defaultWriter 是 Writer方法的實列,因此它有Writer對象中所有的屬性和方法。從源碼中如下代碼可知:
var defaultWriter = new Writer();
Write 函數原型上有如下方法:
function Writer () { this.cache = {}; } Writer.prototype.clearCache = function clearCache () { this.cache = {}; }; Writer.prototype.parse = function parse (template, tags) { // ... }; Writer.prototype.render = function render (template, view, partials, tags) { // ... } Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) { // ... } Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) { // ... } Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) { // ... } Writer.prototype.renderPartial = function renderPartial (token, context, partials) { // ... } Writer.prototype.unescapedValue = function unescapedValue (token, context) { // ... } Writer.prototype.escapedValue = function escapedValue (token, context) { // ... } Writer.prototype.rawValue = function rawValue (token) { // ... }
下面我們最主要看 Writer.prototype.render 中的方法吧,代碼如下所示:
/* @param {template} 值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; @param {view} 值為:{name: 'kongzhi', msg: ['111']} */ Writer.prototype.render = function render (template, view, partials, tags) { var tokens = this.parse(template, tags); var context = (view instanceof Context) ? view : new Context(view); return this.renderTokens(tokens, context, partials, template); };
如上代碼,我們首先會調用 this.parse(template, tags); 方法來解析該模板代碼; 那么我們就繼續看 parse 代碼如下:
/* @param {template} 值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; @param {tags} 值為:undefined */ Writer.prototype.parse = function parse (template, tags) { var cache = this.cache; /* template = "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; tags的默認值:從源碼可以看到:mustache.tags = [ '{{', '}}' ]; 因此:[ '{{', '}}' ].join(':') = "{{:}}"; 因此:cacheKey的值返回 "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}:{{:}}" */ var cacheKey = template + ':' + (tags || mustache.tags).join(':'); // 第一次 cache 為 {}; 所以 第一次 tokens 返回undefined; var tokens = cache[cacheKey]; /* 因此會進入 if語句內部,然后會調用 parseTemplate 模板進行解析,解析完成后,把結果返回 tokens = cache[cacheKey]; */ if (tokens == null) tokens = cache[cacheKey] = parseTemplate(template, tags); // 最后把token的值返回 return tokens; };
如上代碼解析,我們來看下 parseTemplate 函數代碼如下:
/* @param {template} 的值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; @param {tags} 的值為:undefined */ function parseTemplate (template, tags) { // 沒有模板,直接返回 []; if (!template) return []; var sections = []; var tokens = []; var spaces = []; var hasTag = false; var nonSpace = false; // Strips all whitespace tokens array for the current line // if there was a {{#tag}} on it and otherwise only space. function stripSpace () { if (hasTag && !nonSpace) { while (spaces.length) delete tokens[spaces.pop()]; } else { spaces = []; } hasTag = false; nonSpace = false; } var openingTagRe, closingTagRe, closingCurlyRe; function compileTags (tagsToCompile) { if (typeof tagsToCompile === 'string') tagsToCompile = tagsToCompile.split(spaceRe, 2); if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) throw new Error('Invalid tags: ' + tagsToCompile); openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*'); closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1])); closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1])); } compileTags(tags || mustache.tags); var scanner = new Scanner(template); var start, type, value, chr, token, openSection; while (!scanner.eos()) { start = scanner.pos; value = scanner.scanUntil(openingTagRe); if (value) { for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); if (isWhitespace(chr)) { spaces.push(tokens.length); } else { nonSpace = true; } tokens.push([ 'text', chr, start, start + 1 ]); start += 1; // Check for whitespace on the current line. if (chr === '\n') stripSpace(); } } // Match the opening tag. if (!scanner.scan(openingTagRe)) break; hasTag = true; // Get the tag type. type = scanner.scan(tagRe) || 'name'; scanner.scan(whiteRe); // Get the tag value. if (type === '=') { value = scanner.scanUntil(equalsRe); scanner.scan(equalsRe); scanner.scanUntil(closingTagRe); } else if (type === '{') { value = scanner.scanUntil(closingCurlyRe); scanner.scan(curlyRe); scanner.scanUntil(closingTagRe); type = '&'; } else { value = scanner.scanUntil(closingTagRe); } // Match the closing tag. if (!scanner.scan(closingTagRe)) throw new Error('Unclosed tag at ' + scanner.pos); token = [ type, value, start, scanner.pos ]; tokens.push(token); if (type === '#' || type === '^') { sections.push(token); } else if (type === '/') { // Check section nesting. openSection = sections.pop(); if (!openSection) throw new Error('Unopened section "' + value + '" at ' + start); if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); } else if (type === 'name' || type === '{' || type === '&') { nonSpace = true; } else if (type === '=') { // Set the tags for the next time around. compileTags(value); } } // Make sure there are no open sections when we're done. openSection = sections.pop(); if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); return nestTokens(squashTokens(tokens)); }
如上是 parseTemplate 源碼,首先會進入 parseTemplate 函數內部,代碼依次往下看,我們會看到首先會調用compileTags函數, 該函數有一個參數 tagsToCompile。從源碼上下文中可以看到 mustache.tags 默認值為:[ '{{', '}}' ]; 因此 tagsToCompile = [ '{{', '}}' ]; 如果 tagsToCompile 是字符串的話,就執行 tagsToCompile = tagsToCompile.split(spaceRe, 2); 這句代碼。
注意:其實我覺得這邊 typeof tagsToCompile === 'string' 不可能會是字符串,如果是字符串的話,那么在 parse 函數內部就會直接報錯了,如下代碼內部:
Writer.prototype.parse = function parse (template, tags) { var cache = this.cache; var cacheKey = template + ':' + (tags || mustache.tags).join(':'); }
如上,如果tags 傳值了的話,它一定是一個數組,如果是字符串的話,那么使用 join分隔符會報錯的。
如果 tagsToCompile 不是一個數組 或 它的長度 不等於2的話,那么就拋出一個錯誤。因為開始標簽和結束標簽必須成對傳遞。
繼續往下看代碼:openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
如上代碼,首先會調用 escapeRegExp 函數,傳遞了一個參數 tagsToCompile[0],從上面分析我們知道 tagsToCompile = [ '{{', '}}' ]; 因此 tagsToCompile[0] = '{{'了。escapeRegExp 函數代碼如下:
function escapeRegExp (string) { // $& 的含義是:與 regexp 相匹配的子串。 return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'); }
因此 代碼實際就返回了這樣的了
return '{{'.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
$& 含義是 與 regexp 相匹配的子串;那么匹配了被替換的結果就是 "\{\{";
因為 它匹配到 "{{", 匹配到第一個 "{" 的話,結果被替換為 "\{", 同理匹配到第二個的時候 也是 '\{'; 因此結果就是:"\{\{"; 也可以理解對 { 進行字符串轉義。
因此 openingTagRe = new RegExp("\{\{" + '\\s*') = /\{\{\s*/; 接着往下執行代碼:closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
tagsToCompile[1] 值為 "}}"; 同理 escapeRegExp(tagsToCompile[1]) 結果就變為:"\}\}"; 因此 closingTagRe = new RegExp("\\s*" + "\}\}") = /\s*\}\}/;
從上面我們可知:openingTagRe 的含義可以理解為 開始標簽,因此正則為 /\{\{\s*/ 就是匹配 開始標簽 "{{ " 或 "{{",后面允許0個或多個空白。因為我們編寫html模板的時候會這樣寫 {{ xxx }} 這樣的。 因此 openingTagRe = /\{\{\s*/; 同理可知:closingTagRe 就是閉合標簽了,因此正則需要為 /\s*\}\}/; 那么可以匹配結束標簽 " }}" 或 "}}" 這樣的了。因此 closingTagRe = /\s*\}\}/;
繼續往下執行代碼:
closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1])); closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + "}}")) = /\s*\}\}\}/; 該closingCurlyRe是匹配 " }}}" 或 "}}}" 這樣的。
繼續往下看代碼:var scanner = new Scanner(template);
如上代碼,會實列化 Scanner 函數,該函數會傳遞一個 template參數進去,template參數的值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; 下面我們來看下 Scanner 函數源碼如下:
function Scanner (string) { this.string = string; this.tail = string; this.pos = 0; };
因此可以分別得出如下值:
this.string 值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; this.tail 的值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; this.pos = 0; 因此 scanner 實例化的值為 = { string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", pos: 0 };
繼續看代碼:var start, type, value, chr, token, openSection; 這些變量我們先不管他,然后繼續代碼往下:
然后就進入了while循環代碼了,while (!scanner.eos()) {} 這樣的。
eos方法如下所示:該方法的作用就是判斷 scanner對象的 tail屬性值是否等於空,如果等於空,說明模板數據已經被解析完成了。
如果解析完成了,就跳出while循環。如下代碼:
Scanner.prototype.eos = function eos () { return this.tail === ''; };
第一次調用 scanner.eos(); 結果返回 false; 因此進入 while循環內部,start = scanner.pos = 0;
1. 第一次while循環
代碼初始化調用 value = scanner.scanUntil(openingTagRe); openingTagRe 值為 /\{\{\s*/; scanUntil函數代碼如下:
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
如上 Scanner.prototype.scanUntil 函數代碼可以看到,這里的this指向了 scanner 對象,因此 this.tail = "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}";
re = /\{\{\s*/; 因此 var index = this.tail.search(re) = 0; 會進入 case 0: 的情況,因此 match = ''; 最后 this.pos += ''.length = this.pos + 0 = 0; 最后返回 match = ''; 因此 value = ''; 因此不會進入下面的 if(value){} 的語句里面,
scanner 此時值為:= { pos: 0, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" }
繼續往下代碼執行 if (!scanner.scan(openingTagRe)) openingTagRe的值 = /\{\{\s*/; 函數如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
1. if (!scanner.scan(openingTagRe)) {} 調用的時候,openingTagRe 值為 /\{\{\s*/; 因此re的值為 /\{\{\s*/ 此時 this.tail 值為 "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}",re的值為:/\{\{\s*/;
var match = "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}".match(/\{\{\s*/); 因此:
match = [ "{{", index: 0, groups: undefined, input: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" ];
因此 var string = match[0]; 即:string = "{{"; this.tail = this.tail.substring(string.length); this.tail = "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}".substring(2); 最后 this.tail = "#msg}} {{.}} * <div>{{name}}</div> {{/msg}}";
this.pos += "{{".length = 2; 返回 return string; 最后返回 "{{";
此時 scanner 的值為 = { pos: 2, tail: "#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
繼續代碼往下執行,看第二點解釋:
2. 在parseTemplate函數中的 type = scanner.scan(tagRe) || 'name'; 這個代碼調用的時候; 此時:this.tail的值為 = "#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; tagRe 在頁面初始化值為 = /#|\^|\/|>|\{|&|=|!/; 因此 re = /#|\^|\/|>|\{|&|=|!/; 因此 var match = this.tail.match(re) = "#msg}} {{.}} * <div>{{name}}</div> {{/msg}}".match(/#|\^|\/|>|\{|&|=|!/);
即match的值為如下:
var match = [ "#", index: 0, groups: undefined, input: "#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" ];
因此 var string = match[0]; 即:string = '#'; this.tail = this.tail.substring(string.length); this.tail = "#msg}} {{.}} * <div>{{name}}</div> {{/msg}}".substring(1);
最后 this.tail = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; this.pos += "#".length = 3; 返回 return string; 最后返回 '#';
最后返回 type 的值為 "#"; 此時的 scanner 的值為:
scanner = { pos: 3, tail: "msg}} {{.}} * <div>{{name}}</div> {{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" }
代碼繼續往下執行,看下面第三點解釋:
3. 在 parseTemplate函數中的 scanner.scan(whiteRe); 中調用。 whiteRe 在頁面初始化的正則為:var whiteRe = /\s*/;
從上面第二次調用的返回結果來看scanner的值為:
scanner的值為:= { pos: 3, tail: "msg}} {{.}} * <div>{{name}}</div> {{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
此時:this.tail 的值為 = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; re = /\s*/;
Scanner.prototype.scan 函數源碼如下(方便查看源碼):
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
因此 match = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}".match(/\s*/);
var match = [ "", index: 0, group: undefined, input: "msg}} {{.}} * <div>{{name}}</div> {{/msg}}" ];
因此 var string = match[0]; 即:string = ""; this.tail = this.tail.substring(0) = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}";
this.pos += 0; this.pos = 3; 返回 return string; 最后返回 "";
此時的 scanner 的值為:
scanner = { pos: 3, tail: "msg}} {{.}} * <div>{{name}}</div> {{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
由上面我們知道 type = "#"; 因此會直接跳到 else 代碼內部。
if (type === '=') { value = scanner.scanUntil(equalsRe); scanner.scan(equalsRe); scanner.scanUntil(closingTagRe); } else if (type === '{') { value = scanner.scanUntil(closingCurlyRe); scanner.scan(curlyRe); scanner.scanUntil(closingTagRe); type = '&'; } else { value = scanner.scanUntil(closingTagRe); }
因此 value = scanner.scanUntil(closingTagRe); 執行,看如下代碼解釋:
函數代碼如下:
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
scanner.scanUntil(closingTagRe);調用的時候;closingTagRe = "/\s*\}\}/";
因此 re = "/\s*\}\}/"; 從上面分析我們可以知道,最終 scanner 對象返回的值如下:
scanner = { pos: 3, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
因此 此時的 var index = this.tail.search(re) = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}".search(/\s*\}\}/) = 3;
因此會進入 default 的情況下;match = this.tail.substring(0, index); match = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}".substring(0, 3); = "msg";
因此 this.tail = this.tail.substring(index); this.tail = "msg}} {{.}} * <div>{{name}}</div> {{/msg}}".substring(3);
最后 this.tail 的值為 = "}} {{.}} * <div>{{name}}</div> {{/msg}}"; this.pos += match.length; 因此 this.pos = 3 + 3 = 6;
最后返回 match; 因此最后就返回 "msg" 字符串了。
此時我們再看下 scanner 的值為如下:
scanner = { pos: 6, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "}} {{.}} * <div>{{name}}</div> {{/msg}}" };
4. 在 parseTemplate 函數 內部中 if (!scanner.scan(closingTagRe)) 這句代碼時候調用。
此時 scanner 的值如下所示:
scanner = { pos: 6, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "}} {{.}} * <div>{{name}}</div> {{/msg}}" }; closingTagRe = "/\s*\}\}/";
Scanner.prototype.scan 函數源碼如下(方便查看源碼):
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
此時 this.tail 的值為 = "}} {{.}} * <div>{{name}}</div> {{/msg}}"; re = /\s*\}\}/;
var match = "}} {{.}} * <div>{{name}}</div> {{/msg}}".match(/\s*\}\}/);
var match = { "}}", groups: undefined, index: 0, input: "}} {{.}} * <div>{{name}}</div> {{/msg}}" }; var string = match[0] = "}}"; this.tail = this.tail.substring(string.length);
因此 this.tail = "}} {{.}} * <div>{{name}}</div> {{/msg}}".substring(2);
最后 this.tail = " {{.}} * <div>{{name}}</div> {{/msg}}";
this.pos += "}}".length = 8;
最后返回 "}}"; 因此此時的 scannel 的值變為如下:
scanner = { pos: 8, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: " {{.}} * <div>{{name}}</div> {{/msg}}" };
代碼繼續往下執行, 如下代碼:
token = [ type, value, start, scanner.pos ]; tokens.push(token); if (type === '#' || type === '^') { sections.push(token); } else if (type === '/') { // ... } else if (type === 'name' || type === '{' || type === '&') { nonSpace = true; } else if (type === '=') { // Set the tags for the next time around. compileTags(value); }
因此 token = ["#", "msg", 0, 8]; tokens = [["#", "msg", 0, 8]];
因為 type = "#", 因此進入第一個if循環內部。因此 sections = [["#", "msg", 0, 8]];
2. 第二次while循環
此時的 scannel 的值為如下:
scanner = { pos: 8, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: " {{.}} * <div>{{name}}</div> {{/msg}}" };
因此 start = 8;
繼續執行如下代碼:
value = scanner.scanUntil(openingTagRe); scanUtil 源碼函數如下(為了方便理解,繼續貼下代碼) Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
openingTagRe的值為:openingTagRe = /\{\{\s*/; 因此 re = /\{\{\s*/; 執行代碼:var index = this.tail.search(re), match;
由上返回的數據可知:this.tail = " {{.}} * <div>{{name}}</div> {{/msg}}"; 因此 var index = " {{.}} * <div>{{name}}</div> {{/msg}}".search(/\{\{\s*/) = 1;
同理進入default語句內部,因此 match = this.tail.substring(0, index);
match = " {{.}} * <div>{{name}}</div> {{/msg}}".substring(0, 1) = " ";
this.tail = this.tail.substring(index);
this.tail = " {{.}} * <div>{{name}}</div> {{/msg}}".substring(1);
最后 this.tail = "{{.}} * <div>{{name}}</div> {{/msg}}";
this.pos += match.length;
this.pos = 8 + 1 = 9;
最后返回 return match; 返回 " ";
因此 此時 scanner 的值變為如下:
scanner = { pos: 9, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{.}} * <div>{{name}}</div> {{/msg}}" };
執行完成后,value 此時的值為 " "; 因此會進入 if (value) {} 的內部代碼。
注意:if("") {} 和 if (" ") {} 結果是不一樣的。 "".length = 0; " ".length = 1; 源碼如下(方便代碼理解):
var regExpTest = RegExp.prototype.test; function testRegExp (re, string) { return regExpTest.call(re, string); } var nonSpaceRe = /\S/; // 匹配非空白字符 function isWhitespace (string) { return !testRegExp(nonSpaceRe, string); } if (value) { for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); if (isWhitespace(chr)) { spaces.push(tokens.length); } else { nonSpace = true; } tokens.push([ 'text', chr, start, start + 1 ]); start += 1; // Check for whitespace on the current line. if (chr === '\n') stripSpace(); } }
因此 chr = ' '; 調用 isWhitespace(chr); 方法,其實就是調用了 RegExp.prototype.test.call(/\S/, ' '); 判斷 ' ' 是否是非空白字符,因此返回false,在 isWhitespace 函數內部,使用了 !符號,因此最后返回true。
spaces.push(tokens.length); 從上面代碼可知,我們知道 tokens = [["#", "msg", 0, 8]];
因此 spaces = [1]; tokens.push([ 'text', chr, start, start + 1 ]); 執行后 tokens的值變為如下:
tokens = [["#", "msg", 0, 8], ['text', ' ', 8, 9]]; start +=1; 因此 start = 9;
如果 chr === '\n'; 則執行 stripSpace()方法,這里為false,因此不執行。
繼續執行如下代碼:
if (!scanner.scan(openingTagRe)) break; openingTagRe的值為:openingTagRe = /\{\{\s*/;
scan 函數代碼如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
因此 re = /\{\{\s*/; 從上面可知,我們的scanner的值為如下:
scanner = { pos: 9, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{.}} * <div>{{name}}</div> {{/msg}}" };
繼續執行 Scanner.prototype.scan() 函數內部代碼:
var match = this.tail.match(re) = "{{.}} * <div>{{name}}</div> {{/msg}}".match(/\{\{\s*/);
因此 match的匹配結果如下:
var match = [ "{{", index: 0, groups: undefined, input: "{{.}} * <div>{{name}}</div> {{/msg}}" ]; var string = match[0] = "{{"; this.tail = this.tail.substring(string.length);
因此 this.tail = "{{.}} * <div>{{name}}</div> {{/msg}}".substring(2);
最后 this.tail = ".}} * <div>{{name}}</div> {{/msg}}";
this.pos += string.length; 因此 this.pos = 9 + 2 = 11;
最后返回 return string; 即返回 "{{";
因此 此時 scanner 的值變為如下:
scanner = { pos: 11, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: ".}} * <div>{{name}}</div> {{/msg}}" };
接着繼續執行代碼:type = scanner.scan(tagRe) || 'name';
tagRe 在頁面是定義的正則為:/#|\^|\/|>|\{|&|=|!/;
因此又會執行 Scanner.prototype.scan = function scan (re) {}, 代碼如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; }
由上面可知:
scanner = { pos: 11, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: ".}} * <div>{{name}}</div> {{/msg}}" }; re = /#|\^|\/|>|\{|&|=|!/;
因此 var match = this.tail.match(re) = ".}} * <div>{{name}}</div> {{/msg}}".match(/#|\^|\/|>|\{|&|=|!/);
var match = [ ">", index: 10, groups: undefined, input: ".}} * <div>{{name}}</div> {{/msg}}" ];
如上代碼:match.index === 10; 因此 不等於0;所以就直接返回 ''; 跳出函數,因此 type = 'name' 了;
繼續執行如下代碼:scanner.scan(whiteRe); whiteRe = /\s*/;
還是一樣執行 Scanner.prototype.scan = function scan (re) {} 函數;
因此 var match = ".}} * <div>{{name}}</div> {{/msg}}".match(/\s*/);
var match = [ '', groups: undefined, index: 0, input: ".}} * <div>{{name}}</div> {{/msg}}" ];
再接着執行代碼 var string = match[0] = '';
this.tail = this.tail.substring(0) = ".}} * <div>{{name}}</div> {{/msg}}";
this.pos += string.length = 11 + 0 = 11;
此時 scanner 的值,和上一步的值一樣:
scanner = { pos: 11, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: ".}} * <div>{{name}}</div> {{/msg}}" };
最后返回 空字符串 '';
如上我們知道 type = 'name'; 因此 繼續進入如下else代碼:
if (type === '=') { } else if (type === '{') { } else { value = scanner.scanUntil(closingTagRe); }
再來看下 scanUntil 代碼如下:
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
如上代碼:closingTagRe = /\s*\}\}/;
var index = this.tail.search(re) = ".}} * <div>{{name}}</div> {{/msg}}".search(/\s*\}\}/) = 1;
因此進入 default語句內部。
因此 match = this.tail.substring(0, index) = ".}} * <div>{{name}}</div> {{/msg}}".substring(0, 1);
match = '.';
this.tail = this.tail.substring(index) = ".}} * <div>{{name}}</div> {{/msg}}".substring(1);
因此 this.tail = "}} * <div>{{name}}</div> {{/msg}}";
this.pos += match.length = 11 + 1 = 12; 最后 return match; 返回 '.';
此時 scanner的值為如下:
scanner = { pos: 12, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "}} * <div>{{name}}</div> {{/msg}}" };
接着繼續執行 if (!scanner.scan(closingTagRe)){} 代碼; closingTagRe = /\s*\}\}/;
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
因此調用 Scanner.prototype.scan() 函數后,
var match = "}} * <div>{{name}}</div> {{/msg}}".match(/\s*\}\}/); var match = [ "}}", groups: undefined, index: 0, input: "}} * <div>{{name}}</div> {{/msg}}" ]; var string = match[0] = "}}"; this.tail = this.tail.substring(string.length);
因此 this.tail = "}} * <div>{{name}}</div> {{/msg}}".substring(2);
最后 this.tail = " * <div>{{name}}</div> {{/msg}}";
this.pos = 12 + 2 = 14;
最后 return string; 返回 "}}";
此時 scanner的值為如下:
scanner = { pos: 14, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: " * <div>{{name}}</div> {{/msg}}" };
繼續執行代碼:token = [ type, value, start, scanner.pos ];
因此 token = ['name', '.', 9, 14];
繼續往下執行代碼:
tokens.push(token);
因此此時 tokens = [ ["#", "msg", 0, 8], ['text', ' ', 8, 9], ["name", ".", 9, 14] ];
此時 type = 'name'; 因此 nonSpace = true; 執行完成后。繼續while循環。
第三次while循環
此時scanner值為如下:
scanner = { pos: 14, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: " * <div>{{name}}</div> {{/msg}}" };
start = scanner.pos; 因此 start = 14;
value = scanner.scanUntil(openingTagRe); 執行這句代碼:
openingTagRe = /\{\{\s*/;
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
var index = this.tail.search(re) = " * <div>{{name}}</div> {{/msg}}".search(/\{\{\s*/);
因此 var index = 8;
然后又繼續進入 default語句;此時 match = this.tail.substring(0, index);
match = " * <div>{{name}}</div> {{/msg}}".substring(0, 8) = " * <div>";
this.tail = this.tail.substring(index) = " * <div>{{name}}</div> {{/msg}}".substring(8);
因此 this.tail = "{{name}}</div> {{/msg}}";
this.pos += match.length = 14 + 8 = 22;
因此 此時scanner值為如下:
scanner = { pos: 22, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{name}}</div> {{/msg}}" };
最后返回 " * <div>" 賦值給 value;
因此繼續進入 if (value) {} 代碼內部:
if (value) { for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); if (isWhitespace(chr)) { spaces.push(tokens.length); } else { nonSpace = true; } tokens.push([ 'text', chr, start, start + 1 ]); start += 1; // Check for whitespace on the current line. if (chr === '\n') stripSpace(); } } var regExpTest = RegExp.prototype.test; function testRegExp (re, string) { return regExpTest.call(re, string); } var nonSpaceRe = /\S/; function isWhitespace (string) { return !testRegExp(nonSpaceRe, string); }
而此時 value.length = 8了;因此在for語句需要循環8次。 i = 0:chr = value.charAt(i) = " * <div>".charAt(0) = " "; 執行 isWhitespace(chr); 函數代碼,如下代碼: RegExp.prototype.test.call(/\S/, ' '); 判斷 ' ' 是否是非空白字符,因此返回false,因此 !false 就是true了。 因此 執行 spaces.push(tokens.length); 之前tokens = [["#", "msg", 0, 8],["text", " ", 8, 9],["name", ".", 9, 14]]; spaces = [1]; 因此此時 spaces = [1, 3]; 了。 接着執行 tokens.push([ 'text', chr, start, start + 1 ]); 因此 tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ]; start += 1; 因此 start = 15; i = 1:chr = value.charAt(i) = " * <div>".charAt(1) = "*"; 執行 isWhitespace(chr); 返回false; 因此進入else 語句; 此時:nonSpace = true; 繼續執行代碼:tokens.push([ 'text', chr, start, start + 1 ]); 因此tokens的值變為 如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16] ]; start += 1; 因此 start = 16; i = 2:chr = value.charAt(i) = " * <div>".charAt(2) = " "; 執行 isWhitespace(chr); 返回true; 和第一步一樣, 因此 執行 spaces.push(tokens.length); 因此 spaces.push(tokens.length); 即 spaces = [1, 3, 5]; 繼續執行代碼:tokens.push([ 'text', chr, start, start + 1 ]); 因此tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17] ]; start +=1; 因此 start = 17; i = 3: chr = value.charAt(i) = " * <div>".charAt(3) = "<"; 執行 isWhitespace(chr); 返回false, 因此進入else 語句。此時:nonSpace = true; 繼續執行代碼:tokens.push([ 'text', chr, start, start + 1 ]); 因此tokens的值變為 如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18] ]; start +=1; 因此 start = 18; i = 4: 同理,和第三步一樣。因此 chr = 'd'; 因此tokens的值變為 如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19] ]; start +=1; 因此 start = 19; i = 5; 同理,和第三步一樣。因此 chr = 'i'; 最后tokens的值變為: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20] ]; start +=1; 因此 start = 20; i = 6; 同理,和第三步一樣。因此 chr = 'v'; 最后tokens的值變為: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ]; start +=1; 因此 start = 21; i = 7; 同理,和第三步一樣。因此 chr = '>'; 最后tokens的值變為: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22] ]; start +=1; 因此 start = 22; ng: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{name}}</div> {{/msg}}" }
繼續執行代碼:if (!scanner.scan(openingTagRe)) {}; 因此進入 Scanner.prototype.scan = function scan (re) {} 函數代碼內部。源碼如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
re 值 = /\{\{\s*/; 此時 scanner 值為如下:
scanner = { pos: 22, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "{{name}}</div> {{/msg}}" }
因此 var match = "{{name}}</div> {{/msg}}".match(/\{\{\s*/);
var match = [ "{{", index: 0, groups: undefined, input: "{{name}}</div> {{/msg}}" ];
var string = match[0]; 因此 var string = "{{";
this.tail = this.tail.substring(string.length) = "{{name}}</div> {{/msg}}".substring(2);
因此:this.tail = "name}}</div> {{/msg}}";
this.pos += string.length; this.pos = 22 + 2 = 24; 最后返回 "{{". 因此此時 scanner的值變為如下:
scanner = { pos: 24, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "name}}</div> {{/msg}}" };
繼續執行代碼:type = scanner.scan(tagRe) || 'name';
函數代碼如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
如上:tagRe = /#|\^|\/|>|\{|&|=|!/; this.tail = "name}}</div> {{/msg}}";
因此 var match = "name}}</div> {{/msg}}".match(/#|\^|\/|>|\{|&|=|!/);
var match = [ '/', index: 7, groups: undefined, input: "name}}</div> {{/msg}}" ];
由於 match.index !== 0; 因此直接 返回 ''; 此時 type = 'name';
繼續執行代碼:scanner.scan(whiteRe); whiteRe 的值 = /\s*/; 然后又調用 Scanner.prototype.scan 函數。
因此 var match = "name}}</div> {{/msg}}".match(/\s*/);
var match = [ "", index: 0, groups: undefined, input: "name}}</div> {{/msg}}" ]; var string = match[0] = ""; this.tail = this.tail.substring(string.length); this.tail = this.tail.substring(0); this.tail = "name}}</div> {{/msg}}"; this.pos = 24;
最后 返回 return string; 返回 "";
此時 scanner 的值變為如下:
scanner = { pos: 24, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "name}}</div> {{/msg}}" };
由於上面 type = "name"; 因此 就會執行 else 語句代碼,因此 執行 value = scanner.scanUntil(closingTagRe);
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; }; var closingTagRe = /\s*\}\}/;
因此繼續調用 Scanner.prototype.scanUntil 函數。
因此 var index = "name}}</div> {{/msg}}".search(/\s*\}\}/) = 4;
因此 繼續進入 default語句代碼;
match = "name}}</div> {{/msg}}".substring(0, 4);
match = "name";
this.tail = "name}}</div> {{/msg}}".substring(4) = "}}</div> {{/msg}}";
this.pos += match.length = 24 + 4 = 28;
最后我們返回 return "name"; 此時 scanner 的值變為如下:
scanner = { pos: 28, tail: "}}</div> {{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
因此 value = "name";
繼續執行下面的代碼:
if (!scanner.scan(closingTagRe)) {};
又會調用 Scanner.prototype.scan = function scan (re) {} 函數代碼了。
代碼如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
因此值分別為如下:
var match = "}}</div> {{/msg}}".match(/\s*\}\}/); var match = [ "}}", index: 0, groups: undefined, input: "}}</div> {{/msg}}" ]; var string = match[0] = "}}"; this.tail = this.tail.substring(string.length) = "}}</div> {{/msg}}".substring(2); this.tail = "</div> {{/msg}}"; this.pos += string.length; this.pos = 28 + 2 = 30;
最后我們返回 "}}". 因此此時 scanner 的值變為如下:
scanner = { pos: 30, tail: "</div> {{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
繼續執行代碼: token = [ type, value, start, scanner.pos ]; 代碼;因此token的值為如下:
token = ['name', 'name', 22, 30];
繼續執行 tokens.push(token); 因此 tokens的值變為如下:
tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30] ];
由於 type = "name"; 因此 nonSpace = true;
第四次while循環
此時 scanner = { pos: 30, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}", tail: "</div> {{/msg}}" }; start = scanner.pos; start = 30; // Match any text between tags. value = scanner.scanUntil(openingTagRe); openingTagRe 值為:openingTagRe = /\{\{\s*/;
調用 scanUntil 函數代碼如下:
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; }; var index = "</div> {{/msg}}".search(/\{\{\s*/); var index = 7;
因此 進入 default 語句代碼:
match = this.tail.substring(0, index) = "</div> {{/msg}}".substring(0, 7);
因此 match = "</div> ";
this.tail = this.tail.substring(index) = "</div> {{/msg}}".substring(7);
this.tail = "{{/msg}}";
this.pos += match.length = 30 + 7 = 37;
最后返回 return match; 因此 返回 "</div> ";
此時 scanner 的值變為如下:
scanner = { pos: 37, tail: "{{/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
因此 value = "</div> ";
因此會進入 if (value) {}; 語句代碼,如下所示:
if (value) { for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); if (isWhitespace(chr)) { spaces.push(tokens.length); } else { nonSpace = true; } tokens.push([ 'text', chr, start, start + 1 ]); start += 1; // Check for whitespace on the current line. if (chr === '\n') stripSpace(); } }
由於value的值為 "</div> "; 長度為7. 因此會在內部for循環中循環7次。依次看下:
1. i = 0; 執行 chr = value.charAt(i); 因此 chr = "<"; 此時 chr 不是空白字符,因此該 isWhitespace(chr) 函數返回false。 因此 nonSpace = true; 接着執行:tokens.push([ 'text', chr, start, start + 1 ]); 因此 tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31] ]; start += 1; 因此 start 值為 31. 2. i = 1; 同第一步一樣,因此 chr = "/"; 因此 tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32] ]; start += 1; 因此 start 值為 32. 3. i = 2; 同第一步一樣,因此 chr = "d"; 因此 tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33] ]; start += 1; 因此 start 值為 33. 4. i = 3; 同第一步一樣,因此 chr = "i"; 因此 tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33], ["text", "i", 33, 34] ]; start += 1; 因此 start 值為 34. 5. i = 4; 同第一步一樣,因此 chr = "i"; 因此 tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33], ["text", "i", 33, 34], ["text", "v", 34, 35] ]; start += 1; 因此 start 值為 35. 6. i = 5; 同第一步一樣,因此 chr = ">"; 因此 tokens的值變為如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33], ["text", "i", 33, 34], ["text", "v", 34, 35], ["text", ">", 35, 36] ]; start += 1; 因此 start 值為 36. 7. i = 6 執行 chr = value.charAt(i); 因此 chr = " "; 此時 chr 為空白字符,因此會執行 isWhitespace(chr) 函數返回true。 因此執行 spaces.push(tokens.length); 因此 spaces 的值為: spaces = [1, 3, 5, 18]; 繼續執行代碼:tokens.push([ 'text', chr, start, start + 1 ]); 因此 tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33], ["text", "i", 33, 34], ["text", "v", 34, 35], ["text", ">", 35, 36], ["text", " ", 36, 37] ]; start += 1; 因此 start 值為37。
接着執行代碼: if (!scanner.scan(openingTagRe)) { break; }
openingTagRe 值為:openingTagRe = /\{\{\s*/;
Scanner.prototype.scan() 函數代碼如下:
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
由上可知,此時 scanner 的值為 = {
pos: 37,
tail: "{{/msg}}",
string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"
};
因此 var match = "{{/msg}}".match(/\{\{\s*/);
var match = [ "{{", index: 0, groups: undefined, input: "{{/msg}}" ]; var string = match[0] = "{{"; this.tail = this.tail.substring(string.length); this.tail = "{{/msg}}".substring(2) = "/msg}}"; this.pos += 2 = 39;
最后返回 return string; 就返回了 "{{"; 此時 scannel的值變為如下:
scanner = { pos: 39, tail: "/msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
再執行代碼:type = scanner.scan(tagRe) || 'name';
tagRe 的值 = /#|\^|\/|>|\{|&|=|!/; 因此會繼續調用 Scanner.prototype.scan = function scan (re) {} 函數。
繼續執行該函數內部的代碼,因此:
var match = this.tail.match(re); var match = "/msg}}".match(/#|\^|\/|>|\{|&|=|!/); var match = [ "/", index: 0, groups: undefined, input: "/msg}}" ]; var string = match[0] = "/"; this.tail = this.tail.substring(string.length) = "/msg}}".substring(1); this.tail = "msg}}"; this.pos += 1; 因此 this.pos = 40;
最后返回 "/"; 因此 type = '/';
此時 scanner 的值為如下:
scanner = { pos: 40, tail: "msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
再執行代碼:scanner.scan(whiteRe); whiteRe 的值 = /\s*/;
因此又進入 scan函數內部依次執行如下:
var match = this.tail.match(re) = "msg}}".match(/\s*/); var match = [ "", index: 0, groups: undefined, input: "msg}}" ]; var string = match[0]; var string = ""; this.tail = this.tail.substring(string.length); this.tail = "/msg}}".substring(0) = "/msg}}"; this.pos += string.length; this.pos = 40;
最后返回 return string; 返回 ""; 跳出該函數,繼續執行下一步代碼。
此時 scanner = { pos: 40, tail: "msg}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
因此代碼執行到 if (type === '=') {} else if(type === '{') {} else {} 這里了
由於 type = '/'; 因此代碼執行到 else 內部了。
執行代碼:value = scanner.scanUntil(closingTagRe);
closingTagRe 值為 = /\s*\}\}/;
Scanner.prototype.scanUntil 函數如下:
Scanner.prototype.scanUntil = function scanUntil (re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ''; break; case 0: match = ''; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
因此 var index = "msg}}".search(/\s*\}\}/) = 3;
執行到 default語句代碼內。
因此 match = this.tail.substring(0, index) = "msg}}".substring(0, 3) = "msg";
this.tail = "msg}}".substring(3) = "}}";
this.pos += match.length; this.pos = 40 + 3 = 43;
最后返回 return match; 因此 返回 "msg"; 此次此刻 scanner的值變為如下:
scanner = { pos: 43, tail: "}}", string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" };
現在跳出 Scanner.prototype.scanUntil 函數代碼,執行下一步代碼:如下:
if (!scanner.scan(closingTagRe)) {}; 因此會調用 scan 函數代碼。
closingTagRe 值為 = /\s*\}\}/;
Scanner.prototype.scan = function scan (re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ''; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; };
因此 var match = "}}".match(/\s*\}\}/);
var match = [ "}}", index: 0, groups: undefined, input: "}}" ]; var string = match[0] = "}}"; this.tail = this.tail.substring(string.length) = "}}".substring(2) = ""; this.pos = 43 + 2 = 45;
最后返回 return string; 因此返回 "}}". 此時scanner的值變為如下:
scanner = { pos: 45, string: "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}" tail: "" };
跳出 Scanner.prototype.scan 函數代碼后,接着執行下面的代碼:
token = [ type, value, start, scanner.pos ];
tokens.push(token);
因此 token = ['/', 'msg', 37, 45];
tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33], ["text", "i", 33, 34], ["text", "v", 34, 35], ["text", ">", 35, 36], ["text", " ", 36, 37], ['/', 'msg', 37, 45] ];
此時此刻 type = '/'; 因此 會執行
else if (type === '/') { // Check section nesting. openSection = sections.pop(); if (!openSection) throw new Error('Unopened section "' + value + '" at ' + start); if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
else if 內部的代碼,如上所示:執行一些清空操作。
因此 這個時候會跳出while循環了,因為所有的字符都解析完畢了。最后一句代碼:return nestTokens(squashTokens(tokens));
我們會調用 nestTokens()函數,在調用該函數之前會調用 squashTokens(tokens);
squashTokens 函數代碼如下:
function squashTokens (tokens) { var squashedTokens = []; var token, lastToken; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { token = tokens[i]; if (token) { if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { lastToken[1] += token[1]; lastToken[3] = token[3]; } else { squashedTokens.push(token); lastToken = token; } } } return squashedTokens; }
由上面的一系列操作,我們知道tokens的值為如下:
tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22], ["name", "name", 22, 30], ["text", "<", 30, 31], ["text", "/", 31, 32], ["text", "d", 32, 33], ["text", "i", 33, 34], ["text", "v", 34, 35], ["text", ">", 35, 36], ["text", " ", 36, 37], ['/', 'msg', 37, 45] ];
首先在函數內部定義一個新數組 var squashedTokens = []; 然后循環傳進來的tokens的值。tokens的長度為20.
為了更清楚的理解具體做了哪些事情,我們繼續一步步把for循環拆開理解。如下所示:
i = 0; token = tokens[0] = ["#", "msg", 0, 8]; 因此進入 if (token) 內部代碼,由於是第一次循環,所以 lastToken 為undefined,因此進入else語句代碼; squashedTokens.push(token); 因此 squashedTokens = [ ["#", "msg", 0, 8] ]; lastToken = token; 因此 lastToken = ["#", "msg", 0, 8]; i = 1; token = tokens[1] = ["text", " ", 8, 9]; 進入if語句,if (token) 內部代碼,這個時候 lastToken 有值了,token[0] === 'text' 為true,lastToken也為true, 但是 lastToken = ["#", "msg", 0, 8]; 因此 lastToken[0] === 'text' 為false。因此還是進入else語句代碼: 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9] ]; lastToken = ["text", " ", 8, 9]; i = 2; token = tokens[2] = ["name", ".", 9, 14]; 進入if語句代碼,由於 token[0] === "name"; 因此進入else語句,此時 squashedTokens.push(token); 值為如下: ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14] ]; lastToken = ["name", ".", 9, 14]; ``` i = 3; token = tokens[3] = ["text", " ", 14, 15]; 進入if(token)語句代碼,token[0] === 'text' 為true; lastToken 也有值,為true,lastToken[0] === 'name' 為false,因此進入else語句代碼, 因此 squashedTokens 和 lastToken 值分別為如下: ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15] ]; lastToken = ["text", " ", 14, 15]; ``` i = 4; token = tokens[4] = ["text", "*", 15, 16]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true,因此進入 第二個if 語句代碼:執行 lastToken[1] += token[1]; lastToken[3] = token[3]; 代碼; 即:lastToken[1] = " " + "*" = " *"; lastToken[3] = 16; 因此 lastToken = ["text", " *", 14, 16]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " *", 14, 16] ]; ``` 這里明明沒有進else內部代碼,為什么這邊 squashedTokens 對象值也發生變化呢?那是因為 代碼里面對象數組的復制只是 淺拷貝,如else內部代碼:lastToken = token; 最后一次的token 和 lastToken 指向了同一個指針引用。因此也會導致數組 squashedTokens 的某項的也會發生改變。 i = 5; token = tokens[5] = ["text", " ", 16, 17]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true, 因此 lastToken[1] += token[1]; lastToken[1] = " *" + " " = " * "; lastToken[3] = 17; 即:lastToken = ["text", " * ", 14, 17]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * ", 14, 17] ]; ``` i = 6; token = tokens[6] = ["text", "<", 17, 18]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true, 因此 lastToken[1] += token[1]; lastToken[1] = " * " + "<" = " * <"; lastToken[3] = 18; 即:lastToken = ["text", " * <", 14, 18]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <", 14, 18] ]; ``` i = 7; token = tokens[7] = ["text", "d", 18, 19]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true, 因此 lastToken[1] += token[1]; lastToken[1] = " * <" + "d" = " * <d"; lastToken[3] = 19; 即:lastToken = ["text", " * <d", 14, 19]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <d", 14, 19] ]; ``` i = 8; token = tokens[8] = ["text", "i", 19, 20]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; lastToken[1] = " * <d" + "i" = " * <di"; lastToken[3] = 20; 即:lastToken = ["text", " * <di", 14, 20]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <di", 14, 20] ]; ``` i = 9; token = tokens[9] = ["text", "v", 20, 21]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; lastToken[1] = " * <di" + "v" = " * <div"; lastToken[3] = 21; 即:lastToken = ["text", " * <div", 14, 21]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div", 14, 21] ] ``` i = 10; token = tokens[10] = ["text", ">", 21, 22]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; lastToken[1] = " * <div" + ">" = " * <div>"; lastToken[3] = 22; 即:lastToken = ["text", " * <div>", 14, 22]; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22] ] ``` i = 11; token = tokens[11] = ["name", "name", 22, 30]; token[0] === 'text' 為false, 因此進入else語句代碼:squashedTokens.push(token); lastToken = token; ``` 因此 squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30] ]; lastToken = ["name", "name", 22, 30]; ``` i = 12; token = tokens[12] = ["text", "<", 30, 31]; 由於上一步 lastToken[0] = "name"; 因此進入else語句代碼,即: ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "<", 30, 31] ]; lastToken = ["text", "<", 30, 31]; ``` i = 13; token = tokens[13] = ["text", "/", 31, 32]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此:lastToken[1] += token[1]; 即:lastToken[1] = "<" + "/" = "</"; lastToken[3] = token[3]; 即:lastToken[3] = 32; 因此 lastToken = ["text", "</", 30, 32]; ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</", 30, 32] ]; ``` i = 14; token = tokens[14] = ["text", "d", 32, 33]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; 即:lastToken[1] = "</" + "d" = "</d"; lastToken[3] = token[3]; 即:lastToken[3] = 33; 因此 lastToken = ["text", "</d", 30, 33]; ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</d", 30, 33] ]; ``` i = 15; token = tokens[15] = ["text", "i", 33, 34]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; 即:lastToken[1] = "</d" + "i" = "</di"; lastToken[3] = token[3]; 即:lastToken[3] = 34; 因此 lastToken = ["text", "</di", 30, 34]; ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</di", 30, 34] ]; ``` i = 16; token = tokens[16] = ["text", "v", 34, 35]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; 即:lastToken[1] = "</di" + "v" = "</div"; lastToken[3] = token[3]; 即:lastToken[3] = 35; 因此 lastToken = ["text", "</div", 30, 35]; ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div", 30, 35] ]; ``` i = 17; token = tokens[17] = ["text", ">", 35, 36]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; 即:lastToken[1] = "</div" + ">" = "</div>"; lastToken[3] = token[3]; 即:lastToken[3] = 36; 因此 lastToken = ["text", "</div>", 30, 36]; ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div>", 30, 36] ]; ``` i = 18; token = tokens[18] = ["text", " ", 36, 37]; token[0] === 'text' 為true; lastToken 也有值,為true, lastToken[0] === 'text' 也為true; 因此 lastToken[1] += token[1]; 即:lastToken[1] = "</div>" + " " = "</div> "; lastToken[3] = token[3]; 即:lastToken[3] = 37; 因此 lastToken = ["text", "</div> ", 30, 37]; ``` squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ]; ``` i = 19; token = tokens[19] = ['/', 'msg', 37, 45]; token[0] === 'text' 為false,因此進入else語句代碼。即:squashedTokens.push(token); lastToken = token; ``` 因此:squashedTokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ['/', 'msg', 37, 45] ]; lastToken = ['/', 'msg', 37, 45];
最后我們的代碼返回 return squashedTokens;
最后一步我們就是要調用 nestTokens 函數了,函數代碼如下:
function nestTokens (tokens) { var nestedTokens = []; var collector = nestedTokens; var sections = []; var token, section; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { token = tokens[i]; switch (token[0]) { case '#': case '^': collector.push(token); sections.push(token); collector = token[4] = []; break; case '/': section = sections.pop(); section[5] = token[2]; collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; break; default: collector.push(token); } } return nestedTokens; }
tokens 值就是我們上面返回 return squashedTokens;值了,因此:
tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37], ['/', 'msg', 37, 45] ];
然后for循環遍歷,因為數組的長度為7,因此循環遍歷7次。再分別看下:
i = 0; token = ["#", "msg", 0, 8]; collector.push(token); 因此:collector = [ ["#", "msg", 0, 8] ]; sections.push(token); 因此:sections = [ ["#", "msg", 0, 8] ]; 執行:collector = token[4] = []; 因此 collector = []; sections = [ ["#", "msg", 0, 8, [] ] ]; 但是此時 nestedTokens = [ ["#", "msg", 0, 8, [] ] ]; 為什么是這樣的呢?按道理來說 var nestedTokens = []; var collector = nestedTokens; 這兩個數組是淺拷貝,但是為什么 collector數組被置空了,為什么 nestedTokens 數組還是有數據呢?我們在理解之前,我們可以看如下demo來理解下: ``` var arr1 = []; var arr2 = arr1; var token = [1]; arr2.push(token); console.log(arr2); // [ [1] ] console.log(arr1); // [ [1] ] arr2 = token[1] = []; console.log(token); // [ [1], []] console.log(arr2); // [] console.log(arr1); // [ [1], []] console.log(arr2.__proto__ === arr1.__proto__); // ture ``` 如上我們可以看到,上面兩個demo是仿照我們代碼的意思來的,其結果是一樣的,雖然其中一個數組值為空了,但是另外一個數組並沒有。 那是因為 arr2 = token[1] = []; 這句代碼,我們上面的token的值為 token = [1]; 這樣的,然后設置 token[1] = []; 因此 token = [1, []]; 最后把token[1] 的值賦值給 arr2了,因此arr2的值變為 []; 但是 arr1.__proto__ === arr2.__proto__ 是相等的,也就是他們倆還是指向了同一個引用,因為數組或對象 比較的不是值,而是是否是同一個引用。引用的還是同一個token對象。 所以 arr2 = [], 但是 arr1 = [ [1], [] ]; i = 1; token = ["text", " ", 8, 9]; token[0] = "text", 因此進入default語句代碼,因此 collector = [["text", " ", 8, 9]]; 由於 collector = token[4] = []; 是淺拷貝,因此 collector 值發生改變,會導致 token[4] 也會發生改變。 因此 sections 值變為如下: sections = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9] ] ] ]; nestedTokens = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9] ] ] ]; i = 2; token = ["name", ".", 9, 14]; token[0] = "name"; 因此 collector = [["text", " ", 8, 9],["name", ".", 9, 14]]; sections = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14] ] ] ]; nestedTokens = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14] ] ] ]; i = 3; token = ["text", " * <div>", 14, 22]; token[0] = "text"; collector = [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22] ]; sections = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22] ] ] ]; nestedTokens = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22] ] ] ]; i = 4; token = ["name", "name", 22, 30]; token[0] = "name"; collector = [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30] ]; sections = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30] ] ] ]; nestedTokens = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30] ] ] ]; i = 5; token = ["text", "</div> ", 30, 37]; token[0] = "text"; collector = [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ]; sections = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] ] ]; nestedTokens = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] ] ]; i = 6; token = ['/', 'msg', 37, 45]; token[0] = '/'; 此時此刻sections 和 nestedTokens 值如下: sections = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] ] ]; nestedTokens = [ ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] ] ]; 因此會進入如下代碼: case '/': section = sections.pop(); section[5] = token[2]; collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; break; 如上代碼執行完成后; section = ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] ]; sections = []; section[5] = token[2]; 因此 section 值變為如下: section = ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ]; 由於 sections.length = 0; 因此 把 nestedTokens 賦值給 collector; collector = nestedTokens; 最后返回的值:nestedTokens = [["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ]];
因此 Writer.prototype.render 如下方法中的 第一個 this.parse() 函數就返回了值; 因此 tokens的值就是上面返回的值。
Writer.prototype.render = function render (template, view, partials, tags) { var tokens = this.parse(template, tags); var context = (view instanceof Context) ? view : new Context(view); return this.renderTokens(tokens, context, partials, template); };
現在我們要調用 new Context(view) 方法了。
Context 函數有如下方法:
/* @param {view} { "name": "kongzhi", "msg": ['111'] } @param {parentContext} undefined */ function Context (view, parentContext) { this.view = view; this.cache = { '.': this.view }; this.parent = parentContext; } Context.prototype.push = function push (view) { return new Context(view, this); }; Context.prototype.lookup = function lookup (name) {};
因此 context.view = { "name": "kongzhi", "msg": ['111'] };
context.cache = { '.' : { "name": "kongzhi", "msg": ['111'] } };
context.parent = undefined;
context 實列除了上面列舉的三個屬性外,在原型上還有 push 和 lookup方法。
接下來就執行:this.renderTokens(tokens, context, partials, template); 代碼;
如上參數:
tokens = [["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ]]; context = { view: { "name": "kongzhi", "msg": ['111'] }, cache: { '.' : { "name": "kongzhi", "msg": ['111'] } }, parent: undefined, __proto__: { push: fn, lookup: fn } }; partials = undefined; template = "{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}";
現在我們繼續來看下 renderTokens 函數,代碼如下:
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) { var buffer = ''; var token, symbol, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { value = undefined; token = tokens[i]; symbol = token[0]; if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate); else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate); else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context); else if (symbol === 'text') value = this.rawValue(token); if (value !== undefined) buffer += value; } return buffer; }
由上可知:tokens = [["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ]];
因此循環tokens, 因此 token = tokens[i] = ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ];
因此 symbol = "#"; 因此就調用 renderSection 函數,調用完成后,返回的值賦值給value值,因此下面我們來看下 renderSection 函數的代碼如下所示:
/* * @param {token} token = ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ]; * @param {context} context = { view: { "name": "kongzhi", "msg": ['111'] }, cache: { '.' : { "name": "kongzhi", "msg": ['111'] } }, parent: undefined, __proto__: { push: fn, lookup: fn } }; * @param {partials} 是否是可重用的模板,目前沒有傳遞,值為undefined * @param {originalTemplate} 為頁面模板 值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}"; */ Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) { var self = this; var buffer = ''; var value = context.lookup(token[1]); // This function is used to render an arbitrary template // in the current context by higher-order sections. function subRender (template) { return self.render(template, context, partials); } if (!value) return; if (isArray(value)) { for (var j = 0, valueLength = value.length; j < valueLength; ++j) { buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate); } } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') { buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate); } else if (isFunction(value)) { if (typeof originalTemplate !== 'string') throw new Error('Cannot use higher-order sections without the original template'); // Extract the portion of the original template that the section contains. value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); if (value != null) buffer += value; } else { buffer += this.renderTokens(token[4], context, partials, originalTemplate); } return buffer; };
如上代碼,首先會調用 var value = context.lookup(token[1]); lookup函數,然后把值賦值給 value. 那么此時傳遞的 token[1] = 'msg'; 因此我們下面先看下 context.lookup(token[1]) 函數,代碼如下所示:
Context.prototype.lookup = function lookup (name) { var cache = this.cache; var value; if (cache.hasOwnProperty(name)) { value = cache[name]; } else { var context = this, intermediateValue, names, index, lookupHit = false; while (context) { if (name.indexOf('.') > 0) { intermediateValue = context.view; names = name.split('.'); index = 0; while (intermediateValue != null && index < names.length) { if (index === names.length - 1) lookupHit = ( hasProperty(intermediateValue, names[index]) || primitiveHasOwnProperty(intermediateValue, names[index]) ); intermediateValue = intermediateValue[names[index++]]; } } else { intermediateValue = context.view[name]; lookupHit = hasProperty(context.view, name); } if (lookupHit) { value = intermediateValue; break; } context = context.parent; } cache[name] = value; } if (isFunction(value)) value = value.call(this.view); return value; };
如上函數參數 name 值為 = "msg";
var cache = this.cache; 我們之前保存的 this.cache 的值為 = { ".": {name: 'kongzhi', msg: ['111']}};
很明顯 if (cache.hasOwnProperty(name)) {} 為false, 因此進入else語句了。
在else語句代碼如下:
var context = this; 這里的this指向了 Context 對象。之前Context值如下所示:
Context = { view: { "name": "kongzhi", "msg": ['111'] }, cache: { '.' : { "name": "kongzhi", "msg": ['111'] } }, parent: undefined, __proto__: { push: fn, lookup: fn } };
因此 context 也是這個值了。
執行while語句 判斷 while(context) {}; 然后判斷 if (name.indexOf('.') > 0) {} else {} 這樣的,我們上面name為
字符串 "msg"; 因此進入else語句代碼內部,intermediateValue = context.view[name]; 因此 intermediateValue = ['111']; lookupHit = hasProperty(context.view, name); 判斷 context.view 對象內部是否有 "msg" 這個屬性,因此lookupHit = true; 然后執行如下代碼:
if (lookupHit) { value = intermediateValue; break; }
因此 value = ['111']; 跳出while循環,當然 如果 lookupHit 為false的話,它會通過遞歸的方式查找父級元素,直到最頂層
元素,從這句代碼可以看到:context = context.parent; 最后跳出while循環后,執行下面的代碼 cache[name] = value;
因此這個時候 this.cache = { ".": {"name": "kongzhi", "msg": ["111"]}, "msg": ["111"]};
最后判斷,我們的 value 是否是個函數,如果是函數的話,就調用該函數執行。我們上面的demo就可以很好的列子,demo如下:
var data = { "name": "kongzhi", "msg": { "sex": " male ", "age": "31", "marriage": 'single' }, "wrapped": function() { return function(text, render) { return '<div>' + render(text) + '</div>' } } } var tpl = `{{#wrapped}} {{name}} is men {{/wrapped}}`; var html = Mustache.render(tpl, data); console.log(html); // 打印 <div> kongzhi is men </div>
代碼: if (isFunction(value)) { value = value.call(this.view);} 我們demo如上,wrapped 就是一個函數,因此它會調用執行,其中 this.view 參數的值為 = { "name": "kongzhi", "msg": ["111"] }; wrapped函數它自身返回一個函數,它會把值賦值給value,因此把value返回回去。因此 又回到 Writer.prototype.renderSection 函數內部,該函數內部又會判斷該value 是否是一個數組,如果是個數組的話,執行某些操作,或者 他是一個對象、一個數字、一個字符串、就執行另外一些操作,或者它是一個函數就調用該函數。如下 Writer.prototype.renderSection 函數代碼可知:
Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) { var self = this; var buffer = ''; var value = context.lookup(token[1]); // This function is used to render an arbitrary template // in the current context by higher-order sections. function subRender (template) { return self.render(template, context, partials); } if (!value) return; if (isArray(value)) { for (var j = 0, valueLength = value.length; j < valueLength; ++j) { buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate); } } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') { buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate); } else if (isFunction(value)) { if (typeof originalTemplate !== 'string') throw new Error('Cannot use higher-order sections without the original template'); // Extract the portion of the original template that the section contains. value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); if (value != null) buffer += value; } else { buffer += this.renderTokens(token[4], context, partials, originalTemplate); } return buffer; }
因此 我們上面 value 返回的是 = ['111']; 他是一個數組。因此會進入第一個if語句代碼內部。因此循環該數組,由於該 value
數組里面只有 ['111'] 這樣的,因此就循環1次,會執行如下代碼:
buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
在調用該函數之前,會調用 context.push 函數,代碼如下:
function Context (view, parentContext) { this.view = view; this.cache = { '.': this.view }; this.parent = parentContext; } Context.prototype.push = function push (view) { return new Context(view, this); };
這里的this就是我們之前的 context對象了。因此此時的 context對象就變為如下值:
context = { view: '111', cache: {".": "111"}, parent: { parent: undefined, view: { "name": "kongzhi", "msg": ["111"] }, cache: { "msg": ["111"], ".": { "name": "kongzhi", "msg": ["111"] } } } };
我們之前的token值如下:
token = ["#", "msg", 0, 8, [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ] , 37 ];
因此調用 buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) { var buffer = ''; var token, symbol, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { value = undefined; token = tokens[i]; symbol = token[0]; if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate); else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate); else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context); else if (symbol === 'text') value = this.rawValue(token); if (value !== undefined) buffer += value; } return buffer; }
繼續遞歸調用該函數,那么 token[4] = [ ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " * <div>", 14, 22], ["name", "name", 22, 30], ["text", "</div> ", 30, 37] ];
context 值就是我們上面的值;
partials: 可重用的模板,目前沒有傳遞給參數,因此為undefined。
originalTemplate: 就是我們的模板,值為:"{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}";
如上我們把token[4] 作為參數傳遞進去,因此會循環該數組,然后 tokens[][0] === 'text' 只有該數組內的第一項,第三項,
第五項,當 tokens[i][0] === 'name'; 只有數組中的第二項和第四項。我們來分別來看下代碼如何執行的;
1. 第一個數組值為:["text", " ", 8, 9]; 因此:value = this.rawValue(["text", " ", 8, 9]); rawValue 函數代碼如下:
Writer.prototype.rawValue = function rawValue (token) {
return token[1];
};
該函數直接 返回 token[1]; 因此 value = " "; 因此第一次循環 buffer = " ";
2. 第二個數組值為:["name", ".", 9, 14]; 因此 value = this.escapedValue(token, context); escapedValue 函數代碼:
/* @param {token} ["name", ".", 9, 14] @param {context} context = { view: '111', cache: {".": "111"}, parent: { parent: undefined, view: { "name": "kongzhi", "msg": ["111"] }, cache: { "msg": ["111"], ".": { "name": "kongzhi", "msg": ["111"] } } } }; */ Writer.prototype.escapedValue = function escapedValue (token, context) { var value = context.lookup(token[1]); if (value != null) return mustache.escape(value); }; Context.prototype.lookup = function lookup (name) { var cache = this.cache; var value; if (cache.hasOwnProperty(name)) { value = cache[name]; } else { } // .... return value; }
如上代碼可以看到,我們的token[1] = '.'; 因此先調用 Context.prototype.lookup 這個方法,該方法內部的this指向了
context 對象,我們可以從上面分析可以知道 context對象有哪些值了。因此 this.cache = context.cache = {".": "111"};
因此 if (cache.hasOwnProperty(name)) {} 條件為true,因此 value = cache[name]; 即:value = "111";
最后返回 Writer.prototype.escapedValue 函數內部代碼,
if (value != null) {
return mustache.escape(value);
};
從源碼當中我們知道:
var entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }; mustache.escape = escapeHtml; function escapeHtml (string) { return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { return entityMap[s]; }); }
最后就會返回一個字符串,從該代碼中,我們也可以看到,如果模板中有 <a> 這樣類似的標簽的時候,它會轉換成 "<a>" 這樣
的,會對html標簽進行轉義操作。因此這里我們的值 value 就返回字符串 "111" 了。 buffer = " " + "111" 因此 最后 buffer = " 111";
3. 第三個數組為:["text", " * <div>", 14, 22]; 因此:value = this.rawValue(["text", " * <div>", 14, 22]); rawValue 函數代碼如下:
Writer.prototype.rawValue = function rawValue (token) {
return token[1];
};
因此 value = " * <div>";
最后 buffer = " 111 * <div>";
4. 第四個數組為:["name", "name", 22, 30]; 因此 value = this.escapedValue(token, context); 步驟和第二步一樣的。
escapedValue 函數代碼(為了方便查看,繼續貼下代碼):
/* @param {token} ["name", "name", 22, 30] @param {context} context = { view: '111', cache: {".": "111"}, parent: { parent: undefined, view: { "name": "kongzhi", "msg": ["111"] }, cache: { "msg": ["111"], ".": { "name": "kongzhi", "msg": ["111"] } } } }; */ Writer.prototype.escapedValue = function escapedValue (token, context) { var value = context.lookup(token[1]); if (value != null) return mustache.escape(value); }; Context.prototype.lookup = function lookup (name) { var cache = this.cache; var value; if (cache.hasOwnProperty(name)) { value = cache[name]; } else { var context = this, intermediateValue, names, index, lookupHit = false; while (context) { if (name.indexOf('.') > 0) { // ... 代碼省略 } else { intermediateValue = context.view[name]; lookupHit = hasProperty(context.view, name); } if (lookupHit) { value = intermediateValue; break; } context = context.parent; } cache[name] = value; } // .... return value; }
如上代碼,執行 var value = context.lookup(token[1]); 因此 token[1] = "name"; 由上可知:cache = {".": "111"};
因此代碼會執行else代碼內部;進入 while循環內部,if (name.indexOf('.') > 0) {} 判斷 "name" 是否能找到 ".", 這里是找不到的,因此又進去else代碼內部。因此 intermediateValue = context.view[name] = context.view["name"]; 由上面的context的值我們可知,context.view = "111"; 因此 intermediateValue = undefined; 找不到該值;同理 lookupHit = false; 因此 context = context.parent; 查找父級元素,依次類推.... 由上面可知,我們知道 context的值了, 繼續看下context 值吧,如下所示:
context = { view: '111', cache: {".": "111"}, parent: { parent: undefined, view: { "name": "kongzhi", "msg": ["111"] }, cache: { "msg": ["111"], ".": { "name": "kongzhi", "msg": ["111"] } } } };
我們執行 context = context.parent; 它是有的,因此會繼續進入下一次while循環代碼,因此此時的context值就變為如下了:
context = { parent: undefined, view: { "name": "kongzhi", "msg": ["111"] }, cache: { "msg": ["111"], ".": { "name": "kongzhi", "msg": ["111"] } } }
和上面的操作一樣,也會進入else語句代碼內,現在需要執行如下代碼:
intermediateValue = context.view[name]; lookupHit = hasProperty(context.view, name);
因此 intermediateValue = context.view["name"] = "kongzhi"; lookupHit = true; 因此會執行如下代碼:
if (lookupHit) { value = intermediateValue; break; }
最后我們的 value = "kongzhi" 了,使用break語句,跳出while循環,如上可以看到,如果我們這一次又沒有找到該值的話,它還會繼續往它的父級元素上面的遞歸查找是否有該值,如果有直到找到為止,否則的話,就找不到。直接返回 name 這個未解析的變量。
跳出while循環后,就執行 cache[name] = value; 因此這個時候 cache 的值變為如下:
cache = { ".": 111, "name": "kongzhi" }
此時此刻,我們全局的context的值就變為如下了:
context = { view: '111', cache: {".": "111", "name": "kongzhi"}, parent: { parent: undefined, view: { "name": "kongzhi", "msg": ["111"] }, cache: { "msg": ["111"], ".": { "name": "kongzhi", "msg": ["111"] } } } };
下面還有如下代碼需要執行:
if (isFunction(value)) value = value.call(this.view); return value;
如上,如果該value是一個函數的話,就會返回一個函數給value;否則的話,直接把值value返回給回去。因此我們需要跳到Writer.prototype.escapedValue 函數中,如下代碼:
Writer.prototype.escapedValue = function escapedValue (token, context) { var value = context.lookup(token[1]); if (value != null) return mustache.escape(value); };
返回回來的value = "kongzhi"; 因此會調用 mustache.escape(value); 函數返回回去,escape 函數我們之前講過,它是對html標簽進行轉義的,因此這里為了節約篇幅,就不貼代碼了。執行完成后,把值返回回去。因此我們現在又需要跳到Writer.prototype.renderTokens 函數中,再看剩下的代碼了,為了查看方便,我繼續貼下該函數代碼:
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) { var buffer = ''; var token, symbol, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { value = undefined; token = tokens[i]; symbol = token[0]; if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate); else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate); else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context); else if (symbol === 'text') value = this.rawValue(token); if (value !== undefined) buffer += value; } return buffer; }
因此 執行下面代碼 if (value !== undefined) { buffer += value; } 代碼了,從上面第三步我們知道 buffer的值了。
buffer = " 111 * <div>"; 因此我們繼續字符串拼接,buffer += value; 因此 buffer = " 111 * <div>kongzhi";
5. 第五個數組為:token = ["text", "</div> ", 30, 37]; 由於 token[0] = "text"; 因此:value = this.rawValue(["text", " * <div>", 14, 22]); rawValue 函數代碼如下:
Writer.prototype.rawValue = function rawValue (token) {
return token[1];
};
因此 value = "</div> "; 最后我們又判斷 if (value !== undefined) { buffer += value; }; 在第四步我們知道
buffer的值 = " 111 * <div>kongzhi"; 因此 該值繼續和 "</div> " 字符串拼接的話,最后我們的buffer值就為如下了:
buffer = " 111 * <div>kongzhi</div> ";
如上就是整個模板的解析的過程。當然 mustache.js 里面還有很多未講解到的代碼,比如兼容到一些其他的情況。比如在:
Writer.prototype.renderTokens 函數中,還有如下:
if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate); else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate); else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context); else if (symbol === 'text') value = this.rawValue(token);
如上我們還有三個函數沒有講解到,比如 symbol === '^' 需要調用 this.renderInverted()函數,symbol === '>' 需要調用this.renderPartial()函數,symbol === '&' 需要調用 this.unescapedValue() 這個函數。我們可以貼下他們的代碼如下:
Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) { var value = context.lookup(token[1]); // Use JavaScript's definition of falsy. Include empty arrays. // See https://github.com/janl/mustache.js/issues/186 if (!value || (isArray(value) && value.length === 0)) return this.renderTokens(token[4], context, partials, originalTemplate); }; Writer.prototype.renderPartial = function renderPartial (token, context, partials) { if (!partials) return; var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; if (value != null) return this.renderTokens(this.parse(value), context, partials, value); }; Writer.prototype.unescapedValue = function unescapedValue (token, context) { var value = context.lookup(token[1]); if (value != null) return value; };
如上代碼原理也是一樣的,這里就不做一一分析了,覺得有意思的,可以自己分析下。到這里源碼是分析完了。我們可以從頭一步步去理解下mustache.js 模板引擎如何解析的思路。