vue系列---Mustache.js模板引擎介紹及源碼解析(十)

mustache.js(3.0.0版本) 是一個javascript前端模板引擎。官方文檔(https://github.com/janl/mustache.js)javascript

根據官方介紹:Mustache能夠被用於html文件、配置文件、源代碼等不少場景。它的運行得益於擴展一些標籤在模板文件中,而後使用一個hash字典或對象對其進行替換渲染操做。html

基本語法以下:前端

1. {{ keyName }}: 讀取屬性值, 若是有html標籤的話,會被轉義。
2. {{{ keyName }}}: 讀取屬性值且原樣輸出,即html不進行轉義。
3. {{ #keyName }} {{ /keyName }}: 用於遍歷。
4. {{ ^keyName }} {{ /keyName }}: 反義數據,當keyName不存在、或爲null,或爲false時會生效。能夠理解至關於咱們js中的 !(非)。
5. {{.}}: 用於遍歷數組。
6. {{ !comments }}: 用於註釋。
7. Partials: 使用可重用的模板,使用方式:{{> 變量}}。java

1. 變量 {{ keyName }} 或 {{{ keyName }}}node

標籤最主要是經過一個變量來使用。好比 {{ keyName }}標籤在模板中會嘗試查找keyName這個變量在當前的上下文中,若是上下文中不存在keyName變量,那麼它會經過遞歸的方式依次查找它的父級元素,依次類推... 若是最頂級的上下文中依然找不到的話,那麼該keyName變量就不會被渲染。不然的話,keyName標籤就會被渲染。
若是變量中存在html標籤會被轉義的。所以若是咱們不想html標籤轉義的話,咱們可使用三個花括號 {{{ keyName }}}.git

好比以下列子:
項目基本結構以下:github

|--- mustache 文件夾
| |--- index.html
| |--- mustache.js (庫文件)

基本代碼以下所示:apache

<!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> &lt;a&gt;kongzhi&lt;a&gt;</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 }}; 效果是同樣的。app

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 = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
    '/': '&#x2F;',
    '`': '&#x60;',
    '=': '&#x3D;'
  };
  // 轉換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 = {
          '&': '&amp;',
          '<': '&lt;',
          '>': '&gt;',
          '"': '&quot;',
          "'": '&#39;',
          '/': '&#x2F;',
          '`': '&#x60;',
          '=': '&#x3D;'
        };
        mustache.escape = escapeHtml;
        function escapeHtml (string) {
          return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
            return entityMap[s];
          });
        }

最後就會返回一個字符串,從該代碼中,咱們也能夠看到,若是模板中有 <a> 這樣相似的標籤的時候,它會轉換成 "&lt;a&gt;" 這樣
的,會對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 模板引擎如何解析的思路。

相關文章
相關標籤/搜索